SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理

前情提要 上一篇搭建了基础的架子,这一篇搭建页面架子,主要偏前端一点, 手摸手上一篇地址,点击跳转
这篇不做账号登陆的验证,留到下一篇shiro的时候一起做了,写到最后几章更像是在写前后端分离,
页面的跳转没有走controller,因为thymeleaf模版的原因,要不然每个页面都要写一个跳转的,
后面大部分都在前端解决跳转问题
第二章需要搭建的页面如下:

SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片

正文开始 新建以下文件和文件夹 SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片

修改application.yml文件 设置映射路径为:templates文件夹下

thymeleaf: cache: false encoding: utf-8 mode: HTML5 prefix: classpath:/templates/ suffix: .html # 保证html能直接访问,不需要跳转controller,后期动态管理页面地址 resources: static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/templates/

详细图片
SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片

layui-admin.css
.layui-admin-bg{background-color: #20222A !important ; } .layui-admin-border {box-shadow: 0 1px 2px 0 rgb(0 0 0 / 5%); } .panel {padding: 15px; } .panel-height {height: 600px; } .card-header {height: 60px; } .title {text-align: center; } .layui-layer-title {background-color: #001529 !important; color:#fff !important; } .layui-layer-close1 {color:#fff !important; } .layui-upload-img {width: 92px; height: 92px; } .avatar-uploader .el-upload {border: 1px dashed #d9d9d9; border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; width: 98px; height: 98px; } .avatar-uploader .el-upload:hover {border-color: #409EFF; } .avatar-uploader-icon {font-size: 28px; color: #8c939d; width: 178px; height: 178px; line-height: 178px; text-align: center; } .avatar {width: 98px; height: 98px; display: block; }.el-upload-list--text {display: none; }.el-dialog {margin-top: 20px !important; } .el-dialog > .el-dialog__header {background-color: #001529 !important; } .el-dialog__title {color: #fff !important; } .el-dialog__body {padding-left: 0px !important; }

login.css代码
.login-form {width: 25%; margin: auto; margin-top: 120px; } .login-title {text-align: center; font-size: 30px; } .input-div,.input-div2{display:flex; border: 1px solid #eee ; background-color: #fff; height: 38px; line-height: 38px; padding: 0px 10px; } .layui-input {border-style: none; }input::-webkit-input-placeholder {color: #ccc; } input:-moz-placeholder {color: #ccc; } input::-moz-placeholder {color: #ccc; } input:-ms-input-placeholder {color: #ccc; } .layui-form {margin-top: 50px; } body {background-color: #EFEFEF; } .layui-icon {color: #D2D2D2; }

login.html
登陆 - 锐客网 src="https://www.layuicdn.com/layui/layui.js"> src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"> src="http://code.jquery.com/jquery-2.1.1.min.js"> src="https://unpkg.com/element-ui/lib/index.js"> src="https://unpkg.com/axios@0.21.1/dist/axios.min.js">
> var vm = new Vue({el:"#app", data:{user:{username:null, password:null } }, methods:{//登陆按钮设置了@click=“login”,点击就会调用此方法 login(){let that = this; if(vm.user.username == null || vm.user.username == ""){this.$message.error("请填写用户名"); return; } if(vm.user.password == null || vm.user.password == ""){this.$message.error("请填写密码"); return; } //post请求发送账号密码 axios({url:"user/login", method: "post", headers:{"Content-Type": "application/json" }, data:JSON.stringify(vm.user) }).then(res => {console.log("返回结果",res.data); }) }, // 聚焦和失焦事件 focusCss(name){$("."+name).css("border"," 1px solid #D2D2D2 "); }, blurCss(name){$("."+name).css("border"," 1px solid #eee "); } } })

请求方式采用了axios,如果后面要接触了前后端分离项目,可能就会了解到axios
controller文件夹中编写登陆代码和页面跳转 新建一个LoginController类,跳转到login.html是依靠yml中设置thymeleaf配置
跳转到templates/login.html页面的
import com.github.pagehelper.util.StringUtil; import com.macro.entity.UserEntity; import com.macro.service.UserService; import com.macro.utils.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class LoginController {@Autowired private UserService userService; @GetMapping("login") public String login(){//依靠thymeleaf的映射,跳转到templates/login.html return "login"; }@PostMapping("user/login") @ResponseBody public Result login(@RequestBody UserEntity user){//判断传过来是否为空 if(user == null || StringUtil.isEmpty(user.getUsername()) || StringUtil.isEmpty(user.getPassword())){return Result.error("账号或者密码不能为空"); } //通过用户名查询 UserEntity entity =userService.findByUserName(user.getUsername()); //为空则回到 if(entity == null){return Result.error("账号不存在!"); }if(!entity.getPassword().equals(user.getPassword())){return Result.error("密码错误!"); } return Result.success(); } }

UserService接口添加findByUserName方法
//findByUserName方法,通过用户名近些年刚擦黑讯 UserEntity findByUserName(String username);

UserServiceImpl 实现findByUserName方法
@Autowired private UserDao userDao; @Override public UserEntity findByUserName(String username) {QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("username",username); return userDao.selectOne(wrapper); }

启动访问:http://127.0.0.1:8086/login
我数据库设置的账号为:macro 密码为:123456
SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片

登陆成功code:200 失败为code:400,工具类Result中默认设置的成功:200 失败为: 400
SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片

ifame的 index.html 登陆算是做好了,开始弄index.html
默认展示main.html,data中设置了./main.html,代码里面一些 注解记得看一下,不能面面俱到,只能通过注解
描述一些关键的东西
index.html
后台管理页面 - 锐客网 src="https://unpkg.com/layui@2.6.8/dist/layui.js"> src="http://code.jquery.com/jquery-2.1.1.min.js"> src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js">
> var vm =new Vue({el: '#rapp', data: {main:"./main.html", val:"" }, methods:{logout(){//退出接口:访问的是 127.0.0.1/logout window.location = "/logout" } }, update:function(){console.log("###"); } }); //iframe自适应 $(window).on('resize', function() {var $content = $('.content'); $content.height($(this).height() - 154); $content.find('iframe').each(function() {$(this).height($content.height()); }); }).resize(); //这块是页面跳转的关键,动态目录时会换掉 $(document).ready(function(){$("dd>a").click(function (e) {e.preventDefault(); vm.val = $(this).text(); vm.main = $(this).attr("href") }); });

新建main.html
Title - 锐客网 你好,我是无术同学!

LoginController类添加logout接口,可跳转到login.html
@GetMapping("logout") public String logout(){//退出跳到login.html页面 return "login"; }

修改login.html that.$messageelement ui的弹框,用来提示的
//请求user/login的方法改成 axios({url:"user/login", method: "post", headers:{ "Content-Type": "application/json"}, data:JSON.stringify(vm.user) }).then(res => {//此处为新加代码 if(res.data.code == 200){that.$message({ message:"登陆成功", type: 'success'}); window.location = "/" }else {that.$message.error(res.data.msg); }})

访问地址:http://127.0.0.1:8086/login
SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片

以上index.html的配置便好了,可以加入一些模块了,比如管理员模块
管理员模块 新建sysUser.html 【SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理】templates文件夹下新建sysUser.html
src="http://code.jquery.com/jquery-2.1.1.min.js"> src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"> src="https://unpkg.com/axios@0.21.1/dist/axios.min.js"> src="https://unpkg.com/element-ui/lib/index.js"> src="https://www.it610.com/article/common.js"> > tbody > tr {height: 50px; }
SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片
src="https://www.layuicdn.com/layui/layui.js"> src="https://www.it610.com/article/js/sysUser.js">

新建common.jsstatic文件夹下新建common.js
function ids(){var arr =new Array(); var res = layui.table.checkStatus('table'); if(res.data!= null && res.data.length > 0){for (let i = 0; i < res.data.length; i++) {arr[i] = res.data[i].id; } } return arr; } function alert(msg){layer.msg(msg); }

新建sysUser.jssysUser.html下方有个引入sysUser.js的配置,src设置的为js/sysUser.js
js前面没有添加/ ,这是一个相对路径 ,
src="https://www.it610.com/article/js/sysUser.js">

static下新建一个js文件夹,然后再js文件夹中新建sysUser.js
mounted在初始化的时候会加载一次,类似$(function(){ })
templet的写法是可以在Layui中看到答案的,d相当于当前循环中的一条数据
var vm = new Vue({el:"#app", //初始化加载 mounted(){layui.use('table', function(){var table = layui.table; table.render({elem: '#table' ,cellMinWidth: 80 ,url:'sysUser/list' ,cols: [[ { type:'checkbox'} ,{ field:'id',title: 'id' } ,{ field:'niceName',title: '昵称' } ,{ field:'username',title: '用户名'} ,{ field:'sex',title: '性别', templet: ' { {d.sex == null?"未知":d.sex==0?"男":"女"}} ' } ,{ field: 'avatar', title:'头像', templet: ' SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片
' } ]] ,page: true }); }); }, data:{user:{niceName:null, username:null, password:null, sex:"", avatar:"" }, show:false, title:"新增用户", param:{niceName:"", username:"" } },methods:{//查询+重新加载数据 reload(){layui.use('table', function () {var table = layui.table; table.reload('table', {url: 'sysUser/list' ,where: vm.param }); }); vm.show = false; }, add(){vm.show = true; //初始化 vm.user = {niceName:null, username:null, password:null, sex:"0", avatar:null }; vm.title= "新增用户"; }, update(){let data = https://www.it610.com/article/ids(); if(data == null || data.length == 0 || data.length> 1){alert("请选择一条数据!") return; } vm.show = true; vm.user = { }; vm.info(data[0]); vm.title= "修改用户"; }, del(){let that = this; let data = https://www.it610.com/article/ids(); if(data == null || data.length == 0){alert("请选择!") return; } layer.open({title: '删除' ,content: '是否删除数据', btn:['确定','取消'], yes: function(index, layero){axios({url:"sysUser/del", method: "post", headers:{"Content-Type": "application/json" }, data:JSON.stringify(data) }).then(res =>{if(res.data.code == 200){that.$message({ message:"删除成功", type: 'success'}); vm.reload(); }else {that.$message.error("删除失败"); } }); layer.close(index) } }); }, //保存或者更新 saveOrUpdate(){let state = vm.user.id == null|| vm.user.id == ""; let url = state ?"sysUser/add":"sysUser/update" axios({url:url, method: "post", headers:{"Content-Type": "application/json" }, data:JSON.stringify(vm.user) }).then(res =>{if(res.data.code == 200){this.$message({ message: state?"添加成功":"修改成功", type: 'success'}); vm.reload(); }else{this.$message.error(state?'新增失败':"修改失败"); } }); }, cannel(){vm.show = false; }, //查询单条 info(id){axios({method:"get", url: "sysUser/info/" + id }).then(res =>{if(res.data.code == 200){vm.user = res.data.data; vm.user.sex = vm.user.sex.toString(); }}) }, success(res, file){vm.user.avatar = res.data; }}})

一些重要的点 我默认在add或者update方法中都会去初始化数据为空,这样重复操作数据就不会造成数据异常
JSON.stringify 这个是将对象转化成json数据,好方便后端接收
addupdate 中有一个ids()方法,这个是common.js中的方法,用户获取table表单id值,后面每个增删改查的页面都有有这个的影子,所有把这个东西抽取出来了,其实head头部的那些引用也可以
抽取出来,更加的灵活一些
新建 PageEntitymacro包下新建一个Vo
创建PageEntity
import lombok.Data; //后面带查询的都会用到分页数据 @Data public class PageEntity {//页数 private Integer page; //每页数量 private Integer limit; }

Vo下新建UserParam类 将参数封装起来对以后的开发有好处,只需要改动UserParam类,然后去xml添加对应的sql即可,
无需改动servicedao代码了,一个开发小技巧送给你
//继承PageEntity @Data public class UserParam extends PageEntity {//昵称 private String niceName; //账号 private String username; }

新增sysUserController 除了findByParam方法,其余的新增修改删除都是mybatis plus提供的
import com.macro.Vo.UserParam; import com.macro.entity.UserEntity; import com.macro.service.UserService; import com.macro.utils.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.Arrays; @RestController @RequestMapping("sysUser") public class sysUserController {@Autowired private UserService userService; @GetMapping("list") public Result list(UserParam param){Result result = userService.findByParam(param); return result; }@PostMapping("add") public Result add(@RequestBody UserEntity user){//密码加密 boolean save = userService.save(user); if(save){return Result.success(); } return Result.error("添加失败"); }@GetMapping("info/{id}") public Result info(@PathVariable("id") Integer id){UserEntity userEntity = userService.getById(id); return Result.success(userEntity); }@PostMapping("update") public Result update(@RequestBody UserEntity user){boolean type = userService.updateById(user); return type ? Result.success() : Result.error("更新失败"); }@PostMapping("del") public Result del(@RequestBody String[] ids){if(ids.length > 0){boolean type = userService.removeByIds(Arrays.asList(ids)); return type ? Result.success() : Result.error("删除失败"); } returnResult.success(); }}

UserService新增 findByParam
Result findByParam(UserParam param);

UserServiceImpl新增 findByParam方法的实现 PageHelper是分页插件工具,舒舒服服写代码!Result.success报错是因为还没改,
后面写了改动
@Override public Result findByParam(UserParam param) {PageHelper.startPage(param.getPage(), param.getLimit()); List list = userDao.findByParam(param); PageInfo pageInfo = new PageInfo<>(list); return Result.success(0,pageInfo.getTotal(),list); }

UserDao新增findByParam
//传入param实体类参数 List findByParam(@Param("param")UserParam param);

UserDao.xml 代码 like模糊查询,防注入
id="findByParam" resultType="com.macro.entity.UserEntity"> select * from sys_user where 1=1 andnice_name likeCONCAT('%',#{param.niceName,jdbcType=VARCHAR},'%') andusername likeCONCAT('%',#{param.username,jdbcType=VARCHAR},'%')

Result类改动
import lombok.Data; @Data public class Result {private Integer code; private String msg; private Object data; private Long count; Result(Integer code,String msg,Object data,Long count){this.code = code; this.msg = msg; this.data = https://www.it610.com/article/data; this.count = count; }public static Result success(Object data){return new Result(200,"成功",data,null); } public static Result success(){return new Result(200,"成功",null,null); } public static Result success(Integer code,Long count,Object data){return new Result(code,"成功",data,count); }public static Result error(Integer code,String msg){return new Result(code,msg,null,null); }public static Result error(String msg){return new Result(400,msg,null,null); } }

启动之后点击管理员管理,出现的页面就是这样了,除了图片上传基本就没问题了
SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片

图片上传 在sysUser.html 头像上传采用了elemen uiel-upload上传控件,action设置了图片上传的请求地址
SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片

sysUser.js 这一块有一个图片处理,有兴趣可以打印看一下返回了什么
SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片

新建类 添加这三个类
SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片

FileUploadUtil
import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; public class FileUploadUtil {public static String BasePath = ""; private static String LINUXPATH = "/classmate/upload/"; private static String MACPATH = "/Users/classmate/upload/"; private static String WINDOWSPATH = "C:/classmate/upload/"; //根目录 private static String ROOT = "upload/"; static {initBasePath(); }/** * 系统初始化上传根目录 */ public static void initBasePath() {BasePath = getBasePath(); File BasePathFile = new File(BasePath); if (!BasePathFile.exists()) {BasePathFile.mkdirs(); } }/** *获取当前环境的路径 */ public static String getBasePath() {Properties prop = System.getProperties(); String os = prop.getProperty("os.name"); if (os != null && os.toLowerCase().indexOf("linux") > -1) {return LINUXPATH; } else if (os != null && os.toLowerCase().indexOf("mac") > -1) {return MACPATH; } else {return WINDOWSPATH; } }/** * * @param file 流 * @param folderName 文件夹名称, 如: C:/classmate/upload/ (folderName文件名) * @return */ public static String uploadFile(MultipartFile file, String folderName){FileOutputStream fot = null; //获取上传到的路径地址 String path = getBasePath(); path = path + folderName; //文件名称 String filePath = getFilePath(file,path); File dest = new File(path + "/" + filePath); try {dest.createNewFile(); //创建一个文件 fot = new FileOutputStream(dest); byte[] bytes = file.getBytes(); fot.write(bytes); //根目录: upload/ + 自定义目录: user/+ 文件名: xxxx.jpg return ROOT + folderName + "/" +filePath; } catch (IOException e) {e.printStackTrace(); return null; }finally {if(fot != null){try {fot.close(); } catch (IOException e) {e.printStackTrace(); } } } }/** * @param path 文件夹名字 * @param filefile文件 * @return */ private static String getFilePath(MultipartFile file, String path) {//生成文件名称(防止重复) long date = System.currentTimeMillis(); //检测是否创建 if (!new File(path).isDirectory()) {new File(path).mkdirs(); } //截取文件后缀 如:xxx.jpg xxx.png String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().indexOf(".")); //拼接新的文件名称 String name = date + suffix; //生成路径加上名字 returnname; }}

新建 WebConfig
import com.macro.utils.FileUploadUtil; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer {@Override public void addResourceHandlers(ResourceHandlerRegistry registry) {//设置访问路径为:127.0.0.1:8080/upload/** //映射路径:FileUploadUtil.getBasePath() 获取的路径 registry.addResourceHandler("/upload/**").addResourceLocations("file:///" + FileUploadUtil.getBasePath()); } }

新建UploadController
import com.macro.utils.FileUploadUtil; import com.macro.utils.Result; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; /** * 文件上传controller */ @RestController @RequestMapping("upload") public class UploadController {//图片上传 @PostMapping("img") public Result upload(MultipartFile file){//user:文件加名称,图片或者文件会放在user文件夹 String imgPath = FileUploadUtil.uploadFile(file, "user"); returnResult.success(imgPath); } }

文件上传 文件上传只是保存了部分路径,如:upload/user/xxxx.png
无视项目名称的更改和端口的更改,直接可以访问
运行看一下效果,完全没问题
SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片

总结 逻辑比较简单,就是代码量比较多,下面这张图大概的意思就是,请求/login通过controller跳转到login,
只不过通过thymeleaf的帮助,去寻找templates文件夹下的login.html文件,原本相加info.html,
但是篇幅太长了,留到下一篇吧
SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片

源码 在公众号内发送后台即可获取源码和数据库
SpringBoot|【爆肝推荐】手摸手带你做后台管理项目(第二章)登陆和用户管理
文章图片

    推荐阅读