该项目经验是我在Java学习过程中,根据黑马程序员推出的课程 瑞吉外卖 项目学习整理的,其中涵盖了具体的功能实现。个人觉得非常适合新手来学习,可以很好的整合前面学习的Spring、MyBatisPlus等相关知识,同时,该项目也是我在秋招中常用来介绍的一个项目,根据秋招面试官对项目的提问方式,进行了细致的划分,可以很好的应对秋招面试中项目上的提问,希望可以给大家带来帮助,也希望友友么可以点赞收藏+转发,谢谢。有需要 pdf 版本的可以私聊我 !!!
课程名称: 黑马程序员Java项目实战《瑞吉外卖》,轻松掌握springboot + mybatis plus开发核心技术的真java实战项目。
本外卖项目是专门为餐饮企业设计开发的一款软件产品,包括 系统管理后台 和 移动端应用 两部分。
其中系统管理后台主要提供给餐饮企业内部员工使用,管理员可以对 员工管理 在内的所有功能进行管理,还可以同普通员工一样对餐厅的 菜品、套餐、订单等 进行管理维护。
移动端应用主要提供给消费者使用,可以 在线浏览菜品、添加删除购物车、下单等 。目前移动端暂时通过h5实现,用户可以通过手机浏览器访问。
今后改进:
①之后将针对移动端应用进行改进,使用微信小程序实现,用户使用将更加方便;
②其次是将系统进行优化升级,提高系统的访问性能。
简单介绍一下第一个功能吧,员工登录、新增员工、退出、员工名单分页查询(其它模块类似,都是对数据库的CRUD操作)。{create增 retrieve查 update改 delete删}
员工登录: 前端页面获取到用户的username和password后,向请求地址/employee/login发送 Post请求 ,并提交这两个参数,后台关于Employee的Controller类捕获并处理,首先进行密码的 md5加密 (DigestUtils.md5DigestAsHex()),然后使用查询条件构造器 LambdaQueryWrapper 将两个参数对比数据库,比如是否为null值,是否密码不一致,是否员工状态为禁用等,查询失败则返回失败消息,查询成功则将员工id存入Session,便于后期获取当前登录用户的id。最终给前端页面返回成功消息。
新增员工: 前端页面向地址/employee/save发送 Post请求,将提交过来的Employee对象通过employeeService save到数据库中,这个过程中要将密码做md5加密处理。
退出: 前端页面向地址/employee/logout发送 Post请求,Controller清理Session中保存的当前员工的id,返回退出成功的消息,前端页面接收后跳转至登录页面。
员工信息分页查询: 前端页面向地址/employee/page发送 Get请求,并携带page、pageSize(如果是点击了搜索按钮还会携带name)参数,然后利用page和pageSize构造分页构造器Page和查询条件构造器LambdaQueryWrapper对员工表进行遍历查询(此处改动了一下,判断一下登录的id是否为管理员,是管理员则可以查看所有员工信息,而如果是普通员工则只允许查看本人信息)。添加排序条件后将查询结果返回前端页面并进行展示呈现。
过滤器功能: 为前端页面设置过滤器,在登陆前拦截未经登录就访问的页面。
新增员工时,需要设置创建时间、创建人、修改时间、修改人等字段,使用 Mybatis Plus提供的公共字段填充功能 。
流程:
① 在实体类Employee的属性上加上@TableField注解,指定自动填充的策略;
(fill = FieldFill.INSERT) 或 (fill = FieldFill.INSERT_UPDATE) 插入、更新时填充字段
② 自定义元数据对象处理器,实现MetaObjectHandler接口;
创建基于 ThreadLocal 的封装工具类BaseContext,用户保存和获取当前登录用户id。BaseContext.setCurrentId(empId);
在添加菜品或者套餐之前,要先设计一个菜品分类、套餐分类的页面。便于后期添加菜品或者套餐时,进行分类,在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐。
category菜品套餐分类表: id、type、name、sort、create_time … (type = 1为菜品分类、2为套餐分类)
流程:
①前端页面 发送ajax请求 ,将新增分类窗口输入的数据以json形式提交到服务端;
②服务端Controller 接收页面提交的数据 ,调用Service将数据进行保存 ;
③Service 调用Mapper操作数据库 ,完成数据的保存。
流程:
① 前端页面 发送ajax请求 ,将 分页查询参数(page,pageSize) 提交到服务端;
② 服务端Controller 接收页面提交的数据 ,调用Service查询数据;
③ Service 调用Mapper操作数据库 ,查询数据;
④ Controller将 查询到的分页数据响应给页面 ;
⑤ 页面接收到分页数据展示到页面上。
操作 category菜品套餐分类表 ,需要判断! 当分类关联了 菜品dish或者套餐setmeal 时,此分类不允许删除。
流程:
①前端页面 发送ajax请求 ,将 参数id提交到服务端 ;
②服务端Controller 接收页面提交的数据 ,分别使用 条件查询构造器LambdaQueryWrapper去菜品dish表、套餐setmeal表中查找 对应的category_id;
③如果当前分类下 关联了菜品或套餐,则不能删除 ,报 自定义异常 (R.error(exception msg));
④如果未关联则直接删除category表中此id对应的分类。 (removeById(id))
上传流程:
①采用 post方式 提交数据;
②使用Spring框架中提供的 MultipartFile类型的file参数 ;
③在配置文件application.yml中指定转存的path,声明一个basePath变量并加上@Value(“${reggie.path}”)注解;
④使用UUID重新随机生成一个文件名,防止重名;
⑤file.transferTo(new File(basePath + fileName))进行转存。
下载流程:
本质上就是服务器将文件以 流 的形式写回浏览器的过程;
① 通过输入流FileInputStream读取文件内容;
② 通过输出流ServletOutputStream将文件写回浏览器,在浏览器展示图片;
将新增页面录入的菜品数据插入到 dish菜品表 中,如果添加了口味信息,还要向 dish_flavor菜品口味表 插入数据。
流程:
① 页面发送ajax请求,请求服务端获取菜品分类数据(type=1)并展示到下拉框list↓中(/category/list?type=1);
② 页面发送请求进行图片上传,请求服务端将图片保存在服务器;
使用Spring框架中提供的MultipartFile类型的file参数,调用transferTo()方法进行转存;
③ 页面发送请求进行图片下载,将上传的图片进行回显;
通过输入流FileInputStream读取文件内容;通过输出流ServletOutputStream将文件写回浏览器;
④ 新增菜品信息输入完成后,点击保存按钮,发送ajax请求,将新增菜品数据以json形式提交到数据库。
参数封装: 由于新增菜品时还添加了口味信息,所以不能只返回一个存入dish菜品表的dish对象,前端发送来的数据中还包含了口味信息,所以 导入DishDto ,用于 封装页面提交的数据。(dto中不仅包含dish对象还包含dish_flavor对象)
⑤ 由于要同时操作两张表,不能直接调用DishService的save()方法,需要在DishService中重新定义一个saveWithFlavor(),使用 实现类DishServiceImpl实现saveWithFlavor()↓ ,来新增菜品,同时保存对应的口味数据。注意要加入 事务控制 。
流程:
① 前端页面发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务器端,获取分页数据;
在DishController中编写page()方法↓(GetMapping);
此处 页面展示 中菜品分类这个字段存在问题,由于Dish表中只存在categoryId,而前端页面需要的字段为categoryName,如果直接返回dish对象查询到的分页信息的话会导致页面中菜品分类这一项不显示。这个categoryName字段在 DishDto 中存在,所以需要用DishDto对象来返回查询出的分页数据。
将查询到的dish对象使用Spring框架中的BeanUtils进行 对象拷贝 到dishDto中,然后在数据库中查询对应的categoryId,根据categoryId调用categoryService的getById查询出category对象,进而获取category的name,将name赋给dishDto中的categoryName属性,就得到了菜品分类的名字,返回dishDto给前端页面进行展示即可。
② 页面发送请求,请求服务端进行图片下载,用于页面图片展示。
通过输出流ServletOutputStream的方式将图片写回浏览器
流程:
① 前端页面发送ajax请求,请求服务端获取菜品分类数据,用于菜品分类下拉框(新增菜品list)中数据展示;
② 前端页面发送ajax请求,请求服务端根据id查询当前菜品信息,用于菜品信息回显;
由于菜品信息还包含菜品的口味信息,也就是说需要 同时查询dish表和dish_flavor表 ,dishService中自定义一个**getByIdWithFlavor(id)**,在实现类中将根据id查询到的dish数据拷贝到dishDto对象中,然后再dish_flavor表中查询对应的口味信息,封装到dishDto对象中后,返回给前端页面。
③ 前端页面发送ajax请求,请求服务端进行图片下载,透过输出流的方式将图片写回浏览器,实现图片回显;
④ 在页面点击保存按钮后,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端。
由于菜品信息还包含菜品的口味信息,也就是说需要 同时查询dish表和dish_flavor表 ,dishService中自定义一个updateWithFlavor()方法,在实现类中先调用updateById(dishDto)更新dish表的基本信息,然后清除掉dish_flavor表中对应的口味信息,然后以stream流的方式向dish_flavor表中添加当前提交过来的口味数据,dishFlavorService.saveBatch(flavors)。
套餐就是菜品的集合。
后台系统中可以管理套餐信息,通过新增套餐功能来添加一个新的套餐,在添加套餐时需要选择当前套餐所属的套餐分类和包含的菜品,并且需要上传套餐对应的图片,在移动端会按照套餐分类来展示对应的套餐。
两个表:
流程:
① 页面发送ajax请求,请求服务端,(查category表)获取套餐分类数据(type=2)并展示到套餐分类下拉框中;
② 页面发送ajax请求,请求服务端,(查category表)获取菜品分类数据(type=1)并展示到添加 套餐菜品 窗口大类中;
③ 页面发送ajax请求,请求客户端,(查dish表)根据菜品分类查询分别对应的菜品数据(dish)并展示到添加套餐菜品窗口中;
DishController中定义R
↓方法(GetMapping)> list(Dish dish)
④ 页面发送ajax请求,进行图片上传,请求服务端将图片保存到服务器;
使用Spring框架中提供的MultipartFile类型的file参数,调用transferTo()方法进行转存;
⑤ 页面发送ajax请求,进行图片下载,将上传的图片进行回显;
通过输入流FileInputStream读取文件内容;通过输出流ServletOutputStream将文件写回浏览器;
⑥ 点击保存按钮,页面发送ajax请求,将套餐相关数据以json形式提交到服务端。
SetmealController中定义save(setmealDto)方法(PostMapping),新增套餐,同时需要保存 套餐表setmeal 和 套餐菜品的关联关系表setmeal_dish ,调用 实现类 SetmealServiceImpl 中实现 saveWithDish()↓ 方法。
其中,套餐表setmeal直接执行save()方法即可;
套餐菜品的关联关系表setmeal_dish中以stream流的方式赋上setmeal_id字段后即可执行insert操作新增数据了。
流程:
① 页面发送ajax请求,将分页查询参数(page,pageSize,name)提交到服务端,获取分页数据;
在SetmealController中编写 page(page,pageSize)↓ 方法;
此处页面呈现中套餐分类这个字段存在问题,由于Setmeal表中只存在categoryId,而前端页面需要的字段为categoryName,如果直接返回setmeal对象查询到的分页信息的话会导致页面中套餐分类这一项不显示。这个categoryName字段在 SetmealDto 中存在,所以需要用SetmealDto对象来返回查询出的分页数据。
将查询到的setmeal对象使用Spring框架中的BeanUtils进行对象拷贝到setmealDto中,然后在数据库中查询对应的categoryId,根据categoryId调用categoryService的getById查询出category对象,进而获取category的name,将name赋给setmealDto中的categoryName属性,就得到了套餐分类的名字,返回setmealDto给前端页面进行展示即可。
② 页面发送ajax请求,请求服务端进行图片下载,用于页面展示。
通过输入流FileInputStream读取文件内容;通过输出流ServletOutputStream将文件写回浏览器;
流程:
① 在 实现类SetmealServiceImpl中实现一个removeWithDish(List ids)↓ ,定义条件构造器来查询该套餐是否为正在销售状态,如果是则不能删除,抛出一个自定义业务异常↓,向页面发送提示消息。如果不在销售状态则可以删除,先执行removeByIds(ids)删除套餐表setmeal中的数据,然后删除套餐菜品关系表setmeal_dish中的数据。
前端页面发送ajax请求,将分页查询参数(page、pageSize)提交到服务器端,服务器 查询orders表 中所有的数据,
获取分页数据;
项目本来使用的是接收手机验证码验证登录,由于资费问题,把这里 改成了接收邮箱验证码进行登录 。
流程:
① 使用 两个工具类 :一个名为 MailUtils的发邮件工具类 ,其中包含一个 sendMail方法 ,参数为邮件接收方地址to,邮件内容text,邮件标题title;另外还有一个用于 随机生成验证码的ValidateCodeUtils工具类 。
在 UserController中定义sendMsg方法 ,页面接收用户输入的邮箱地址,点击“获取验证码”按钮后,会向目的邮箱发送四位数的验证码,同时服务端后台将用户邮箱和生成的验证码保存到Session中;
用户登录时,将用户邮箱和密码与后台Session中的数据(Map集合)进行比对,并且根据邮箱查询用户是否为新用户,如果为新用户则自动完成注册,将用户信息保存到user表中。
用户登录后可以修改、删除自己的地址信息(address_book表),同一个用户可以有多个地址信息,但是只能有一个 默认地址 。
获取当前登录用户的id后,对页面传过来的addreBook对象直接使用service中的save()方法即可。
① 利用条件构造器,将用户地址表address_book的所有地址的is_default字段设置为0;
②设置当前选定的地址的is_default字段为1,执行updateById(addressBook)即可。
去 address_book用户地址表 中使用getById(id)
根据当前登录用户的id去 address_book用户地址表 中查询,并且添加is_default=1的条件约束。
根据当前登录用户的id去 address_book用户地址表 中查询。
流程:
① 前端页面发送ajax请求,获取分类数据(category表中菜品分类(type=1)和套餐分类(type=2)),左侧呈现;
② 页面发送ajax请求,获取第一个分类(如湘菜)下的菜品或者套餐,右侧呈现。
这里改造了之前在新增套餐时使用的DishController中的 R
方法↓,因为在移动端呈现的时候要展示菜品的口味信息,原本的list返回的是Dish对象,不包含flavor数据,此时将对flavor的查询结果与Dish对象封装到 DishDto对象 中进行返回。从而可以在移动端呈现菜品的同时展示菜品的口味信息。> list(Dish dish)
③ 根据条件查询套餐数据在右侧显示,在 SetmealController中定义list(Setmeal setmeal)↓ 方法,前端页面返回的是包含categoryId和status字段的key-value对。
流程:
① 添加购物车add():在菜品或者套餐上点击“加入购物车”或者“+”按钮,页面发送ajax请求,请求客户端将菜品DishId或者套餐SetmealId添加到购物车;
设置当前用户的id—BaseContext.getCurrentId();
查询当前菜品或套餐是否已经在购物车中:
如果已经存在则number+1;如果不存在则number设置为1;最后更新 shopping_cart表 。
② 查看购物车:点击购物车图标,页面发送ajax请求,请求客户端查询购物车中的菜品和套餐;
根据当前登录用户的id使用 list()↓ 查询shopping_cart表。
③ 点击清空购物车按钮,页面发送ajax请求,请求服务端来执行清空购物车操作。
根据当前登录用户的id使用 remove()↓ 直接删除相关数据即可。
订单表: orders
订单明细表: order_detail
流程:
① 在购物车中点击“去结算”按钮,页面跳转到订单确认页面;
② 在订单确认页面,发送请求,请求服务端获取当前登录用户的默认地址;请求服务端获取当前登录用户的购物车数据;
③ 在订单确认页面点击“去支付”按钮,发送ajax请求,请求服务端完成下单操作 submit(Orders orders)↓
。
实现类OrderServiceImpl中实现submit()方法,获得当前用户id,根据当前用户的 id查询购物车数据;
由于生成order订单需要用户的个人信息+地址信息,所以需要提前查询出用户的 基本信息和地址信息;
利用stream()流将购物车数据shoppingCart填充到 order_detail表 所有属性;
填充 orders表 所有属性;
向订单表插入一条orders数据 — save(orders)
向订单明细表插入多条orderDetails数据 — saveBatch(orderDetails)
最后清空购物车
① 前端页面发送ajax请求,将分页查询参数(page、pageSize)提交到服务器端,获取分页数据;
在 OrderController 中编写 page()方法↓ (GetMapping);
答:加乐观锁(MP中自带,可以在修改的同时加上版本号version),redis实现乐观锁。
gitee网址: https://gitee.com/enterprises/?utm_source=pzpc
Git是一个 分布式版本控制工具 ,主要用于管理开发过程中的源代码文件(Java类、xml文件、html页面等),在软件开发过程中被广泛使用。
本地仓库: 开发人员自己电脑上的Git仓库。
远程仓库: 远程服务器上的Git仓库。
commit: 提交,将本地文件和版本信息保存到本地仓库。
push: 推送,将本地仓库文件和版本信息上传到远程仓库。
pull: 拉取,将远程仓库文件和版本信息下载到本地仓库。
Git存在两个仓库,一个本地仓库,一个远程仓库,所以说是分布式的。
下载地址:https://git-scm.com/download
安装完成后,任意目录点击鼠标右键,显示Git GUI Here(打开Git图形界面) 和 Git Bash Here(打开Git命令行),则表示安装成功。
Git中存在两种类型的仓库,即本地仓库和远程仓库。如何搭建Git远程仓库?
使用网上常见的GitHub(https://github.com/)、码云(https://gitee.com/)、GitLab(https://about.gitlab.com/)等来实现。
步骤:
①注册码云账号(gitee用户名:177xxxxxxxx;密码:xxxxxxxx)
②登录码云
③创建远程仓库
④邀请其他用户称为仓库成员
注意: 每个Git远程仓库对应一个网络地址,可以直接复制。
注意: 如果合并分支过程中出现错误 “fatal:cannot do a partial commit during a merge ”
,我们可以在提交命令后追加 “-i” 参数,即可解决。
在IDEA中使用Git获取仓库有两种方式:
①本地初始化仓库
②从远程仓库克隆
注意: 项目文件夹下的 .gitignore 文件作用:为了告诉 Git 项目中哪些内容是不需要Git管理的。
步骤:
①将文件加入暂存区
②将暂存区的文件提交到版本库
③查看日志
文件加入暂存区:2种方式
①当我们新创建一个文件时,创建结束就会弹出是否将这个文件加入暂存区,直接点击 Add 即可添加成功;
②选中文件,右键,弹出的菜单中 Git --> Add ,即可把文件加入暂存区。
注意: 在IDEA开发工具中,我们可以根据文件的颜色来区分文件是否已经纳入Git管理,黑色的是已经纳入的,绿色的是已经提交到暂存区的(相当于执行前面的 “git add 文件名” 操作),红色的是未提交到暂存区的。
暂存区的文件提交到版本库
①单个文件提交,右键 Git --> Commit File 即可提交;
②提交所有文件,右键 Git --> Commit File,勾选所有待提交的文件,即可提交;
③点击Git工具栏中的 “√” 即可提交。
查看日志
点击Git工具栏中的 “时钟符号 show history”,即可查看文件的日志。
1、步骤:
①查看远程仓库
②添加远程仓库
③推送至远程仓库
④从远程仓库拉取
2、查看远程仓库(git remote)
点击当前项目,右键 Git --> Repository --> Remotes ,即可看到当前项目关联的远程仓库。
3、添加远程仓库(git remote add)
点击当前项目,右键 Git --> Repository --> Remotes ,弹出的对话框中选择 “+”,即可添加远程仓库。即可看到当前项目关联的远程仓库。
4、推送至远程仓库(git push)
点击当前项目,右键 Git --> Repository --> Push 。
5、从远程仓库拉取(git pull)
点击当前项目,右键 Git --> Repository --> Pull 。或者点击Git工具栏的 “”
1、查看分支
①点击当前项目,右键 Git --> Repository --> Branches ,即可看到分支。
②点击右下角 master,也可以查看分支。
2、创建分支
①点击右下角 master --> new branch,创建一个新的分支。
3、切换分支
①点击右下角 master --> master --> Checkout,即可切换分支。
4、将分支推送到远程仓库
①点击右下角 master --> 右键某个分支 --> Push,即可将当前分支推送到远程仓库。
5、合并分支
①点击右下角 master --> 右键某个待合并分支 --> Merge into Current,即可将选中的分支合并到master分支。
主流操作系统有:桌面操作系统(Windows/OS/Linux)、服务器操作系统(Unix/Linux)、移动设备操作系统(Android/iOS)、嵌入式操作系统(Linux)
Linux系统版本: 内核版(Linux团队开发维护、免费开源)和发行版(Linux厂商开发维护,有收费和免费两个版本,如ubuntu、debain、centOS等)
物理机安装:直接将操作系统安装到服务器硬件上
虚拟机安装:通过虚拟机软件安装
什么是虚拟机? 虚拟机是指通过软件模拟的具有完整硬件系统功能、运行在完全隔离环境中的完整计算机系统。
常用的虚拟机软件有VMWare等。
①安装VMWare虚拟机;②安装CentOS镜像。
未设置网卡,在启动服务器时导致IP地址初始化失败,修改网络初始化配置,设定网卡在系统启动时初始化,网卡设置步骤:
SSH(Secure Shell),建立在应用层基础上的安全协议。常用的SSH连接工具有:mobaxterm、finalshell、xshell等,通过SSH连接工具可以实现从本地连接到远程的Linux服务器。
①二进制发布包安装: 软件已经针对具体平台编译打包发布,只需要解压,修改配置即可。
②rpm安装: 软件已经按照redhat的包管理规范进行打包,使用rpm命令进行安装,不能自行解决库依赖问题。
③yum安装: 一种在线软件安装方式,本质上是rpm安装,自动下载安装包并安装,安装过程中自动安装所依赖的库。
④源码编译安装: 软件以源码工程的形式发布,需要自己编译打包。
补充:Linux防火墙相关操作
注意事项: 如果当前系统中已经安装了MySQL数据库,安装将失败。CentOS7自带了mariadb,与MySQL数据库冲突。
注意: 服务器上的MySQL数据库的用户名和密码均为:root
Linux下防火墙相关操作可参见安装Tomcat部分。
直接运行SpringBoot程序会导致屏幕无法再进行其他操作,所以要将SpringBoot程序改为在后台运行。
Redis是一个 基于内存 的 key-value结构 的数据库。Redis的特点:基于内存存储,读写性能高;适合存储热点数据(如热点商品、资讯、新闻等);企业应用广泛。
Redis是一个开源的内存中的数据结构存储系统,它可以用作:数据库、缓存、消息中间件。
Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,存储的value类型丰富,又称结构化的NoSQL数据库。
官网:https://redis.io
补充:关系型数据库和非关系型数据库
关系型数据库:MySQL、Oracle、DB2、SQLServer
非关系型数据库:Redis、Mongo db、MemCached
Redis应用场景
缓存、任务队列、消息队列、分布式锁。
Redis安装包分为Windows版和Linux版:
Windows版下载地址:https://github.com/microsoftarchive/redis/release
Linux版下载地址:https://download.redis.io/releases/
之所有出现闪退,实际上是由于 redis-server 看似没打开,实际上还在后台运行,我们需要先将后台的 redis 服务停止,然后再双击 redis-server.exe ,即可启动 redis 服务器。
Redis存储的是key-value结构的数据,其中 key是字符串类型,value有5种数据类型:字符串、哈希、列表、集合、有序集合。
具体参见链接 Redis中文网
Redis的Java客户端有:Jedis、Lettuce、Redisson。
Spring对Redis客户端进行了整合,提供了Spring Data Redis,在Spring Boot项目中还提供了对应的Starter,即 spring-boot-starter-data-redis
。
使用Jedis操作Redis的步骤:
①获取连接
②执行操作
③关闭连接
在Spring Boot项目中,还可以使用Spring Data Redis来简化Redis操作,maven坐标:
Spring Data Redis中提供了一个封装的类:RedisTemplate,针对jedis客户端中大量api进行了归类封装,将同一类型的操作封装成operation接口,具体分类如下:
①ValueOperations:简单k-v操作
②SetOperations:set类型数据操作
③ZSetOperations:zset类型数据操作
④HashOperations:针对map类型的数据操作
⑤ListOperations:针对list类型的数据操作
使用Spring Data Redis提供的模板类RedisTemplate来注入数据,会出现我们无法通过key获取对应的value值,原因在于RedisTemplate提供的key序列化器 JdkSerializationRedisSerializer 在存储key时会在前面加上一串无关字符,导致我们根据存入的key取值时无法获取到。解决办法:自己实现一个序列化器,代码如下:
当用户数量增多的时候,请求也会随之增多,每个请求都和数据库进行交互,会造成数据库访问量增大,效率降低,用户体验变差(原本很快显示出界面,现在需要等待一会才能显示)
使用缓存。当请求过来的时候,会先查看缓存中是否存在需要的数据,如果存在,直接返回;反之再去查询数据库,并将查询的结果保存到缓存中,以便下一次使用。
①pom.xml文件中导入Spring Data Redis的maven坐标:
②在application.yml中加入redis相关配置:
③定义一个RedisConfig配置类:主要用来解决RedisTempalte自带的key序列化器序列化后找不到存入的key值问题。
之前我们已经实现了移动端手机验证码登录,但随机生成的验证码我们是 保存在HttpSession中的。现在需要改造为将验证码 缓存在Redis中,具体的实现思路如下:
1、在服务端UserController中注入RedisTemplate对象,用于操作Redis;
2、在服务端UserController的sendMsg方法中,将随机生成的验证码缓存到Redis中,并设置有效期为5分钟;
3、在服务端UserController的login方法中,从Redis中获取缓存的验证码,如果 登录成功则删除Redis中的验证码。
前面我们已经实现了移动端菜品查看功能,对应的服务端方法为DishController的list方法,此方法会根据前端提交的查询条件进行数据库查询操作。在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长。现在需要对此方法进行缓存优化,提高系统的性能。
具体的实现思路如下:
1、改造DishController的list方法,先从Redis中获取菜品数据,如果有则直接返回,无需查询数据库;如果没有则查询数据库,并将查询到的菜品数据放入Redis缓存中;
2、改造DishController的save和update方法,加入清理缓存的逻辑。
注意: 在使用缓存过程中,要注意保证数据库中的数据和缓存中的数据一致,如果 数据库中的数据发生变化,需要及时清理缓存数据。
Spring Cache是一个框架,实现了 基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。
Spring Cache提供了一层抽象,底层可以切换不同的cache实现。具体就是通过 CacheManager接口 来统一不同的缓存技术。
CacheManager是Spring提供的各种缓存技术抽象接口。
针对不同的缓存技术需要实现不同的CacheManager:
在spring boot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存支持即可。
例如,使用Redis作为缓存技术,只需要导入Spring data Redis的maven坐标即可。
前面我们已经实现了移动端套餐查看功能,对应的服务端方法为SetmealController的list方法,此方法会根据前端提交的查询条件进行数据库查询操作。在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长。现在需要对此方法进行缓存优化,提高系统的性能。
1、具体的实现思路如下:
1. 导入Spring Cache和Redis相关maven坐标;
2. 在application.yml中配置缓存数据的过期时间;
3. 在启动类上加入@EnableCaching注解,开启缓存注解功能;
4. 在SetmealController的list方法上加入@Cacheable注解;
5. 在SetmealController的save和delete方法上加入CacheEvict注解。
Request processing failed; nested exception is org.springframework.data.redis.serializer.SerializationException: Cannot serialize;
实体类未实现序列化接口,导致错误。
解决方法: 实体类实现序列化接口Serializable。
只用一台服务器,读和写的压力都由一台数据库承担,数据库压力大;如果数据库服务器 磁盘损坏则数据全部丢失。
MySQL主从复制是一个 异步的复制过程,底层是基于Mysql数据库自带的 二进制日志功能。就是一台或多台NySQL数据库(slave,即从库)从另一台MySQL数据库(master,即主库)进行日志的复制然后再解析日志并应用到自身,最终实现从库的数据和主库的数据保持一致。MySQL主从复制是MysQL数据库自带功能,无需借助第三方工具。
MySQL主从复制过程分成三步:
①master将改变记录到二进制日志(binary log);
②slave将master的binary log拷贝到它的中继日志(relay log);
③slave重做中继日志中的事件,将改变应用到自己的数据库中。
MySQL主从库配置
注意:
主库的主机地址为:172.168.75.100,从库的主机地址为:127.0.0.1。
由于我们使用的从库是本地计算机上的MySQL,因此配置从库的第一步、第二步操作不需要操作,直接操作第三步、第四步即可,具体的操作指令如下:
① change master to master_host='192.168.75.100',master_user='xiaoming',master_password='Root@123456', master_log_file='mysql-bin.000001',master_log_pos=441;
// 从库连接主库,标红的需要根据自己情况修改
② start slave;
// 开启从库
③ show slave status;
// 查看从库的状态
面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。
Sharding-JDBC定位为 轻量级Java框架,在Java的JDBC层提供额外的服务。它 使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。使用Sharding-JDBC可以在程序中轻松的 实现数据库读写分离 。
使用Sharding-JDBC实现读写分离步骤:
①导入maven坐标
②在配置文件中配置读写分离规则
③在配置文件中配置允许bean定义覆盖配置项
第一步:pom.xml中导入坐标
第二步:application.yml中配置读写分离规则
第三步:配置文件中配置允许bean定义覆盖配置项
出错的原因: 在导入的 ShardingJDBC框架下 的SpringBootConfiguration中,会 创建一个数据源对象;同时在 DruidDataSourceAutoConfigure下也会创建一个数据源对象 ,导致冲突。(多个数据源对象导致报错)
Sharding-JDBC框架实现读写分离十分简便,不用书写Java代码,只需要在pom.xml中导入Sharding-JDBC框架的坐标,然后在yml配置文件中配置主库和从库信息,以及读写分离规则即可;配置完成后,框架会自动的在JDBC层 通过创建不同的数据源来操作对应的主库和从库,从而实现读写分离。
直接使用我们前面在虚拟机中搭建的主从复制的数据库环境即可。
在主库中创建瑞吉外卖项目的业务数据库reggie并导入相关表结构和数据。
报错com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure,显示远程服务器连接失败,试了好久没解决,最后在数据源url地址后面加上:&serverTimezone=Asia/Shanghai&useSSL=false
后,成功解决。
Nginx是一款 轻量级的web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx的网站有:百度、京东、新浪、网易、腾讯、淘宝等。
官网: https://nginx.org/
可以到Nginx官方网站下载Nginx的安装包,地址为: https://nginx.org/en/download.html
注意: 安装tree命令(使用yum install tree)后,可以在Linux某个目录下使用 tree命令,展示该目录下的文件结构。
注意: 没有配置Nginx环境变量情况下,上面的命名只能在 sbin 目录下使用,或者使用绝对路径去使用。
前面我们要想执行 nginx 相关的命令,都得进入 sbin 目录下才可以正确执行,现在我们想要在任意目录下使用 nginx 命令,怎么操作??? 将 nginx 加入系统的环境变量中即可。
操作步骤:
① vim /etc/profile
② 在profile文件中追加 nginx 的路径:/opt/data/software/nginx/sbin: ,然后保存退出。
③ source /etc/profile
④ nginx -s reload
Nginx可以作为 静态web服务器来部署静态资源。
静态资源 指在服务端真实存在并且能够直接展示的一些文件,比如
常见的html页面、css文件、js文件、图片、视频等资源。
相对于Tomcat,Nginx处理静态资源的能力更加高效,所以在生产环境下,一般都会将静态资源部署到Nginx中。将静态资源部署到Nginx非常简单,只需要 将文件复制到Nginx安装目录下的html目录中即可。
正向代理
正向代理{正向代理一般是在 客户端,用来访问Internet (如我们常说的翻墙软件,就是使用了正向代理服务器)}是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。
正向代理的 典型用途 是为 在防火墙内的局域网客户端提供访问Internet的途径 。
正向代理一般是在客户端设置代理服务器,通过代理服务器转发请求,最终访问到目标服务器。
反向代理
反向代理服务器位于用户与目标服务器之间,但是 对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源,反向代理服务器负责将请求转发给目标服务器。
用户不需要知道目标服务器的地址,也无须在用户端作任何设定。
配置反向代理
早期的网站流量和业务功能都比较简单,单台服务器就可以满足基本需求,但是随着互联网的发展,业务流量越来越大并且业务逻辑也越来越复杂,单台服务器的性能及单点故障问题就凸显出来了,因此需要多台服务器组成应用集群,进行性能的水平扩展以及避免单点故障出现。
应用集群: 将同一应用部署到 多台机器上,组成应用集群,接收负载均衡器分发的请求,进行业务处理并返回响应数据。
负载均衡器: 将用户请求根据对应的 负载均衡算法 分发到应用集群中的一台服务器进行处理。
配置负载均衡
负载均衡策略
开发人员同时负责前端和后端代码开发,分工不明确;
开发效率低;
前后端代码混合在一个工程中,不便于管理;
对开发人员要求高,人员招聘困难。
前后端分离开发,就是在项目开发过程中,对于前端代码的开发由专门的前端开发人员负责,后端代码则由后端开发人员负责,这样可以做到分工明确、各司其职,提高开发效率,前后端代码并行开发,可以加快项目开发进度。
目前,前后端分离开发方式已经被越来越多的公司所采用,成为当前项目开发的主流开发方式。
前后端分离开发后,从工程结构上也会发生变化,即前后端代码不再混合在同一个maven工程中!!!而是分为前端工程和后端工程。
开发工具:Visual Studio、Codehbuilder
技术框架:nodejs、VUE、ElementUl、mock、webpack
YApi是高效、易用、功能强大的api管理平台,旨在为开发、产品、测试人员提供更优雅的 接口管理服务 。可以帮助开发者轻松创建、发布、维护API,YApi还为用户提供了优秀的交互体验,开发人员只需利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理。
YApi让接口开发更简单高效,让接口的管理更具可读性、可维护性,让团队协作更合理。
源码地址: https://github.com/YMFE/yapi
要使用YApi,需要自己进行部署。
Swagger是后端用来生成接口的工具。使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,再通过Swagger衍生出来的一系列项目和工具,就可以做到生成各种格式的接口文档,以及在线接口调试页面等等。
官网:https://swagger.io/
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。
操作步骤:
①导入knife4j的maven坐标
②导入knife4j相关配置类
③设置静态资源,否则接口文档页面无法访问
④在LogincheckFilter中设置不需要处理的请求路径
注意:菜品的图片也要上传到服务器上,否则前端界面无法加载。