写在前面:第一次发表发文章,若有不足欢迎指正! ! !
前端代码实现:
<script>newVue({ el: '#app', data() { return { defAct: '2', menuActived: '2', userInfo: {}, menuList: [ // {// id: '1',// name: '门店管理',// children: [ { id: '2', name: '员工管理', url: 'page/member/list.html', icon: 'icon-member' }, { id: '3', name: '分类管理', url: 'page/category/list.html', icon: 'icon-category' }, { id: '4', name: '菜品管理', url: 'page/food/list.html', icon: 'icon-food' }, { id: '5', name: '套餐管理', url: 'page/combo/list.html', icon: 'icon-combo' }, { id: '6', name: '订单明细', url: 'page/order/list.html', icon: 'icon-order' } // ],// }, ], iframeUrl: 'page/member/list.html', headTitle: '员工管理', goBackFlag: false, loading: true, timer: null } }, computed: {}, created() { constuserInfo=window.localStorage.getItem('userInfo') if (userInfo) { this.userInfo=JSON.parse(userInfo) } this.closeLoading() }, beforeDestroy() { this.timer=nullclearTimeout(this.timer) }, mounted() { window.menuHandle=this.menuHandle }, methods: { logout() { logoutApi().then((res)=>{ if(res.code===1){ localStorage.removeItem('userInfo') window.location.href='/backend/page/login/login.html' } }) }, goBack() { // window.location.href = 'javascript:history.go(-1)'constmenu=this.menuList.find(item=>item.id===this.menuActived) // this.goBackFlag = false// this.headTitle = menu.namethis.menuHandle(menu,false) }, menuHandle(item, goBackFlag) { this.loading=truethis.menuActived=item.idthis.iframeUrl=item.urlthis.headTitle=item.namethis.goBackFlag=goBackFlagthis.closeLoading() }, closeLoading(){ this.timer=nullthis.timer=setTimeout(()=>{ this.loading=false },1000) } } }) </script>
以上是进入主页面用vue写的script脚本,接下来是整个项目的排版:
<htmllang="en"><head><metacharset="UTF-8"/><metahttp-equiv="X-UA-Compatible"content="IE=edge"/><metaname="viewport"content="width=device-width, initial-scale=1.0"/><title>瑞吉外卖管理端</title><linkrel="shortcut icon"href="favicon.ico"><!-- 引入样式 --><linkrel="stylesheet"href="plugins/element-ui/index.css"/><linkrel="stylesheet"href="styles/common.css"/><linkrel="stylesheet"href="styles/index.css"/><linkrel="stylesheet"href="styles/icon/iconfont.css"/><style>.body{ min-width: 1366px; } .app-main{ height: calc(100% - 64px); } .app-main.divTmp{ width: 100%; height: 100%; } </style></head><body><divclass="app"id="app"><divclass="app-wrapper openSidebar clearfix"><!-- sidebar --><divclass="sidebar-container"><divclass="logo"><!-- <img src="images/logo.png" width="122.5" alt="" /> --><imgsrc="images/login/login-logo.png"alt=""style="width: 117px; height: 32px"/></div><el-scrollbarwrap-class="scrollbar-wrapper"><el-menu:default-active="defAct":unique-opened="false":collapse-transition="false"background-color="#343744"text-color="#bfcbd9"active-text-color="#f4f4f5"><divv-for="item in menuList":key="item.id"><el-submenu:index="item.id"v-if="item.children && item.children.length>0"><templateslot="title"><iclass="iconfont":class="item.icon"></i><span>{{item.name}}</span></template><el-menu-itemv-for="sub in item.children":index="sub.id":key="sub.id"@click="menuHandle(sub,false)"><i:class="iconfont":class="sub.icon"></i><spanslot="title">{{sub.name}}</span></el-menu-item></el-submenu><el-menu-itemv-else:index="item.id"@click="menuHandle(item,false)"><iclass="iconfont":class="item.icon"></i><spanslot="title">{{item.name}}</span></el-menu-item></div></el-menu></el-scrollbar></div><divclass="main-container"><!-- <navbar /> --><divclass="navbar"><divclass="head-lable"><spanv-if="goBackFlag"class="goBack"@click="goBack()"><imgsrc="images/icons/btn_back@2x.png"alt=""/> 返回</span><span>{{headTitle}}</span></div><divclass="right-menu"><divclass="avatar-wrapper">{{ userInfo.name }}</div><!-- <div class="logout" @click="logout">退出</div> --><imgsrc="images/icons/btn_close@2x.png"class="outLogin"alt="退出"@click="logout"/></div></div><divclass="app-main"v-loading="loading"><divclass="divTmp"v-show="loading"></div><iframeid="cIframe"class="c_iframe"name="cIframe":src="iframeUrl"width="100%"height="auto"frameborder="0"v-show="!loading"></iframe></div></div></div></div>
以上使用的是:vue加element ui实现菜单,图片如下:
同时进入系统后,axios会发送ajax分页请求,用来分页展示数据,提供好的体验,以下是其中一个controller的源代码
packagecom.zhi.reggie.controller; importcom.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; importcom.baomidou.mybatisplus.extension.plugins.pagination.Page; importcom.zhi.reggie.common.R; importcom.zhi.reggie.entity.Employee; importcom.zhi.reggie.service.EmployeeService; importlombok.extern.slf4j.Slf4j; importorg.apache.commons.lang.StringUtils; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.util.DigestUtils; importorg.springframework.web.bind.annotation.*; importjavax.servlet.http.HttpServletRequest; //日志"/employee") (publicclassEmployeeController { privateEmployeeServiceemployeeService; //员工登陆"/login") (publicR<Employee>login(HttpServletRequestrequest, Employeeemployee){ //注意这里传的request方便把登陆者的id存到session当中//后一个参数传入的是json格式//1.密码md5加密处理Stringpassword=employee.getPassword(); password=DigestUtils.md5DigestAsHex(password.getBytes()); //2.根据username查询数据库LambdaQueryWrapper<Employee>queryWrapper=newLambdaQueryWrapper<>(); queryWrapper.eq(Employee::getUsername,employee.getUsername()); Employeeemp=employeeService.getOne(queryWrapper); //3.如果没有查到就返回登陆失败结果if(emp==null) { returnR.error("登陆失败"); } //密码比对if(!emp.getPassword().equals(password)){ returnR.error("登陆失败"); } //查看员工状态if(emp.getStatus() ==0){ returnR.error("用户已经被禁用"); } //登陆成功,把员工id存入session并返回登陆成功结果request.getSession().setAttribute("employee",emp.getId()); returnR.success(emp); } //员工退出"/logout") (publicR<String>logout(HttpServletRequestrequest){ //清理session中保存的当前员工idrequest.getSession().removeAttribute("employee"); returnR.success(" 退出成功"); } publicR<String>save(HttpServletRequestrequest, Employeeemployee){ log.info("新增员工,员工信息:{}",employee.toString()); //设置初始密码123456,md5加密employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes())); //employee.setCreateTime(LocalDateTime.now());//employee.setUpdateTime(LocalDateTime.now());//获得当前登陆用户的id//Long empId = (Long)request.getSession().getAttribute("employee");//employee.setCreateUser(empId);//创建人信息//employee.setUpdateUser(empId);employeeService.save(employee); returnR.success("新增员工成功"); } //员工信息的分页"/page") (publicR<Page>page(intpage,intpageSize,Stringname){ log.info("page ={},pagesize = {}, name ={}",page,pageSize,name); //构造分页构造器PagepageInfo=newPage(page,pageSize); //构造条件构造器LambdaQueryWrapper<Employee>queryWrapper=newLambdaQueryWrapper(); //添加过滤条件queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name); //添加排序条件queryWrapper.orderByDesc(Employee::getUpdateTime); employeeService.page(pageInfo,queryWrapper); returnR.success(pageInfo); } //根据id修改员工信息publicR<String>update(HttpServletRequestrequest,Employeeemployee){ LongempId= (Long) request.getSession().getAttribute("employee"); //employee.setUpdateTime(LocalDateTime.now());//employee.setUpdateUser(empId);employeeService.updateById(employee); returnR.success("员工信息修改成功"); } //根据id查询员工信息"/{id}") (publicR<Employee>getById(longid){ log.info("根据id查询员工信息"); Employeeemployee=employeeService.getById(id); if(employee!=null){ returnR.success(employee); } returnR.error("没有查询到员工信息"); } }
本项目使用的R类是方便前后端沟通,后端只需要返回json数据或者R.success("成功内容"),或者R.error("错误内容")。便可轻易实现沟通。R类的配置如下
packagecom.zhi.reggie.common; importlombok.Data; importjava.io.Serializable; importjava.util.HashMap; importjava.util.Map; //通用返回结果,服务器端响应得数据最终都会封装成此类publicclassR<T>implementsSerializable { privateIntegercode; //编码:1成功,0和其它数字为失败privateStringmsg; //错误信息privateTdata; //数据privateMapmap=newHashMap(); //动态数据publicstatic<T>R<T>success(Tobject) { R<T>r=newR<T>(); r.data=object; r.code=1; returnr; } publicstatic<T>R<T>error(Stringmsg) { Rr=newR(); r.msg=msg; r.code=0; returnr; } publicR<T>add(Stringkey, Objectvalue) { this.map.put(key, value); returnthis; } }
同时,以下展示本项目使用的主从数据库代码,以及真实效果:
spring shardingsphere enabled true # 是否启用sharding,不启用时使用datasource配置的数据源 datasource names master,slave0 # 节点名称,多个时使用逗号隔开 master type com.alibaba.druid.pool.DruidDataSource driver-class-name com.mysql.cj.jdbc.Driver url jdbc mysql //47.113.191.1 3306/项目数据库?characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai username root password123456# 以下为druid配置,可以共用datasource中的druid配置,需要覆盖时再重新配置#注意此时的环境是在linux,主机在服务器,从机是windows initial-size2 max-active45 min-idle6 slave0 type com.alibaba.druid.pool.DruidDataSource driver-class-name com.mysql.cj.jdbc.Driver url jdbc mysql //172.18.65.63 3306/项目数据库?characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai username root password123456# 以下为druid配置,可以共用datasource中的druid配置,需要覆盖时再重新配置 initial-size3 max-active20 min-idle8 masterslave name ms master-data-source-name master slave-data-source-names slave0 load-balance-algorithm-type round_robin props sql showtrue
可以看出主从数据库都配置成功,以下进行新增和查询操作。
主库写,可以清晰看到处理该请求的是master数据库,以上报错可以不用理,不影响程序的进行。
从句执行select sql语句,可以看到查询的是slave数据库,实现了主从复制,读写分离。
未登陆的时候会自动拦截,以下是拦截器的实现:
packagecom.zhi.reggie.filter; importcom.alibaba.fastjson.JSON; importcom.zhi.reggie.common.BaseContext; importcom.zhi.reggie.common.R; importlombok.extern.slf4j.Slf4j; importorg.springframework.stereotype.Component; importorg.springframework.util.AntPathMatcher; importjavax.servlet.*; importjavax.servlet.annotation.WebFilter; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; importjava.io.IOException; filterName="LoginCheckFilter",urlPatterns="/*") (publicclassLoginCheckFilterimplementsFilter { publicstaticfinalAntPathMatcherPATH_MATCHER=newAntPathMatcher(); publicvoiddoFilter(ServletRequestservletRequest, ServletResponseservletResponse, FilterChainfilterChain) throwsIOException, ServletException { HttpServletRequestrequest= (HttpServletRequest)servletRequest; HttpServletResponseresponse= (HttpServletResponse) servletResponse; // 1.获取本次请求的urlStringrequestURI=request.getRequestURI(); log.info("拦截到请求: {}",requestURI); String[] urls=newString[]{ "/employee/login", "/employee/logout",//这些是放行的url"/backend/**", "/front/**", "/common/**", "/user/sendMsg",//移动端发送短信"/user/login", // 移动端登陆"/doc.html", "/webjars/**", "/swagger-resources", "/v2/api-docs" }; //2.判断本次请求是否需要处理booleancheck=check(urls,requestURI); if(check){ filterChain.doFilter(request,response); log.info("本次请求{}不需要处理",requestURI); return;//不需要处理,直接放行 } //3-1.判断登陆状态,如果已经登陆,直接放行if(request.getSession().getAttribute("employee") !=null){ log.info("用户已登陆,用户id为:{}",request.getSession().getAttribute("employee")); LongempId= (Long) request.getSession().getAttribute("employee"); BaseContext.setCurrentId(empId); filterChain.doFilter(request,response); return; } //3-2(手机端).判断登陆状态,如果已经登陆,直接放行if(request.getSession().getAttribute("user") !=null){ log.info("用户已登陆,用户id为:{}",request.getSession().getAttribute("user")); LonguserId= (Long) request.getSession().getAttribute("user"); BaseContext.setCurrentId(userId); filterChain.doFilter(request,response); return; } //4.如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN"))); log.info("用户未登陆"); } publicbooleancheck(String[] urls,StringrequestURI){ //检查本次请求是否放行for (Stringurl : urls) { booleanmatch=PATH_MATCHER.match(url,requestURI); if(match){ returntrue; } } returnfalse; } //新增员工}
可以看出,其实没有使用高级的一些验证,用的只是简单的一些逻辑判断。没有用到springsecurity进行权限认证,这个到后续还是需要改进的,同时redis集群没有使用,这个项目还是蛮不完善的,后续会增加功能。
不过也算自己第一个项目了。
本项目还实现了手机端的访问,需要用户接受验证码登陆。
具体实现如下
packagecom.zhi.reggie.controller; importcom.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; importcom.zhi.reggie.common.R; importcom.zhi.reggie.entity.User; importcom.zhi.reggie.service.UserService; importcom.zhi.reggie.utils.SMSUtils; importcom.zhi.reggie.utils.ValidateCodeUtils; importlombok.extern.slf4j.Slf4j; importorg.apache.commons.lang.StringUtils; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.data.redis.core.RedisTemplate; importorg.springframework.web.bind.annotation.PostMapping; importorg.springframework.web.bind.annotation.RequestBody; importorg.springframework.web.bind.annotation.RequestMapping; importorg.springframework.web.bind.annotation.RestController; importjavax.servlet.http.HttpSession; importjava.util.Map; importjava.util.concurrent.TimeUnit; "/user") (publicclassUserController { privateRedisTemplateredisTemplate; privateUserServiceuserService; /*** 发送手机短信验证码* @param user* @return*/"/sendMsg") (publicR<String>sendMsg(Useruser, HttpSessionsession){ //获取手机号Stringphone=user.getPhone(); if(StringUtils.isNotEmpty(phone)){ //生成随机的4位验证码Stringcode=ValidateCodeUtils.generateValidateCode(4).toString(); log.info("code={}",code); //调用阿里云提供的短信服务API完成发送短信//SMSUtils.sendMessage("瑞吉外卖","",phone,code);//需要将生成的验证码保存到Session//session.setAttribute(phone,code);//将生成的验证码缓存到redis中,并且设置有效期为5分钟redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES); returnR.success("手机验证码短信发送成功"); } returnR.error("短信发送失败"); } /*** 移动端用户登录* @param map* @param session* @return*/"/login") (publicR<User>login(Mapmap, HttpSessionsession){ log.info(map.toString()); //获取手机号Stringphone=map.get("phone").toString(); //获取验证码Stringcode=map.get("code").toString(); //从Session中获取保存的验证码//Object codeInSession = session.getAttribute(phone);//从reids中获取缓存的验证码ObjectcodeInSession=redisTemplate.opsForValue().get(phone); //进行验证码的比对(页面提交的验证码和Session中保存的验证码比对)if(codeInSession!=null&&codeInSession.equals(code)){ //如果能够比对成功,说明登录成功LambdaQueryWrapper<User>queryWrapper=newLambdaQueryWrapper<>(); queryWrapper.eq(User::getPhone,phone); Useruser=userService.getOne(queryWrapper); if(user==null){ //判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册user=newUser(); user.setPhone(phone); user.setStatus(1); userService.save(user); } session.setAttribute("user",user.getId()); //如果用户登陆成功,删除redis中缓存的验证码redisTemplate.delete(phone); returnR.success(user); } returnR.error("登录失败"); } }
登陆界面:
用户将登陆手机端。验证码存储在redis中,登陆成功后删除手机验证码缓存。
目前就到这里了,谢谢你的阅读!!!!