开启漫漫SpringBOOT之路(2)
因为一个项目开发的需要,所以漫漫SpringBoot之路估计要不断加速,现在进入第二篇,实现一下SpringBoot开发完整后台的web项目骨架,整个思路就会开始调整。
(1)项目概要设计
后台的项目骨架主要还是相关业务模块里的数据增删改查业务。我们设计一下用户的管理和赛事活动的管理模块。其中会员管理包括用户列表、用户登录日志、会员管理菜单,赛事活动包括赛事活动发布、赛事活动修改、赛事活动删除、赛事活动报名等。看起来已经够复杂的了。
如下后台管理系统的基本设计,包括两个大的菜单:会员管理和赛事活动管理。
这个管理系统就是纯的H5+JS+CSS,CSS负责样式管理,js作用大了,渲染模板显示以及数据读取显示等。
本文先实现用户部分的管理。
数据库部分里包括了用户表和赛事表,后续会对应到两个实体类。如果考虑用户参与赛事报名,还需要添加第三个表报名表。
-- auto-generated definition
create table user
userID int auto_increment
primary key,
username varchar(100) null,
userpwd varchar(100) null,
userphone varchar(20) null,
level int default 1 null
create table `match`
matchID int auto_increment
primary key,
matchname varchar(100) null,
matchdate varchar(100) null,
matchplace varchar(20) null,
matchhost varchar(50) null,
matchstatus int default 1
# 赛事报名表
create table match_fillin
fillinID int auto_increment
primary key,
username varchar(50) null,
matchname varchar(50) null,
fillTime varchar(50)
);
选用开发技术:后台管理springboot+mybatis,视图端通过ajax获取数据渲染显示,也是一种分离操作。其实主要是不想使用thymeleaf,这样前端模板表格框架可以选用x-admin。x-admin是一个集成了layui组件的管理模板框架,使用时只需要根据需要修改调整菜单文字内容即可。
(2)创建服务器端spring boot restful数据接口
第一步,创建一个springboot项目,名称为spring02,如下效果图:
选用相关依赖,包括springweb,mybatis,mysqlDriver和lombok插件等:
第二步,数据库建库建表,设置字段和关系
包括三张表:user、match、match_fillin,具体结构如上图。
第三步,配置数据库接口属性、mybatis配置,就是修改application.yml文件
#数据库配置
spring:
datasource:
url: jdbc:mysql://49.232.194.141:3306/spring02?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: xxx
driver-class-name: com.mysql.cj.jdbc.Driver
#mybatis配置
#xml文件存放的位置为resources目录下的mapper文件夹里,命名为*mapper.xml
#mapper文件中实体类型与自己定义的实体类要一致
mybatis:
mapper-locations: classpath:mapper/*mapper.xml
type-aliases-package: com.ricecoder.spring02.pojo
第四步,根据数据表设计开始创建实体pojo,包括User,Match和MatchFillin。并使用lombok插件完成构造函数、set和get等方法注解。
以User实体为例,在主程序类同级别目录下创建一个pojo包,然后创建User类,在里面写入如下内容:
package com.ricecoder.spring02.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int userID; //会员id
private String username; //会员姓名
private String userpwd; //会员密码
private String userphone; //会员手机号
private String level; //会员级别
}
其余两个类似:
package com.ricecoder.spring02.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Match {
private int matchID; //赛事ID
private String matchname; //赛事名称
private String matchdate; //赛事日期
private String matchplace; //赛事举办地点
private String matchhost; //赛事主办方
private int matchstatus; //赛事状态
}
第五步,按照基本的逻辑顺序,先pojo包,然后mapper包,然后service包,然后controller包,pojo包为实体类,mapper包为数据接口类,service包为数据实现类,controller包为控制器路由类。其中mapper包与原来的dao功能完全一致,如下以User实体类为例:
在mapper包里定义对User的增删改查方法接口:
package com.ricecoder.spring02.mapper;
import com.ricecoder.spring02.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
//用户数据接口
@Repository
public interface UserMapper {
// 查询所有用户接口
public List<User> queryAllUsers();
// 根据ID查询用户接口
public User queryUserByID(int userID);
// 往数据库插入单条数据接口
public Boolean addUsers(User user);
// 根据用户的信息更新现有用户接口
public Boolean updateUsers(int userID,String userpwd);
// 根据指定的id来删除该用户接口
public Boolean deleteUsers(int userID);
}
然后在service包里定义具体实现方法:
package com.ricecoder.spring02.service;
import com.ricecoder.spring02.mapper.UserMapper;
import com.ricecoder.spring02.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
//注解声明为Serivce
@Service
public class UserService implements UserMapper {
@Autowired //直接输入mapper类
UserMapper userMapper;
@Override //重写接口方法,查询所有用户
public List<User> queryAllUsers() {
//对查询结果返回类型定义为List
List<User> allusers = userMapper.queryAllUsers();
return allusers;
@Override //重写接口方法,根据ID查询该用户信息
public User queryUserByID(int userID) {
return userMapper.queryUserByID(userID);
@Override //重写接口方法,增加用户数据
public Boolean addUsers(User user) {
return userMapper.addUsers(user);
@Override //重写接口方法,更新用户数据
public Boolean updateUsers(int userID,String userpwd) {
return userMapper.updateUsers(userID,userpwd);
@Override //重写接口方法,删除用户数据
public Boolean deleteUsers(int userID) {
return userMapper.deleteUsers(userID);
}
这些实现方法在与mybatis结合的时候有两种方式,一个是直接在这个mapper层方法上使用mybatis的注解形式,例如:
// 查询所有用户接口
@Select("select * from user")
public List<User> queryAllUsers();
不过为了便于统一管理,大多数情况下会将所有sql注解语句都归纳放到一个xml配置文件中,也就是之前application.yml文件中设置的mapper文件路径。对于这个User实体类的增删改查操作,将sql操作都归纳到user_mapper.xml文件中,该文件在resources目录下mapper文件夹中,符合之前的yml配置设定。具体内容参考如下:
<?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="com.ricecoder.spring02.mapper.UserMapper">
<!-- 查询记录-->
<select id="queryAllUsers" resultType="User">
select * from user
</select>
<!-- 查询某个用户的信息记录-->
<select id="queryUserByID" resultType="User">
select * from user where userID=#{userID}
</select>
<!-- 插入记录-->
<insert id="addUsers" useGeneratedKeys="true" keyProperty="userID"
parameterType="com.ricecoder.spring02.pojo.User">
insert into user values(null,#{username},#{userpwd},#{userphone},#{level})
</insert>
<!-- 修改记录-->
<update id="updateUsers" parameterType="com.ricecoder.spring02.pojo.User">
update user set userpwd=#{userpwd} where userID=#{userID}
</update>
<!-- 删除记录-->
<delete id="deleteUsers" parameterType="com.ricecoder.spring02.pojo.User">
delete from user where userID = #{userID}
</delete>
</mapper>
最后回到针对用户的controller类,里面主要是具体请求路由设定和业务处理方法,由于要完成restful风格的api接口,数据格式都是json的,所以在组件里使用了RestController注解。
package com.ricecoder.spring02.controller;
import com.ricecoder.spring02.pojo.User;
import com.ricecoder.spring02.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController //注解使用restcontroller
@RequestMapping("/user") //总的path /user
public class UserController {
@Autowired //注入实现类
UserService userService;
// url: host:port/user/queryAll,get请求
@RequestMapping("/queryAll")
public List<User> getAllUsers(){
return userService.queryAllUsers();
// url: host:port/user/userQueryByID/id , post请求
// 路由设定方法 /userQueryByID/{userID},采用这种a/b/c方式请求
// 这里需要使用@PathVariable方式传递参数userID
@RequestMapping("/userQueryByID/{userID}")
public User queryUserByID(@PathVariable("userID") int userID ) {
return userService.queryUserByID(userID);
// url: host:port/user/userAdd ,post请求 增加新用户记录
@RequestMapping("/userAdd")
public Boolean addUsers() {
return userService.addUsers(new User(1, "aotou123", "126656", "1381000000", "1"));
// url: host:port/user/userUpdate?userID=1&userpwd=1
// 请求方式为多参数带问号时则使用@RequestParam接收参数变量
// restful api风格则使用url: host:port/user/userUpdate/{userID}/{userpwd}
@RequestMapping("/userUpdate")
public Boolean UpdateUserByID(@RequestParam("userID") int userID,
@RequestParam("userpwd") String userpwd) {
return userService.updateUsers(userID,userpwd);
// restful api风格则使用url: host:port/user/userUpdate/{userID}/{userpwd}
@RequestMapping("/userUpdate/{userID}/{userpwd}")
public Boolean updateUserByID(@PathVariable("userID") int userID,
@PathVariable("userpwd") String userpwd) {
return userService.updateUsers(userID,userpwd);
}
到这里有关User的业务流程我们就准备完了,不过还有最后一项,由于使用mybatis配置,所以在主启动类前面还需要添加一个注解扫描,给定mapper的路径:
package com.ricecoder.spring02;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#添加扫描注解
@MapperScan(basePackages = "com.ricecoder.spring02.mapper")
@SpringBootApplication
public class Spring02Application {
public static void main(String[] args) {
SpringApplication.run(Spring02Application.class, args);
}
然后可以启动服务进行实际的测试,不用postman也可以的,直接在浏览器请求即可:
同样的方式可以对Match实体类进行业务设计,另外的赛事报名数据表也可以同理完成。
(3)做好后端接口后,就可以与前端进行数据交互了,下面来测试一下x-admin管理框架对用户数据的管理。
第一步,请求服务器获得用户列表,将数据传递到前端页面显示。
由于网页内容较多,这里重点介绍一下思路。x-admin框架也是基于layui框架组件实现的,其中的用户列表主要用到的组件就是动态表格。动态表格就是数据是通过ajax异步请求方式从服务器端获取然后渲染显示到表格中。所以在这一步过程中有几个地方需要调整:
layui对表格渲染可以参考其官网,这里我们依葫芦画瓢定义一个div,里面放入table标签内容:
<div class="layui-card-body layui-table-body layui-table-main">
<table id="tabUser" layui-filter="tabUser"></table>
</div>
然后网页端开始利用jquery的ajax请求获得表格所需要的数据:
layui.use('table', function(){
var table = layui.table;
table.render({
elem:'#tabUser', #绑定到id为tabUser的table上
url:'http://127.0.0.1:8080/user/queryAll', #服务器端数据
page:true, #支持分页,默认10行一页
cols:[[ #定义表头
{field:'userID',title:'用户ID'},
{field:'username',title:'用户'},
{field:'userphone',title:'用户phone'},
{field:'level',title:'级别'},
{field:'menu', title: '操作', width:177}
})
直接运行时发现表头正常,但数据是空的,此时需要知道layUI在动态表格方面的渲染数据格式要求,其默认的json格式为:{'code':0,'msg':'ok','data':[{},{}]'},这种json格式必须满足才能正常渲染,其中code的值默认为0,也必须是0,msg的值就随便,data的值就是实际的数据行,每一行一个对象数组。这样的话在服务器端也需要准备成这样的格式,因此首先要去服务器端修改输出数据的json格式,然后对layui表格代码进行修改。
步骤一:在pojo包内定义一个响应体RespEntity,设置三个属性:code,msg,data:
package com.ricecoder.spring02.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RespEntiry {
private int code; #对应输出体的code
private String msg; #对应输出体的msg
private Object data; #对应输出体的对象数组
}
步骤二:到控制器请求方法函数里修改输出数据格式
// url: host:port/user/queryAll,get请求
// 重新调整输出响应体,输出格式为RespEntity对象
@RequestMapping("/queryAll")
public RespEntiry getAllUsers() throws JsonProcessingException {
RespEntiry res = new RespEntiry();
List<User> users = userService.queryAllUsers(); //查询结果
if(users!=null){ //如果查询结果存在
res.setCode(0); //设定响应体code值为0
res.setMsg("success"); //设定msg值为success
res.setData(users); //输出data值为查询的结果集
}else{
res.setCode(200);
res.setMsg("failed");
res.setData(users);
return res;
}
这样做完后,再从浏览器里发送请求,结果参考如下:
步骤三,修改layui表格代码
数据没问题了,接下来是对layui在表格那里的编辑,主要目的是想在每行数据最后增加一列操作列,实现对该行数据的查询、修改或删除等操作。根据layui官网教程,可以先使用script脚本定义一下最后一列的内容,注意那个设定的id名称,这里为barDemo
<script type="text/html" id="barDemo">
<a class="layui-btn layui-btn-mini" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-mini" lay-event="del">删除</a>
</script>
基于这个然后将layui渲染表格的代码对应修改一下:
layui.use('table', function() {
var table = layui.table;
table.render({
elem: '#tableUser',
url: 'http://127.0.0.1:8080/user/queryAll',
page: true,
cols: [[
{field: 'userID', title: '用户ID'},
{field: 'username', title: '用户'},
{field: 'userphone', title: '用户phone'},
{field: 'level', title: '级别'},
{field: '', title: '操作', width: 177, toolbar: '#barDemo'} #这里就是增加的操作列,注意那个id名称与这里对应
//监听表格里增加的操作按钮选择
table.on('tool(tableUser)', function(obj){
if(obj.event === 'edit'){
alert('edit'); //测试看是否被选中,后续还需要增加代码
else if(obj.event === 'del'){
alert('del'); //测试看是否被选中,后续还需要增加代码
});
步骤四,可以开心的请求数据并显示到页面上了:
如此就完成了数据库中现有用户的列表显示,至于编辑和删除以及添加操作接下来我们再进行。
(4)继续前后端交互,完成用户编辑和删除功能
这个框架模板中有许多弹窗的使用,例如对于点击现有记录的编辑或删除按钮时,就需要弹窗显示,模块中使用的语法为:
xadmin.open('删除用户','./member-del.html?id='+data.userID,600,400)
第一个参数为窗体标题,第二个参数为弹窗显示的页面,同时可以携带参数,第三个和第四个分别为窗体的高和宽大小。这样可以对修改和删除操作进行如下代码操作:
//监听表格复选框选择,其中tool表示toolbar,tableUser与lay-filter里设定的值保持一致
table.on('tool(tableUser)', function(obj){
var data = obj.data;
//针对当行编辑操作
if(obj.event === 'edit'){
xadmin.open('修改用户','./member-edit.html?id='+data.userID+'&username='+data.username+'&userphone='+data.userphone+'&userlevel='+data.level,600,400);
}else if(obj.event === 'del'){
xadmin.open('删除用户','./member-del.html?id='+data.userID,600,400)
});
弹窗的html代码需要先根据情况编写好,例如编辑member-edit.html,其代码参考如下:
<!DOCTYPE html>
<html class="x-admin-sm">
<meta charset="UTF-8">
<title>编辑用户页面</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi" />
<link rel="stylesheet" href="./css/font.css">
<link rel="stylesheet" href="./css/xadmin.css">
</head>
<div class="layui-fluid">
<div class="layui-row">
<div class="layui-form-item">
<label for="L_email" class="layui-form-label">
<span class="x-red">*</span>当前用户名</label>
<div class="layui-input-inline">
<input type="text" id="username" name="username" required="" lay-verify="email" autocomplete="off" class="layui-input"></div>
<div class="layui-form-item">
<label for="L_username" class="layui-form-label">
<span class="x-red">*</span>用户手机号</label>
<div class="layui-input-inline">
<input type="text" id="userphone" name="userphone" required="" lay-verify="nikename" autocomplete="off" class="layui-input"></div>
<div class="layui-form-item">
<label for="L_pass" class="layui-form-label">
<span class="x-red">*</span>用户级别</label>
<div class="layui-input-inline">
<input type="text" id="userlevel" name="userlevel" required="" lay-verify="pass" autocomplete="off" class="layui-input"></div>
<div class="layui-form-mid layui-word-aux">1- 普通; 2-专家</div></div>
<div class="layui-form-item">
<label for="L_repass" class="layui-form-label"></label>
<button class="layui-btn" lay-filter="add" lay-submit="" id="btn_submit">修改</button></div>
</body>
<script type="text/javascript" src="./lib/layui/layui.js" charset="utf-8"></script>
<script type="text/javascript" src="./js/xadmin.js"></script>
<script type="text/javascript" src="./js/jquery.min.js"></script>
<!-- 让IE8/9支持媒体查询,从而兼容栅格 -->
<!--[if lt IE 9]>
<script src="https://cdn.staticfile.org/html5shiv/r29/html5.min.js"></script>
<script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<script type="text/javascript" src="./js/jquery.min.js"></script>
<![endif]-->
<script>
<!-- url传值过来获取到的值 -->
// 获取传递过来的用户参数值
userID = GetQueryString("id");
username = GetQueryString("username");
userphone = GetQueryString("userphone");
userlevel = GetQueryString("userlevel");
$('#username').val(username);
$('#userphone').val(userphone);
$('#userlevel').val(userlevel);
//修改后异步请求发送到服务器端进行修改
$('#btn_submit').click(function () {
var params={};
params.userID = userID;
params.username = $('#username').val();
params.userphone = $('#userphone').val();
params.level = $('#userlevel').val();
console.log(params)
//获得url传参的函数写法
function GetQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
var r = window.location.search.substr(1).match(reg);
if(r != null) return decodeURI(r[2]);
return null;
</script>
</html>
仔细看内容还是挺多的,相当于一个完整的html页面了,只不过在形式上弹窗而已,效果如下:
在修改了用户信息后,点击修改按钮发送ajax请求给服务器实现更新操作,如果返回的状态码为200,就表明更新成功了。由于之前在设定UserService实现类里用于修改用户密码,而现在是修改用户的其他信息,如用户名、用户手机号和用户级别,所以需要在UserMapper里重新添加一个更新用户信息方法接口:
// 根据用户的信息更新现有用户密码接口
public Boolean updateUserPassword(int userID,String userpwd);
// 根据用户的信息更新现有用户信息接口
public Boolean updateUsers(User user);
在UserService里增加一个实现方法:
// 重写接口方法,修改用户密码
@Override
public Boolean updateUserPassword(int userID, String userpwd) {
return null;
// 重写接口方法,修改用户其他信息
@Override
public Boolean updateUsers(User user) {
return userMapper.updateUsers(user);
}
然后在user_mapper.xml配置文件中增加sql操作语句:
<!-- 修改用户信息记录-->
<update id="updateUsers" parameterType="com.ricecoder.spring02.pojo.User">
update user set userphone=#{userphone},username=#{username},level=#{level} where userID=#{userID}
</update>
最后在UserController控制器里来设定路由请求和参数传递规则:
@RequestMapping("/userUpdateInfo")
public RespEntiry updateUsers(@RequestBody User user) {
RespEntiry res = new RespEntiry();
Boolean flag = userService.updateUsers(user); //查询结果
System.out.println(flag);
if(flag){ //如果查询结果存在
res.setCode(200); //设定响应体code值为0
res.setMsg("success"); //设定msg值为success
res.setData(flag); //输出data值为查询的结果
}else{
res.setCode(404);
res.setMsg("failed");
res.setData(flag);
return res;
}
服务器端准备好后,再对前端ajax发送的数据格式进行封装,如下:
// 获取传递过来的用户参数值
userID = GetQueryString("id");
username = GetQueryString("username");
userphone = GetQueryString("userphone");
userlevel = GetQueryString("userlevel");
$('#username').val(username);
$('#userphone').val(userphone);
$('#userlevel').val(userlevel);
//修改后异步请求发送到服务器端进行修改
$('#btn_submit').click(function () {
var params={};
params.userID = userID;
params.username = $('#username').val();
params.userphone = $('#userphone').val();
params.level = $('#userlevel').val();
$.ajax({
url:'http://127.0.0.1:8080/user/userUpdateInfo',
type:'post',
datatype:'json',
contentType : 'application/json',
data: JSON.stringify(params),
success:function(res){
if(res.code ==200){
layer.alert("更新成功", {
icon: 6
function() {
//关闭当前frame
layer.closeAll();
xadmin.father_reload();
}else{
layer.alert("更新失败", {
icon: 2
function() {
//关闭当前frame
xadmin.close();
})
测试更新数据成功, 但layui弹窗的关闭确成了个大问题。看看有没有看过layui的朋友帮给个帮助,即点击修改按钮,弹出一个窗口,修改成功弹窗提示更新成功然后可以自动关闭消息窗口以及背后的修改窗口,直接回到原来的列表窗口中而且实现一下刷新,寻找了许久也没搞定。看来还是对layui框架不太熟悉导致的。
不过数据交互已经成功了,就是修改部分。对于删除就更简单一些,只需要获得该条记录的ID,然后提交给后台的controller部分就可以实现删除。在member-list.html列表JavaScript部分增加一下删除部分的代码:
table.on('tool(tableUser)', function(obj){
var data = obj.data;
//针对当行编辑操作
if(obj.event === 'edit'){
xadmin.open('修改用户','./member-edit.html?id='+data.userID+'&username='+data.username+'&userphone='+data.userphone+'&userlevel='+data.level,600,600)
}else if(obj.event === 'del'){
var userID = data.userID;
$.ajax({
url: 'http://127.0.0.1:8080/user/userDel?userID='+userID,
type: 'get',
success: function (res) {
if (res.code == 200) {
layer.msg("删除成功");
xadmin.close();
xadmin.father_reload();
});
然后再回到springboot的usercontroller部分增加删除部分控制器逻辑:
// restful api风格则使用url: host:port/user/userdel?userID
@RequestMapping("/userDel")
public RespEntiry userDeleteById(@RequestParam("userID") int userID) {
RespEntiry res = new RespEntiry();
Boolean flag = userService.deleteUsers(userID); //查询结果
System.out.println(flag);
if(flag){ //如果查询结果存在
res.setCode(200); //设定响应体code值为0
res.setMsg("success"); //设定msg值为success
res.setData(flag); //输出data值为查询的结果
}else{