瑞吉外卖实现

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 本次项目 使用的技术栈: 前端vue + uielement,后端: springboot + mybatis-plus, 网关 nginx反向代理,实现前后端分离,前端在本机的8080端口部署,后端在阿里云服务器的docker部署,拓展技术:mysql主从复制,读写分离。使用了redis作为中间件缓存,面对高并发的环境下有一战之力。 同时手机端的开发也进行了,但功能没有全部完善。本项目涵盖了手机端和pc端两端。

写在前面:第一次发表发文章,若有不足欢迎指正! ! !

前端代码实现:

<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脚本,接下来是整个项目的排版:

<!DOCTYPE html><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实现菜单,图片如下:

174817f0830bed7bb859187f97b9080.png

同时进入系统后,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;
@Slf4j//日志@RestController@RequestMapping("/employee")
publicclassEmployeeController {
@AutowiredprivateEmployeeServiceemployeeService;
//员工登陆@PostMapping("/login")
publicR<Employee>login(HttpServletRequestrequest, @RequestBodyEmployeeemployee){
//注意这里传的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);
    }
//员工退出@PostMapping("/logout")
publicR<String>logout(HttpServletRequestrequest){
//清理session中保存的当前员工idrequest.getSession().removeAttribute("employee");
returnR.success(" 退出成功");
    }
@PostMappingpublicR<String>save(HttpServletRequestrequest, @RequestBodyEmployeeemployee){
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("新增员工成功");
    }
//员工信息的分页@GetMapping("/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修改员工信息@PutMappingpublicR<String>update(HttpServletRequestrequest,@RequestBodyEmployeeemployee){
LongempId= (Long) request.getSession().getAttribute("employee");
//employee.setUpdateTime(LocalDateTime.now());//employee.setUpdateUser(empId);employeeService.updateById(employee);
returnR.success("员工信息修改成功");
    }
//根据id查询员工信息@GetMapping("/{id}")
publicR<Employee>getById(@PathVariablelongid){
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;
//通用返回结果,服务器端响应得数据最终都会封装成此类@DatapublicclassR<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
        password: 123456# 以下为druid配置,可以共用datasource中的druid配置,需要覆盖时再重新配置#注意此时的环境是在linux,主机在服务器,从机是windows        initial-size: 2        max-active: 45        min-idle: 6      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
        password: 123456# 以下为druid配置,可以共用datasource中的druid配置,需要覆盖时再重新配置        initial-size: 3        max-active: 20        min-idle: 8    masterslave:      name: ms
      master-data-source-name: master
      slave-data-source-names: slave0
      load-balance-algorithm-type: round_robin
    props:      sql:        show: true

可以看出主从数据库都配置成功,以下进行新增和查询操作。

cafcf51c0fd76d0e1466ab78b50d850.png主库写,可以清晰看到处理该请求的是master数据库,以上报错可以不用理,不影响程序的进行。

109339c878d91956ed88f5bff07386f.png从句执行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;
@Slf4j@Component@WebFilter(filterName="LoginCheckFilter",urlPatterns="/*")
publicclassLoginCheckFilterimplementsFilter {
publicstaticfinalAntPathMatcherPATH_MATCHER=newAntPathMatcher();
@OverridepublicvoiddoFilter(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;
@RestController@RequestMapping("/user")
@Slf4jpublicclassUserController {
@AutowiredprivateRedisTemplateredisTemplate;
@AutowiredprivateUserServiceuserService;
/*** 发送手机短信验证码* @param user* @return*/@PostMapping("/sendMsg")
publicR<String>sendMsg(@RequestBodyUseruser, 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*/@PostMapping("/login")
publicR<User>login(@RequestBodyMapmap, 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("登录失败");
    }
}

登陆界面:

bd0a824c7056634e0d93a04c773b66a.png

用户将登陆手机端。验证码存储在redis中,登陆成功后删除手机验证码缓存。


目前就到这里了,谢谢你的阅读!!!!


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
6月前
|
前端开发 Java API
苍穹外卖开发心得(上)
苍穹外卖开发心得(上)
251 5
|
6月前
|
缓存 JavaScript Java
苍穹外卖开发心得(下)
苍穹外卖开发心得(下)
142 5
|
6月前
|
JSON 前端开发 Java
瑞吉外卖业务开发(2)
瑞吉外卖业务开发
52 3
|
6月前
|
运维 前端开发 测试技术
瑞吉外卖业务开发(1)
瑞吉外卖业务开发
79 3
|
6月前
|
JSON 前端开发 安全
瑞吉外卖业务开发(4)
瑞吉外卖业务开发
41 2
|
6月前
|
存储 JSON 前端开发
瑞吉外卖业务开发(3)
瑞吉外卖业务开发
47 1
|
6月前
|
存储 SQL 前端开发
瑞吉外卖精华部分总结(1)
瑞吉外卖精华部分总结(1)
174 0
|
7月前
|
NoSQL Java 关系型数据库
一个完整的外卖系统
一个完整的外卖系统
107 0
|
7月前
|
Java 人机交互
零食商城|基于springboot的零食商城(三)
零食商城|基于springboot的零食商城
100 0
|
7月前
|
前端开发 Java 关系型数据库
零食商城|基于springboot的零食商城(一)
零食商城|基于springboot的零食商城
120 0