1.实现用户新增
这一节我们要完成新增用户的前后台代码。
请求参数封装
Controller控制器的方法中,可以接收HttpServletRequest对象,这个对象中包含了前台的请求参数信息。
为了方便开发,一般我们都会对其进行封装,比如HashMap就挺香的,用HashMap获取某个参数,只需要我们用get方法。给HashMap设置某个参数,我们可以用put方法,反正很爽就对了。
我们单独封装一个方法,把HttpServletRequest转换为HashMap
/**
* 将请求对象的参数转换为HashMap
* @param request
* @return
*/
public static Map<String, Object> handleParamToMap(
HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
for (Map.Entry<String, String[]> entry : request.getParameterMap()
.entrySet()) {
String[] arr = entry.getValue();
String result = "";
if (null != arr && arr.length > 0) {
for (int i = 0; i < arr.length; i++) {
result += arr[i];
if (i < arr.length - 1) {
result += ",";
}
}
map.put(entry.getKey(), result);
}
}
return map;
}
具体的实现细节可以忽略,反正它可以完成我们的要求就对了。
jdbcTemplate的update方法可以接收两个参数,第一个参数是sql语句,insert语句相信我们都不陌生,是sql语法的基础。
jdbcTemplate支持这种写法,比如:
(该图片来自于网络)
按理说后面的values里面需要填写具体的参数值,但是加一个:就变成了一个占位符。比如:ln,就会对应到Map中key=ln的值。
知道这个原理后,我们就可以尝试写出保存用户的方法了。
saveUser
@PutMapping("saveUser")
@ResponseBody
public Map<String, Object> saveUser(HttpServletRequest request){
//拼装请求参数
Map<String, Object> params = handleParamToMap(request);
//设置返回信息
Map<String, Object> resultMap = new HashMap();
resultMap.put("errCode",0); //错误码
resultMap.put("errMsg",null);//错误信息
int update = jdbcTemplate.update("INSERT INTO `vipmgr`.`user` (`user_name`, `create_time`, `header_pic`, `ip_addr`, `is_delete`, `is_logined`, `is_vip`, `last_login_time`, `nick_name`, `password`, `role_id`, `amt`, `last_sign_date`) " +
"VALUES (:userName, :createTime, NULL, :ipAddr, '0', '0', '0', :lastLoginTime, :nickName, :password, '1', 0, NULL)",params);
if(update != 1){
resultMap.put("errCode",11111); //错误码
resultMap.put("errMsg","系统异常,请联系管理员!");//错误信息
}
return params;
}
做一个整理,我们需要的参数有:
userName,createTime,ipAddr,lastLoginTime,nickName,password
其中加粗的部分是从前台传过来的,其他字段需要我们自己想办法来组装。
createTime:创建时间
lastLoginTime:上次登录时间 (额,搞错了,这个不用管,创建用户的时候还没有登录时间呢)
先看创建时间,这个用户是当前这个时间点创建的,所以我们直接想办法获取当前时间就行了。
找到我们的老朋友:Hutool,在pom.xml中添加:
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.6.3</version>
</dependency>
然后这样写:
params.put("createTime", DateUtil.now());
DateUtil是Hutool里面关于时间处理的工具类。
ipAddr是IP地址的意思,如果是用户注册的时候调用了这个方法,那么是可以获取用户当前网段的IP地址的
params.put("ipAddr", request.getRemoteAddr());
为了让前台能够知道方法的调用结果,我们需要设置返回信息,一般分为错误码和错误信息,如果错误码不是00000(返回成功),那么就需要对错误信息进行展示处理。
//设置返回信息
Map<String, Object> resultMap = new HashMap();
resultMap.put("errCode",0); //错误码
resultMap.put("errMsg",null);//错误信息
最终代码
@PutMapping("saveUser")
@ResponseBody
public Map<String, Object> saveUser(HttpServletRequest request){
//拼装请求参数
Map<String, Object> params = handleParamToMap(request);
//设置返回信息
Map<String, Object> resultMap = new HashMap();
resultMap.put("errCode",0); //错误码
resultMap.put("errMsg",null);//错误信息
params.put("createTime", DateUtil.now());
params.put("ipAddr", request.getRemoteAddr());
int update = jdbcTemplate.update("INSERT INTO `vipmgr`.`user` (`user_name`, `create_time`, `header_pic`, `ip_addr`, `is_delete`, `is_logined`, `is_vip`, `last_login_time`, `nick_name`, `password`, `role_id`, `amt`, `last_sign_date`) " +
"VALUES (:userName, :createTime, NULL, :ipAddr, '0', '0', '0', :lastLoginTime, :nickName, :password, '1', 0, NULL)",params);
if(update != 1){
resultMap.put("errCode",11111); //错误码
resultMap.put("errMsg","系统异常,请联系管理员!");//错误信息
}
return params;
}
教程写到这,没有进行测试,上面的代码是我一气呵成的。
如果有问题再说吧。
页面增加一个按钮
<div style="padding:5px;background:#fafafa;width:100%;border:1px solid #ccc">
<a href="#" class="easyui-linkbutton" iconCls="icon-add">增加用户</a>
</div>
设计保存窗口
在页面中添加
<div id="win" class="easyui-window" title="保存用户" style="width:400px;height:250px;">
<form style="padding:10px 20px 10px 40px;">
<p>用户名: <input name="userName" type="text"></p>
<p>密 码: <input name="password" type="password"></p>
<p>昵 称: <input name="nickName" type="text"></p>
<div style="padding:5px;text-align:center;">
<a href="#" class="easyui-linkbutton" icon="icon-ok">确认保存</a>
<a href="#" class="easyui-linkbutton" icon="icon-cancel">取消</a>
</div>
</form>
</div>
这就创建了一个EasyUI风格的窗口。
新增按钮点击事件
这个窗口默认是关闭着的,所以我们要添加这个属性
<div id="win" class="easyui-window" title="保存用户" style="width:400px;height:250px;" closed="true">
然后,给按钮添加点击事件,点击按钮才显示这个窗体:
<a href="#" class="easyui-linkbutton" iconCls="icon-add" onclick="openAddWin()">增加用户</a>
function openAddWin() {
$('#win').window('open');
}
保存方法
给提交按钮添加点击事件
<a href="#" class="easyui-linkbutton" icon="icon-ok" onclick="save()">确认保存</a>
save方法
function save() {
$.ajax({
url: 'user/saveUser',
data: $("#userForm").serialize(),
type: 'put',
dataType: 'json',
success: function (data) {
data = eval("(" + data + ")");
if (data.errCode != '00000') {
alert(data.errMsg);
return;
}
alert("保存成功!");
$('#win').window('close');
}
});
}
测试:
报错了:
java.sql.SQLException: Parameter index out of range (1 > number of parameters, which is 0).
错误分析:应该是sql语句写错了,参数个数不匹配导致的。
不要慌,调试一下
这是请求参数
这是SQL语句
INSERT INTO `vipmgr`.`user` (`user_name`, `create_time`, `header_pic`, `ip_addr`, `is_delete`, `is_logined`, `is_vip`, `last_login_time`, `nick_name`, `password`, `role_id`, `amt`, `last_sign_date`) VALUES (:userName, :createTime, NULL, :ipAddr, '0', '0', '0', :lastLoginTime, :nickName, :password, '1', 0, NULL)
可以看到,具名的参数有
:userName, :createTime,:ipAddr,:lastLoginTime, :nickName, :password,
一共6个,可是Map里面只有5个,少了谁呢?
少了lastLoginTime!
好吧,现在我们改一下sql,lastLoginTime设置为null
在运行,还是报这个错:
java.sql.SQLException: Parameter index out of range (1 > number of parameters, which is 0).
看来还有问题,emmm,错误信息说,我输入了一个参数,但实际上只有0个。
知道了,应该是方法没调对。
这是部分update方法的重载,竟然没有第二个参数是Map的。
我大意了啊,没有闪!
原来,具名的写法得用另一个对象,叫做namedParameterJdbcTemplate
引入:
@Autowired
NamedParameterJdbcTemplate namedParameterJdbcTemplate;
然后修改saveUser的方法,保存的地方是这样的
int update = namedParameterJdbcTemplate.update("INSERT INTO `vipmgr`.`user` (`user_name`, `create_time`, `header_pic`, `ip_addr`, `is_delete`, `is_logined`, `is_vip`, `last_login_time`, `nick_name`, `password`, `role_id`, `amt`, `last_sign_date`) " +
"VALUES (:userName, :createTime, NULL, :ipAddr, '0', '0', '0', NULL, :nickName, :password, '1', 0, NULL)",params);
这下就对了,可是前台又报错了。
eval有点问题,为什么呢?
这是因为,我们用ajax请求,已经设置了dataType是json,就不需要eval了。
去掉这一行代码即可。
再保存,就可以了。
咦?中文乱码。。。
解决方案:数据库连接时加上?characterEncoding=utf-8,问题解决。
把alert代码换成这个
$.messager.show({
title : '提示',
msg : "保存成功!",
timeout : 3000,
showType : 'slide'
});
查询与分页
后台简单实现(不分页)
@RequestMapping("queryUsers")
@ResponseBody
public Map<String, Object> queryUsers(HttpServletRequest request){
//设置返回信息
Map<String, Object> resultMap = new HashMap();
resultMap.put("rows",jdbcTemplate.queryForList("select * from user"));
return resultMap;
}
返回参数需要有一个rows,这边就全部查出来再返回。
data-grid代码
EasyUI的数据表格是(datagrid)以表格格式显示数据,并为选择、排序、分组和编辑数据提供了丰富的支持。
数据网格(datagrid)的设计目的是为了减少开发时间,且不要求开发人员具备指定的知识。它是轻量级的,但是功能丰富。它的特性包括单元格合并,多列页眉,冻结列和页脚,等等。
下面是用户列表的代码:
<div style="padding:5px;background:#fafafa;width:100%;border:1px solid #ccc;height:100%;">
<table id="dg" title="用户列表" class="easyui-datagrid" fitColumns="true"
pagination="true" rownumbers="true" url="user/queryUsers" >
<thead>
<tr>
<th field="cb" checkbox="true" align="center"></th>
<th field="user_name" width="50" align="center">用户名</th>
<th field="nick_name" width="100" align="center">昵称</th>
<th field="role_id" width="60" align="center">角色编号</th>
<th field="create_time" width="200" align="center">创建时间</th>
<th field="is_vip" width="200" align="center">是否VIP</th>
<th field="last_login_time" width="200" align="center">上次登录时间</th>
<th field="is_delete" width="200" align="center">用户状态</th>
</tr>
</thead>
</table>
</div>
启动项目,刷新页面就会访问后台的接口
easyUI本地化
这时候你会发现,为啥下面的分页模块是英文的?
这是因为我们还没有做本地化,方法如下
找到原来的easyUI下载包,里面有个local,复制到static
引入中文模块:
<script src="locale/easyui-lang-zh_CN.js"></script>
就变成这样啦:
后台分页原理
按照easyUI的套路,如果你要做分页,就得告诉他当前的列表数据,还有总条数。
至于当前查询的是第几页page,还有每页多少条,在发送查询接口的时候,easyui就会帮我们自动带上。
验证:
@RequestMapping("queryUsers")
@ResponseBody
public Map<String, Object> queryUsers(HttpServletRequest request){
//拼装请求参数
Map<String, Object> params = handleParamToMap(request);
System.out.println(params);
//设置返回信息
Map<String, Object> resultMap = new HashMap();
resultMap.put("rows",jdbcTemplate.queryForList("select * from user"));
return resultMap;
}
{page=1, rows=10}
所以,我们要做的就是根据page和rows,获取总条数和当前查询的数据。
总条数
总条数简单,一句sql解决了。
Integer total = jdbcTemplate.queryForObject("select count(1) from user", Integer.class);
MySQL分页
limit分页公式:curPage是当前第几页;pageSize是一页多少条记录。
代码就写成了这样:
@RequestMapping("queryUsers")
@ResponseBody
public Map<String, Object> queryUsers(HttpServletRequest request){
//拼装请求参数
Map<String, Object> params = handleParamToMap(request);
int page = Integer.parseInt(params.get("page").toString()) ;
int pageSize = Integer.parseInt(params.get("rows").toString()) ;
//设置返回信息
Map<String, Object> resultMap = new HashMap();
//拿到总条数
Integer total = jdbcTemplate.queryForObject("select count(1) from user", Integer.class);
List rows = jdbcTemplate.queryForList("select * from user limit ?,?",(page-1)*pageSize,pageSize);
resultMap.put("total",total);
resultMap.put("rows",rows);
return resultMap;
}
查询按钮控制列表刷新
之前的查询按钮方法为:
$('#search').click(function(){
$('#ff').form('submit', {
url:'loadUsers',
//提交前可以额外添加参数
onSubmit: function(param){
//这边只是模拟一下
param.search = true;
}
});
});
当时只是测试一下form的表单提交,实际上不应该这么做。
单独写一个查询方法:
function search(){
$('#dg').datagrid('load',{
userName:$('#ff').get(0).userName.value,
nickName:$('#ff').get(0).nickName.value,
isVip:$('#ff').get(0).isVip.value,
});
}
然后查询按钮就调用这个方法即可。
$('#search').click(function(){
search();
});
请求报文如下:
条件查询(重难点)
欢迎来到本项目最难的点:条件查询,先给出代码:
@RequestMapping("queryUsers")
@ResponseBody
public Map<String, Object> queryUsers(HttpServletRequest request){
//拼装请求参数
Map<String, Object> params = handleParamToMap(request);
int page = Integer.parseInt(params.get("page").toString()) ;
int pageSize = Integer.parseInt(params.get("rows").toString()) ;
//拼接where条件
StringBuffer sb = new StringBuffer(" where 1=1 ");
List<Object> injectors = new ArrayList<>();
if(!StrUtil.isEmptyIfStr(params.get("userName"))){
sb.append("and user_name like ?");
injectors.add("%"+params.get("userName")+"%");
}
if(!StrUtil.isEmptyIfStr(params.get("nickName"))){
sb.append("and nick_name like ?");
injectors.add("%"+params.get("nickName")+"%");
}
if(!StrUtil.isEmptyIfStr(params.get("isVip"))){
sb.append("and is_vip = ?");
injectors.add(params.get("isVip").toString());
}
//设置返回信息
Map<String, Object> resultMap = new HashMap();
//拿到总条数
Integer total = jdbcTemplate.queryForObject("select count(1) from user" + sb, injectors.toArray(), Integer.class);
injectors.add((page-1)*pageSize);
injectors.add(pageSize);
List rows = jdbcTemplate.queryForList("select * from user" + sb +" limit ?,?", injectors.toArray());
resultMap.put("total",total);
resultMap.put("rows",rows);
return resultMap;
}
思路是根据查询的参数是否为空,来拼接对应的where条件。这边用到了一个List,这是为了防止sql注入而采取的必要措施。
这个方法非常重要,请务必好好消化一下。
新增用户后立刻触发刷新
字典翻译
相信你也发现了,就是有些字段显示是数字,这一点其实是不科学的。比如,是否vip,显示一个0,用户怎么知道你这个0是什么意思?解决办法就是用字典翻译。
字典翻译有前台翻译,也有后台翻译,一般我们会单独维护一张字典表,然后做后台翻译的。相关的知识点我们在下一章做讲解。
字典(重要)
这是一款非常经典的教程,我直接挪用了企业里面真实项目的编程技巧。
VipMgrApplication是我们的启动类,加一个容器启动完成的listener
public class VipMgrApplication implements ApplicationListener<ApplicationReadyEvent> {
}
实现onApplicationEvent方法
/**
* 容器启动完成事件
* @param applicationReadyEvent
*/
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
ConfigurableApplicationContext applicationContext = applicationReadyEvent.getApplicationContext();
JdbcTemplate jdbcTemplate = (JdbcTemplate) applicationContext.getBean("jdbcTemplate");
//System.out.println(jdbcTemplate);
//加载所有启动状态的字典数据
List<Map<String, Object>> dicts = jdbcTemplate.queryForList("select * from t_dict where is_on = 1");
dictMap = new HashMap<>();
//为了转换效率,给字典列表加索引
for (int i = 0; i < dicts.size(); i++) {
dictMap.put(dicts.get(i).get("dict_code").toString() + dicts.get(i).get("dict_no").toString(),dicts.get(i));
}
System.out.println("数据字典加载完毕!" + dictMap);
}
dictMap是我设计的一个静态Map变量
public static Map<String, Map<String, Object>> dictMap = null;
这玩意会在SpringBoot容器启动完毕后就加载。
t_dict表
CREATE TABLE `t_dict` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`dict_code` varchar(20) DEFAULT NULL COMMENT '字典类型',
`dict_no` varchar(5) DEFAULT NULL,
`dict_value` varchar(30) DEFAULT NULL,
`is_on` varchar(1) DEFAULT '1' COMMENT '是否启用',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
模拟数据
INSERT INTO `vipmgr`.`t_dict` (`id`, `dict_code`, `dict_no`, `dict_value`, `is_on`) VALUES ('1', 'isVip', '0', '普通会员', '1');
INSERT INTO `vipmgr`.`t_dict` (`id`, `dict_code`, `dict_no`, `dict_value`, `is_on`) VALUES ('2', 'isVip', '1', 'VIP会员', '1');
INSERT INTO `vipmgr`.`t_dict` (`id`, `dict_code`, `dict_no`, `dict_value`, `is_on`) VALUES ('3', 'isDelete', '0', '未删除', '1');
INSERT INTO `vipmgr`.`t_dict` (`id`, `dict_code`, `dict_no`, `dict_value`, `is_on`) VALUES ('4', 'isDelete', '1', '已删除', '1');
INSERT INTO `vipmgr`.`t_dict` (`id`, `dict_code`, `dict_no`, `dict_value`, `is_on`) VALUES ('5', 'role', '1', '管理员', '1');
INSERT INTO `vipmgr`.`t_dict` (`id`, `dict_code`, `dict_no`, `dict_value`, `is_on`) VALUES ('6', 'role', '2', '注册会员', '1');
INSERT INTO `vipmgr`.`t_dict` (`id`, `dict_code`, `dict_no`, `dict_value`, `is_on`) VALUES ('7', 'role', '3', 'VIP会员', '1');
启动项目看字典数据
启动项目,看下控制台的打印:
数据字典加载完毕!{isDelete0={id=3, dict_code=isDelete, dict_no=0, dict_value=未删除, is_on=1}, role1={id=5, dict_code=role, dict_no=1, dict_value=管理员, is_on=1}, isVip1={id=2, dict_code=isVip, dict_no=1, dict_value=VIP会员, is_on=1}, role2={id=6, dict_code=role, dict_no=2, dict_value=注册会员, is_on=1}, isVip0={id=1, dict_code=isVip, dict_no=0, dict_value=普通会员, is_on=1}, isDelete1={id=4, dict_code=isDelete, dict_no=1, dict_value=已删除, is_on=1}, role3={id=7, dict_code=role, dict_no=3, dict_value=VIP会员, is_on=1}}
这样做的好处就是,在容器启动后就加载所有的数据字典,放到Map里面。到时候别的地方如果有字典转换的需求,就非常快速。
用户数据查出来后,用lamda表达式进行数据翻译:
//数据字典转换
rows.forEach((map) -> {
//是否VIP翻译
map.put("is_vip",VipMgrApplication.dictMap.get("isVip" + map.get("is_vip")).get("dict_value"));
//角色名称翻译
map.put("roleName",VipMgrApplication.dictMap.get("role" + map.get("role_id")).get("dict_value"));
//用户状态翻译
map.put("is_delete",VipMgrApplication.dictMap.get("isDelete" + map.get("is_delete")).get("dict_value"));
});
角色编号
角色ID可能还会用到,所以保留,我们增加了roleName返回,于是乎:
<div style="padding:5px;background:#fafafa;width:100%;border:1px solid #ccc;height:100%;">
<table id="dg" title="用户列表" class="easyui-datagrid" fitColumns="true"
pagination="true" rownumbers="true" url="user/queryUsers" >
<thead>
<tr>
<th field="cb" checkbox="true" align="center"></th>
<th field="user_name" width="150" align="center">用户名</th>
<th field="nick_name" width="100" align="center">昵称</th>
<th field="role_id" width="60" align="center" hidden>角色编号</th>
<th field="roleName" width="60" align="center">角色</th>
<th field="create_time" width="200" align="center">创建时间</th>
<th field="is_vip" width="200" align="center">是否VIP</th>
<th field="last_login_time" width="200" align="center">上次登录时间</th>
<th field="is_delete" width="200" align="center">用户状态</th>
</tr>
</thead>
</table>
</div>
用户名扩展到150,然后隐藏角色编号。
easyUI给我们提供了很多套皮肤,挑一个自己喜欢的吧,毕竟默认的皮肤太丑了。
皮肤库文件在这
换一个css就行
<link rel="stylesheet" type="text/css" href="themes/material/easyui.css">
我换成了这种风格,你也可以换成自己喜欢的。
我们希望不同的字典值有一个默认的颜色,这样显得层次感强一点。
添加color字段
/**
* 容器启动完成事件
* @param applicationReadyEvent
*/
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
ConfigurableApplicationContext applicationContext = applicationReadyEvent.getApplicationContext();
JdbcTemplate jdbcTemplate = (JdbcTemplate) applicationContext.getBean("jdbcTemplate");
//System.out.println(jdbcTemplate);
//加载所有启动状态的字典数据
List<Map<String, Object>> dicts = jdbcTemplate.queryForList("select * from t_dict where is_on = 1");
dictMap = new HashMap<>();
//为了转换效率,给字典列表加索引
for (int i = 0; i < dicts.size(); i++) {
//如果有颜色,则自动添加font标签
if(!StrUtil.isEmptyIfStr(dicts.get(i).get("color"))){
dicts.get(i).put("dict_value",String.format("<font color='%s'>%s</font>",dicts.get(i).get("color"),dicts.get(i).get("dict_value")));
}
dictMap.put(dicts.get(i).get("dict_code").toString() + dicts.get(i).get("dict_no").toString(),dicts.get(i));
}
System.out.println("数据字典加载完毕!" + dictMap);
}
如果color不为空,就重新渲染dict_value
我故意修改了一些数据,得到这样的效果
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)