文章目录
- 1.构建后台项目
- 2.前端项目搭建
- 3.代码调试
- 4.用户登录业务实现
- 5.左侧菜单获取
- 6.用户管理业务实现
- 7.商品管理业务实现
- - 1.商品分类业务实现
- - 2.商品列表业务实现
1.构建后台项目
- 1.创建项目
- 2.编辑pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jt</groupId>
<artifactId>jt</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/>
</parent>
<properties>
<!--指定JDK版本-->
<java.version>1.8</java.version>
<!--跳过测试类打包-->
<skipTests>true</skipTests>
</properties>
<!--按需导入
历史说明: 2010 原来SSM 需要手动的编辑大量的的配置文件
思想: SpringBoot使用体现了"开箱即用"的思想
-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<!--Springboot的启动器 在内部已经将整合的配置写好,实现拿来就用-->
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--支持热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!--引入插件lombok 自动的set/get/构造方法插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--springBoot数据库连接 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--spring整合mybatis-plus 删除mybatis的包 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
</dependencies>
<!--build标签
springboot项目在打包部署发布时,需要依赖maven工具API
如果不添加该插件,则直接影响项目发布
-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.3</version>
</plugin>
</plugins>
</build>
</project>
- 3.项目结构
说明: 从码云中下载jt的完整代码,之后粘贴到本地项目中,如图所示:
- 4.修改YML文件
server:
port: 8091
servlet:
context-path: /
#配置数据源
spring:
datasource:
#如果使用高版本驱动 则添加cj
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/jt?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
password: root
#mybatis-plush配置
mybatis-plus:
type-aliases-package: com.jt.pojo
mapper-locations: classpath:/mappers/*.xml
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.jt.mapper: debug
- 5.编辑启动项
1.点击修改
2.为启动项修改文件.方便以后使用
2.前端项目搭建
- 1.关于前端路径说明
为了让全国同学们了解前端的编码规则,所以全国最好统一项目路径.
要求: 将前端项目放到IDEA维护的工作目录中.
如图所示:
- 2.操作步骤
1.码云下载前端资料
2.项目解压之后复制到IDEA维护的目录中
注意事项: 不要出现目录的嵌套,要求目录的根 就是项目. 如图
3.导入项目
4.运行前端项目
3.命令启动
3.代码调试
- 1.前端代码调试
目的: 经常性的出现本来编辑的是李逵的代码,但是由于代码不熟练,编辑的李鬼. 所以有如下的测试.
编辑App.vue文件
代码测试
4.用户登录业务实现
- 1.项目划分
前端项目网址: http://localhost:8080/
后端项目网址: http://localhost:8091/
用户操作项目请求的流程: 用户----前端服务器------后端服务器
- 2.用户模块分析
- - 1.表设计分析
注意事项:
1.密码 加密处理
2.created/updated 每张表中都有该字段
- - 2.pojo分析(User.java)
package com.jt.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
// Linux系统,严格区分大小写
// 注意:类中必须写表名,防止大小写问题导致异常
@TableName("user")
@Data
@Accessors(chain = true)
public class User extends BasePojo{
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private String phone;
private String email;
private Boolean status;
@TableField(exist = false) //该属性不存在
private Role role; //定义role角色数据
}
- 3.用户登录业务实现
- - 1.项目开发流程
人员配置:
1.项目经理(统筹规划协调资源,控制项目进度)
2.职能经理: 凭技术吃饭的 项目组长.
3.产品经理: 根据甲方要求,绘制项目原型. 出原型图
4.UI设计: 根据产品的图,用美学的眼光 绘制图片/按钮/logo
5.前端开发: 绘制页面(html/css/js)
6.后端开发: 根据业务接口文档.实现项目
7.测试工程师: 工具测试(黑白盒测试) 测试机型!!!
8.实施/运维工程师 事情杂/工资低
- - 2.接口文档说明
- 请求路径: /user/login
- 请求方式: POST
- 请求参数
参数名称 | 参数说明 | 备注 |
---|---|---|
username | 用户名 | 不能为空 |
password | 密码 | 不能为空 |
- 响应数据 SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 返回密钥token信息 |
返回值格式如下:
{"status":200,"msg":"服务器调用成功!","data":"1e893a97634847b3a8b499b173bea620"}
- - 3.在vo包下编辑SysResult对象(SysResult.java)
说明: 该对象主要负责前端项目与后端项目的数据交互. 几乎所有的后台服务器的返回值都是SysResult对象.
package com.jt.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class SysResult implements Serializable {
private Integer status; //200业务执行成功 201业务执行失败
private String msg; //服务器的提示信息
private Object data; //封装后台返回值
public static SysResult fail(){
return new SysResult(201,"业务执行失败",null);
}
public static SysResult success(){
return new SysResult(200,"业务执行成功",null);
}
//服务器返回业务数据
public static SysResult success(Object data){
return new SysResult(200,"业务执行成功",data);
}
public static SysResult success(String msg,Object data){
return new SysResult(200,msg,data);
}
}
- - 4.页面JS分析
1.路由跳转规则(index.js )
2. axios 全局定义(main.js)
3. axios的使用(Login.vue)
- 4.用户登陆操作
- - 1.关于token的说明
1.由于服务器需要标识已经登录的用户,所以服务器动态生成一个独一无二的token,返回给用户.
2.用户将token保存到本地,方便下次访问时携带.
3.token就是登录用户的一个凭证
- - 2.生成UUID
编辑controller层
package com.jt.controller;
import com.jt.pojo.User;
import com.jt.service.UserService;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/hello")
public List<User> hello(){
return userService.findAll();
}
/**
* 业务说明:实现用户登录
* 思想:根据参数,查询数据库
* 有数据:用户名和密码正确
* 没有数据:用户名和密码错误
*
* url:/user/login
* 参数:username/password
* 类型:post
* 返回值:SysResult对象(token)
*/
@PostMapping("/login")
public SysResult login(@RequestBody User user){
//需求:要求登陆成功之后,返回标识符信息
String token = userService.login(user);
//如果token为null,说明登录失败
if(token == null || token.length()==0){
return SysResult.fail();
}
//否则 正确返回
return SysResult.success(token);
}
}
编辑server层 UserServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.util.List;
import java.util.UUID;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public List<User> findAll() {
return userMapper.selectList(null);
}
/**
* 思路:
* 1.将密码进行加密处理
* 2.根据username/password查询数据库
* 3.有数据:
* 登录成功,返回密钥
* 没有数据:
* 登录失败,返回null
*/
@Override
public String login(User user) {
//1.获取密码明文
String password = user.getPassword();
//2.加密处理
String md5 = DigestUtils.md5DigestAsHex(password.getBytes());
user.setPassword(md5);
System.out.println(md5);
//3.查询数据库
//根据对象中不为空的属性当作where条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
//4.获取数据库对象
User userDB = userMapper.selectOne(queryWrapper);
//5.判断登录是否成功
if(userDB == null){
return null;
}
//6.使用UUID动态生成token,根据当前 毫秒数+随机数 利用hash算法生成
//几乎可以保证不重复
String token = UUID.randomUUID().toString().replace("-", "");
return token;
}
}
- - 3.Session与Cookie机制
为什么会出现?
如果在网页开发中,有个变量我们需要长时间使用,可能在下次登录到这个页面的时候还能使用,那么它们的价值就体现出来了,他们能够保存我们所需要的数据而不需要经常性调用数据库去获取。
- - - 1.Session机制介绍
Session:在计算机中,尤其是在网络应用中,称为“会话控制”。Session对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web页之间跳转时,存储在Session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web页时,如果该用户还没有会话,则Web服务器将自动创建一个 Session对象。当会话过期或被放弃后,服务器将终止该会话。Session 对象最常见的一个用法就是存储用户的首选项。例如,如果用户指明不喜欢查看图形,就可以将该信息存储在Session对象中。有关使用Session 对象的详细信息,请参阅“ASP应用程序”部分的“管理会话”。注意会话状态仅在支持cookie的浏览器中保留。
小结:
1.Session称之为 “会话机制”
2.在浏览器中打开网页 就是一个会话.
3.用户的数据可以保存到会话中,但是有生命周期. 当会话关闭,则数据消失.
- - - 2.Cookie机制
Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息 [1] 。
说明:
1.cookie是一个小型的文本文件
2.cookie中存储的数据一般都是密文.
3.cookie中的数据的生命周期可控. 几天.几年!!!
- - - 3.Session与Cookie的区别
1.session的数据是临时存储.cookie的数据可以永久保存. (生命周期不同)
2.sesion是浏览器中的一个内存对象!而cookie是一个实际的本地文件. (形式不同).
3.session一般存储是一些涉密数据.cookie一般存储相对公开的数据(免密登录). (安全性)
- - - 4.项目使用[前端保存用户信息(Login.vue)]
//登录按钮函数
login(){
//获取表单对象之后进行数据校验
//valid 表示校验的结果 true表示通过 false表示失败
this.$refs.loginFormRef.validate(async valid => {
//如果没有完成校验则直接返回
if(!valid) return
//如果校验成功,则发起ajax请求
const {data: result} = await this.$http.post('/user/login',this.loginForm)
if(result.status !== 200) return this.$message.error("用户登录失败")
this.$message.success("用户登录成功")
//获取用户token信息,将其保存到Session中
let token = result.data
//调用浏览器中Session机制,存储信息
window.sessionStorage.setItem("token",token)
//用户登录成功之后,跳转到home页面
this.$router.push("/home")
})
}
浏览器中的Session 控制:
当会话关闭时,Session数据将会被清空
- - - 5.实现系统首页跳转说明
1.实现页面跳转
2.编辑路由规则
编辑router/index.js 添加组件信息,实现首页跳转
3.实现效果
- 5.MD5介绍
MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。MD5由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代MD4算法。这套算法的程序在 RFC 1321 标准中被加以规范。1996年后该算法被证实存在弱点,可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。
理论: MD5不可以被破解的. 只能由明文加密为密文. 不可以反向编译
- - 1.编辑UserController
用户名: admin123
密码: admin123456
package com.jt.controller;
import com.jt.pojo.User;
import com.jt.service.UserService;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 业务说明: 实现用户登录
* 思想: 根据参数,查询数据库
* 有值: 用户名和密码正确
* 没有值: 用户名和密码错误
*
* URL:/user/login
* 参数: username/password json
* 类型: post
* 返回值: SysResult对象(token)
*/
@PostMapping("/login")
public SysResult login(@RequestBody User user){
//需求:要求登录成功之后,返回值标识符信息
String token = userService.login(user);
//如果token为null,说明登录失败
if(token == null || token.length()==0){
return SysResult.fail();
}
//否则 正确返回
return SysResult.success(token);
}
- - 2.编辑UserServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.util.List;
import java.util.UUID;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
/**
* 思路:
* 1.将密码进行加密处理
* 2.根据username/password查询数据库
* 3.有值:
* 登录成功,返回秘钥
* 没有值:
* 登录失败,返回null
* @param user
* @return
*/
@Override
public String login(User user) {
//1.获取明文
String password = user.getPassword();
//2.加密处理
String md5 = DigestUtils.md5DigestAsHex(password.getBytes());
user.setPassword(md5);
System.out.println(md5);
//3.查询数据库
QueryWrapper<User> queryWrapper= new QueryWrapper<>(user);
//4.获取数据库对象
User userDB = userMapper.selectOne(queryWrapper);
//5.判断登录是否正确
if(userDB == null){
return null;
}
String token = "我是秘钥";
return token;
}
}
- 6.同源策略
规定: 浏览器要求在解析Ajax请求时,要求浏览器的路径与Ajax的请求的路径必须满足三个要求.则满足同源策略.可以访问服务器.
三个要求:
请求协议://域名:端口号都必须相同!!!
要素:
1.浏览器的请求路径.
2.Ajax请求的网址
- - 1.同源策略案例
案例1:
1.浏览器地址 http://localhost:8090/findAll
2.Ajax请求地址 http://localhost:8090/aaaa
满足同源策略.服务器可以正常访问.
案例2:
1.浏览器地址 http://localhost:8091/findAll
2.Ajax请求地址 http://localhost:8090/aaaa
不满足同源策略. 端口号不同. 属于跨域请求.
案例3:
1.浏览器地址 http://localhost:8090/findAll
2.Ajax请求地址 https://localhost:8090/aaaa
不满足同源策略. 协议不同. 属于跨域请求.
案例4:
前提: IP与域名映射
1.浏览器地址 http://www.baidu.com/findAll
2.Ajax请求地址 http://10.0.1.1/aaaa
不满足同源策略. 域名不同.
案例5:
1.浏览器地址 http://10.0.1.1:80/findAll
2.Ajax请求地址 http://10.0.1.1/aaaa
满足同源策略. 默认端口号就是80
案例6:
1.浏览器地址 https://10.0.1.1:443/findAll
2.Ajax请求地址 https://10.0.1.1:443/aaaa
满足同源策略
- - 2.什么是跨域
违反了同源策略的请求,就是跨域的请求.
- - 3.跨域解决方法
1.JSONP(了解)
JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的核心用法: 利用返回值语法固定的: callback(JSON数据)
2.CORS方式
说明: CORS(Cross-origin resource sharing) “跨域资源共享”,现在的主流的浏览器都支持cors的方式. 如果需要跨域,则需要配置响应头信息.标识是否允许.
服务器端标识:
检查响应头信息:
CORS调用原理图:
- 7.权限校验-路由导航守卫
- - 1.业务需求
问题:
用户可以通过输入网址,跳过登陆页面,去访问其他页面
需求:
想让用户访问其他页面的唯一路径就是在登陆页面输入信息,再进行页面之间的跳转;无论输入什么网址,都跳转到登录页面。
前端页面跳转是通过路由进行控制. 规定: 如果用户没有登录,则只允许访问登录页面.只有登录之后才能访问其它页面.
难点: 如何实现用户请求的拦截.
拦截器作用: 拦截用户的请求.
结果1: 请求放行
结果2: 请求拦截,一般配合重定向使用!!
- - 2.路由导航守卫的实现
- - - 1.参数说明
- - - 2.配置前端路由导航守卫(index.js)
const router = new VueRouter({
routes
})
/**
* 定义路由导航守卫!!!
* 参数1: to 路由跳转的网址
* 参数2: from 路由从哪里来
* 参数3: next 是一个函数,表示放行或者重定向
* next() 放行
* next("/login") 重定向
* 业务实现:
* 核心逻辑: 检查是否有token
* 若有token,表示用户已经登录,放行请求
* 若没有token,表示用户没有登陆,重定向到登录页面
* 若访问login页面,则直接放行
*/
router.beforeEach(
(to,from,next) => {
//若访问页面是login,则放行
if(to.path === "/login"){// === 表示数值和类型相等
return next()
}
//说明用户访问的请求不是login,请求需要校验
//获取token数据
let token = window.sessionStorage.getItem("token")
// if(token !== null && token.length > 0) 与 if(token) 相同
//用于子字符串的判断,判断解释为: 如果token不为null
if(token){
return next()
}
next("/login")
}
)
5.左侧菜单获取
- 1.项目介绍
- - 1.表设计分析
说明: 如果查询所有的一级菜单 则parent_id = 0
如果查询二级菜单信息 则parent_id = 1级菜单的ID
如果查询三级菜单信息 则parent_id= 2级菜单的ID
- - 2.编辑POJO(Rights .java)
说明:
1.当前对象中children属性不属于字段,所以使用@TableField(exist = false)进行标识.
2.权限列表中有父子级关系,所以通过children封装子级
package com.jt.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
@TableName("rights")
@Data
@Accessors(chain = true)
public class Rights extends BasePojo{
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
@TableField("parent_id")
private Integer parentId;
private String path;
private Integer level;
//用来封装父子关系
@TableField(exist = false)
private List<Rights> children; //不是表格固有属性
}
- - 3.搭建Rights层级代码
1.编辑Mapper----Service-----Controller 层级代码 方便后续业务调用
2.完成查询
3.页面展示
关于端口号说明
8000端口: VUE UI vue客户端管理器所有占用的端口.
8080端口: jtadmin 脚手架项目启动时占用的端口号
8091端口: 后台SpringBoot业务系统的端口号
- 2.实现左侧菜单
- - 1.编辑页面JS(Home.vue)
说明:当页面访问时,根据生命周期函数,调用getMenuList()方法.从后台的服务器获取菜单列表信息. JS如下.
- - 2.业务接口说明
只查询一级和二级的信息
- 请求路径 /rights/getRightsList
- 请求类型 GET
- 请求参数 无
- 响应数据 SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 返回权限List集合 |
- 响应数据如图所示
- - 3. 编辑RightsController
package com.jt.controller;
import com.jt.pojo.Rights;
import com.jt.service.RightsService;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/rights")
public class RightsController {
@Autowired
private RightsService rightsService;
@GetMapping("/findAll")
public List<Rights> findAll(Rights rights){
return rightsService.findAll(rights);
}
/**
* 查询左侧菜单列表,一级套二级的结构
* url:/rights/getRightsList
* 参数:没有参数
* 返回值:SysResult(list)
*/
@GetMapping("/getRightsList")
public SysResult getRightsList(){
List<Rights> rightsList = rightsService.getRightsList();
return SysResult.success(rightsList);
}
}
- - 4.编辑RightsServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.RightsMapper;
import com.jt.pojo.Rights;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class RightsServiceImpl implements RightsService{
@Autowired
private RightsMapper rightsMapper;
@Override
public List<Rights> findAll(Rights rights) {
// QueryWrapper<Rights> queryWrapper = new QueryWrapper<>(rights);
return rightsMapper.selectList(new QueryWrapper<>(rights));
}
/**
* 实现思路:
* 1.先查询一级菜单信息 parent_id = 0
* 2.将以及菜单循环遍历 一级菜单对象
* 3.根据一级菜单信息,查询当前菜单下的二级
* 4.将查询得到的二级菜单,封装到一级对象中
* 实现思路二(扩展):
* 利用左连接 实现关联查询 封装数据
*/
@Override
public List<Rights> getRightsList() {
//1.查询一级列表信息
QueryWrapper<Rights> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id", 0);
List<Rights> oneList = rightsMapper.selectList(queryWrapper);
//2.遍历一级列表
for(Rights oneRights : oneList){
//根据一级查询二级
queryWrapper.clear(); //清除之前的条件
queryWrapper.eq("parent_id", oneRights.getId());
List<Rights> twoList = rightsMapper.selectList(queryWrapper);
//将查询的结果封装到一级对象中
oneRights.setChildren(twoList);
}
return oneList;
}
}
- - 5.页面效果展现
- - 6.关于层级表设计的说明
案例: 有一个业务逻辑 父子关系有3级 问:表如何设计?
业务关系: 爷爷—父亲—儿子-----孙子------重孙子
想法1: 定义三张表 爷爷表(id)—父亲表(parent_id–爷爷)—儿子表(parent_id—父亲)
数据结构复杂!!! 不便于扩展!!!
想法2: 定义一张表(id-----parent_id)
要求: 每个ID都应该有自己的parent_id
总结: 如果有父子关系,则一遍采用parent_id的方式进行封装. 自关联的方式
- 3.左侧菜单的跳转说明
- - 1.关于左侧菜单路径说明
说明: 用户点击2级菜单时,跳转的路径 是由数据表中的path字段进行控制.
- - 2.左侧菜单路由机制
1.定义路由占位符 在Home组件中 在中间定义了路由占位符.
2.编辑路由机制
根据路由嵌套的规则,通过children属性 实现组件嵌套.最终实现课堂页面效果.
- - 3.左侧菜单路由全部实现
需求: 当用户默认跳转到home时,应该默认展现 Welcome的组件 关键语法:重定向机制
效果:
- 4.ElementUI 基本用法
<template>
<div>
<!-- 标签使用原则:
规则: 先定义,后使用
语法: el-breadcrumb 找到名称为Breadcrumb组件进行展现
定义elementUI的组件: 按需导入组件
从elementUI中导入特定的组件
1.import Vue from 'vue'
2.import {Breadcrumb} from 'element-ui'
3.Vue.use(Breadcrumb) 声明为全局组件.子组件可以直接调用
-->
<!-- 1.添加面包屑导航 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>活动管理</el-breadcrumb-item>
<el-breadcrumb-item>活动列表</el-breadcrumb-item>
<el-breadcrumb-item>活动详情</el-breadcrumb-item>
</el-breadcrumb>
<!-- 2.定义卡片视图 -->
<el-card class="box-card">
<div class="text item">
<!-- 4.栅格: 每行24格,可以动态的缩放 -->
<el-row :gutter="20">
<el-col :span="8">
<!-- 3.定义文本输入框-->
<el-input placeholder="请输入内容" v-model="input3" class="input-with-select">
<el-button slot="append" icon="el-icon-search"></el-button>
</el-input>
</el-col>
<el-col :span="3">
<el-button type="primary">主要按钮</el-button>
</el-col>
</el-row>
</div>
<hr>
<!-- 定义表格
:data 一般采用数组的方式 定义表格数据!!!
label="列名" 列字段的名称
stripe: 创建带斑马纹的表格, 默认为false(不写即为false) 启用为true(写上即为true)
border: 加竖直方向的边框, 默认为false 启用为true
-->
<el-table :data="tableData" style="width: 100%" stripe border>
<el-table-column label="编号" prop="id">
</el-table-column>
<el-table-column label="名称" prop="name">
</el-table-column>
<el-table-column label="年龄" prop="age">
</el-table-column>
<el-table-column label="性别" prop="sex">
</el-table-column>
</el-table>
<!-- 分页工具
:page-sizes 每页展现的条数信息
:total 设定总记录数
layout 展现的数据有哪些
:page-size 初始化时每页的条数,当作参数传递给后端
:current-page 默认展现的页数
@size-change 当每页的条数发生变化时,触发事件
@current-change 当前展现的页数发生变化时,触发事件
-->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage4"
:page-sizes="[10, 20, 30, 40]"
:page-size="10"
layout="total, sizes, prev, pager, next, jumper"
:total="1000">
</el-pagination>
</el-card>
<!--
:visible.sync 控制对话框是否可见 true/false
-->
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%">
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
//对外声明组件属性/方法等参数.要被根组件调用
export default {
data(){
return {
tableData : [
{id : 100, name : "黑熊精1" , age : 3000 , sex : "男"},
{id : 101, name : "黑熊精2" , age : 3001 , sex : "男"},
{id : 102, name : "黑熊精3" , age : 3002 , sex : "男"},
{id : 103, name : "黑熊精4" , age : 3003 , sex : "男"}
]
}
},
methods : {
handleSizeChange(size){
alert("当前的条数"+size)
},
handleCurrentChange(current){
alert("当前的页数"+current)
}
}
}
</script>
<style lang="less" scoped>
</style>
- 5.debug说明
说明: debug是编程中常用的代码的调试工具. 可以让代码一行一行执行.
6.用户管理业务实现
- 1.用户列表展现
- - 1.页面调用JS流程(user.vue)
1.生命周期函数
2.获取用户列表数据
3.页面URL分析
- - 2.用户查询的业务接口文档
- 请求路径: /user/list
- 请求类型: GET
- 请求参数: 后台使用PageResult对象接收
- 请求案例: http://localhost:8091/user/list?query=查询关键字&pageNum=1&pageSize=10
参数名称 | 参数说明 | 备注信息 |
---|---|---|
query | 用户查询的数据 | 可以为null |
pageNum | 分页查询的页数 | 必须赋值不能为null |
pageSize | 分页查询的条数 | 必须赋值不能为null |
- 响应参数: SysResult对象 需要携带分页对象 PageResult
参数名称 | 参数说明 | 备注信息 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 返回值PageResult对象 |
- PageResult 对象介绍
参数名称 | 参数类型 | 参数说明 | 备注信息 |
---|---|---|---|
query | String | 用户查询的数据 | 可以为null |
pageNum | Integer | 查询页数 | 不能为null |
pageSize | Integer | 查询条数 | 不能为null |
total | Long | 查询总记录数 | 不能为null |
rows | Object | 分页查询的结果 | 不能为null |
- 返回值效果
{"status":200,
"msg":"服务器调用成功!",
"data":
{"query":"",
"pageNum":1,
"pageSize":2,
"total":4,
"rows":[
{"created":"2021-02-18T11:17:23.000+00:00",
"updated":"2021-03-26T06:47:20.000+00:00",
"id":1,
"username":"admin",
"password":"a66abb5684c45962d887564f08346e8d",
"phone":"13111112222",
"email":"1235678@qq.com",
"status":true,
"role":null
},
{"created":"2021-02-18T11:17:23.000+00:00",
"updated":"2021-03-13T08:50:30.000+00:00",
"id":2,
"username":"admin123",
"password":"a66abb5684c45962d887564f08346e8d",
"phone":"13111112223",
"email":"1235678@qq.com",
"status":false,
"role":null
}
]
}
}
- - 3. 编辑PageResult对象(PageResult.java)
说明: 该对象主要的作用,封装分页后的结果 其中的数据需要在业务层 赋值.最后由SysResult对象 携带返回给用户.
package com.jt.vo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Accessors(chain = true)
public class PageResult implements Serializable {
private String query;
private Integer pageNum;
private Integer pageSize;
private Long total;
private Object rows;
}
- - 4. 编辑UserController
package com.jt.controller;
import com.jt.pojo.User;
import com.jt.service.UserService;
import com.jt.vo.PageResult;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 业务: 实现用户列表的分页查询
* 类型: GET
* url: http://localhost:8091/user/list?query=查询关键字&pageNum=1&pageSize=10
* 参数: PageResult对象进行接收 接收了3个参数
* 返回值: SysResult对象(PageResult对象) 返回5个参数
*/
@GetMapping("/list")
public SysResult getUserList(PageResult pageResult){ //3
pageResult = userService.getUserList(pageResult);
return SysResult.success(pageResult); //5
}
}
- - 根据手写sql进行分页查询
- - - 5.编辑UserServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import com.jt.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.util.List;
import java.util.UUID;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
/**
* 分页Sql:
* 语法:select * from user limit 起始位置,每页总条数
* 规则:数组 含头不含尾
* 查询第一页:
* select * from user limit 0,10
* 查询第二页:
* select * from user limit 10,10
* 查询第三页:
* select * from user limit 20,10
* 查询第N页:
* select * from user limit (页数-1)*条数,条数
*
* 方式1:手写SQL
* 方式2:MP的方式
*/
@Override
public PageResult getUserList(PageResult pageResult) {
//1.获取总记录数 Integer -- long 自动转换
long total = userMapper.selectCount(null);
//2.获取分页结果
int size = pageResult.getPageSize();
int start = (pageResult.getPageNum()-1) * size;
List<User> userList = userMapper.findListByPage(start,size);
pageResult.setTotal(total).setRows(userList);
return pageResult;
}
}
- - - 6.编辑UserMapper
说明: 利用注解实现Sql查询, 映射文件和注解标签二选一 不能同时使用
package com.jt.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jt.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper extends BaseMapper<User> {
//注解方式查询数据库,只适用于简单SQL语句!!! 注解和映射文件二选一
@Select("select * from user limit #{start},#{size}")
// @Insert()
// @Update()
// @Delete()
List<User> findListByPage(int start, int size);
// List<User> findListByPage(@Param("start") int start, @Param("size") int size);高版本自动封装
}
- - 根据MybatisPuls进行分页查询(API调用!!!)
- - - 5.编辑UserServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import com.jt.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.UUID;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
/**
* 分页Sql:
* 语法:select * from user limit 起始位置,每页总条数
* 规则:数组 含头不含尾
* 查询第一页:
* select * from user limit 0,10
* 查询第二页:
* select * from user limit 10,10
* 查询第三页:
* select * from user limit 20,10
* 查询第N页:
* select * from user limit (页数-1)*条数,条数
*
* 方式1:手写SQL
* 方式2:MP的方式
*/
/**
* 业务说明:利用MP方式查询数据库
* 需求:
* 1.分页查询 List<user>
* 2.获取记录总数 封装pageResult对象
*
* 步骤梳理:
* 1.构建MP的分页对象
* 2.根据分页对象查询数据
* 3.从分页对象中获取数据
* 4.封装pageResult对象
* 5.编辑配置类,封装拦截器
*/
@Override
public PageResult getUserList(PageResult pageResult) {
//1.定义/封装分页对象
//参数1: page分页对象
IPage<User> page = new Page<>(pageResult.getPageNum(), pageResult.getPageSize());//这个page中有两个参数
//2.定义条件构造器,指定动态SQL查询
//判断用户是否传参 如果传参 返回true 反之 返回false
boolean flag = StringUtils.hasLength(pageResult.getQuery());//判断当前对象是否有值
//参数2: 分页的查询条件 username模糊查询
//问题: 如果用户没有传递query like关键字 拼接参数
//动态拼接: 传参拼接like condition:true 拼接like条件
// false 不拼接 like关键字
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(flag, "username", pageResult.getQuery()); //动态SQL
//3.进行分页查询
//规则: page两个参数 根据分页查询返回 total/分页后的记录 4个参数
page = userMapper.selectPage(page,queryWrapper);//这个page中有4个参数
//4.从封装后的分页对象中获取数据
//根据分页对象,获取想要的结果
List<User> userList = page.getRecords();
long total = page.getTotal();
pageResult.setTotal(total).setRows(userList);
return pageResult;
}
}
- - - 6.编辑配置类(拦截器)
包路径: com.jt.config
说明: MP如果需要进行分页的查询 ,则必须添加配置类(拦截器).否则分页不能正常执行.
关于配置类的说明:
SpringBoot整合第三方框架时,提供了配置类的机制, 通过这种机制,第三方框架可以实现定制化的对象的创建.
package com.jt.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
//MybatisPlus在执行分页查询时,会被该拦截器拦截
//拦截器的作用 动态拼接where条件!!!
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MARIADB));
return interceptor;
}
}
- - 7.页面效果展现
- 2.用户状态修改
- - 1.业务说明
说明: 通过开关 可以控制数据类型的 true/false
参数说明:
1.传递userId主键
2.传递当前状态信息 true/false
- - 2.页面JS(user.vue)
1.作用域插槽
作用: 可以在表格中的任意单元格,获取当前行的元素信息.
关键语法: scope.row 获取行元素.
<!-- 定义作用域插槽-->
<!-- 用在表格中,作用是获取行信息 -->
<template slot-scope="scope">
<el-switch v-model="scope.row.status" @change="updateStatus(scope.row)"
active-color="#13ce66" inactive-color="#ff4949">
</el-switch>
</template>
2.Ajax业务调用
async updateStatus(user){
//实现用户状态修改 注意使用模版字符串 ES6中提出的新用法 ${key}
const {data: result} = await this.$http.put(`/user/status/${user.id}/${user.status}`)
if(result.status !== 200) return this.$message.error("用户状态修改失败!")
this.$message.success("用户状态修改成功!")
},
- - 3.业务接口文档
- 请求路径 /user/status/{id}/{status}
- 请求类型 PUT
- 请求参数: 用户ID/状态值数据
参数名称 | 参数类型 | 参数说明 | 备注信息 |
---|---|---|---|
id | Integer | 用户ID号 | 不能为null |
status | boolean | 参数状态信息 | 不能为null |
- 返回值结果: SysResult对象
{"status":200,"msg":"服务器调用成功!","data":null}
- - 4.编辑UserController
package com.jt.controller;
import com.jt.pojo.User;
import com.jt.service.UserService;
import com.jt.vo.PageResult;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 业务说明:修改状态信息
* url:/user/status/{id}/{status}
* 参数:id/status
* 返回值:SysResult
*/
@PutMapping("/status/{id}/{status}")
public SysResult updateStatus(User user){
userService.updateStatus(user);
return SysResult.success();
}
}
- - 5.编辑UserServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import com.jt.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
/**
* 规则:根据对象中不为null的元素当作set条件,Id当作唯一where条件
* sql:update user set status = true where id = xx
*/
@Override
public void updateStatus(User user) {// id/status
userMapper.updateById(user);
}
}
- 3.用户新增操作
- - 1.页面JS分析(user.vue)
- - 2.用户新增业务接口
- 请求路径 /user/addUser
- 请求类型 POST
- 请求参数: 整个form表单数据
参数名称 | 参数类型 | 参数说明 | 备注信息 |
---|---|---|---|
username | String | 用户名 | 不能为null |
password | String | 密码 | 不能为null |
phone | String | 电话号码 | 不能为null |
String | 密码 | 不能为null |
- 返回值结果: SysResult对象
{"status":200,"msg":"服务器调用成功!","data":null}
- - 3.编辑UserController
package com.jt.controller;
import com.jt.pojo.User;
import com.jt.service.UserService;
import com.jt.vo.PageResult;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 业务说明:实现用户新增
* url:/user/addUser
* 参数:整个form表单 对象 json
* 返回值:SysResult对象
*/
@PostMapping("/addUser")
public SysResult addUser(@RequestBody User user){
userService.addUser(user);
return SysResult.success();
}
}
- - 4.编辑UserServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import com.jt.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
/**
* 说明:
* 1.用户入库操作需要手动补齐的数据有
* 创建时间/修改时间 保证一致
* status = true 手动填充
* 2.密码加密处理 新增和登录加密算法必须一致
*/
@Override
public void addUser(User user) {
String password = user.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes());
user.setPassword(password)
.setStatus(true);
userMapper.insert(user);
}
}
- 4.数据的自动填充
- - 1.业务需求说明
需求: 数据库中每张表里 都包含创建时间/修改时间的字段. 如果每次操作表,都手动的去维护时间信息.则响应开发效率. 能否优化策略?
解决策略: MybatisPlus 实现自动填充功能.
- - 2.MPAPI说明
- - - 1.语法规则
- 实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
- 注解填充字段 @TableField(… fill = FieldFill.INSERT) 生成器策略部分也可以配置!
- - - 2.编辑POJO(添加注解) BasePojo.java
说明: 新增操作 需要自动填充 created/updated.
修改操作 需要自动填充 updated
- - - 3.编辑配置类 MyMetaObjectHandler.java
编辑完成之后,测试代码自动填充是否正常
package com.jt.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component //将对象交给Spring容器管理
public class MyMetaObjectHandler implements MetaObjectHandler {
//当数据库做新增操作时,自动调用 API调用 不需要问为什么
//metaObject对象 是MP自动填充的配置 有默认行为
@Override
public void insertFill(MetaObject metaObject) {
//获取当前时间
Date date = new Date();
this.setFieldValByName("created", date, metaObject);
this.setFieldValByName("updated", date, metaObject);
}
//当数据库做修改操作时,自动调用
@Override
public void updateFill(MetaObject metaObject) {
//获取当前时间
Date date = new Date();
this.setFieldValByName("updated", date, metaObject);
}
}
- 5.用户修改操作
- - 1.用户修改-数据回显
当用户点击修改按钮时应该出现弹出框.其中展现用户的数据信息.
展现方式:
- 获取用户ID,动态查询后台数据库信息,之后实现数据回显. 最新数据!!!
- 可以利用作用域插槽,获取当前行的对象,之后实现数据回显. 页面数据!!!
- - - 1.页面分析
页面修改按钮
//1. 点击修改的按钮
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="small" @click="updateUserBtn(scope.row)"></el-button>
<el-button type="danger" icon="el-icon-delete" size="small" @click="deleteUser(scope.row)"></el-button>
</template>
js页面修改按钮触发的回显函数
//2. 按钮事件
async updateUserBtn(user){
this.updateDialogVisible = true //弹框 true表示显示
const {data: result} = await this.$http.get("/user/"+user.id)
if(result.status !== 200) return this.$message.error("用户查询失败")
this.updateUserModel = result.data
},
- - - 2.查询用户的业务接口
- 请求路径: /user/{id}
- 请求类型: GET
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 返回user对象 |
- JSON格式如下:
{
"status":200,
"msg":"服务器调用成功!",
"data":{
"created":"2021-02-18T11:17:23.000+00:00",
"updated":"2021-05-17T11:33:46.000+00:00",
"id":1,
"username":"admin",
"password":"a66abb5684c45962d887564f08346e8d",
"phone":"13111112222",
"email":"1235678@qq.com",
"status":true,
"role":null
}
}
- - - 3.编辑UserController
需求: 根据ID查询User信息
package com.jt.controller;
import com.jt.pojo.User;
import com.jt.service.UserService;
import com.jt.vo.PageResult;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 业务说明:根据id查询用户信息
* url:/user/{id}
* 参数:主键id
* 返回值:SysResult(User对象)
*/
@GetMapping("/{id}")
public SysResult getUserById(@PathVariable Integer id){
User user = userService.getUserById(id);
return SysResult.success(user);
}
}
- - - 4.编辑UserServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import com.jt.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public User getUserById(Integer id) {
return userMapper.selectById(id);
}
}
- - - 5.数据回显
- - 2.用户修改–数据更新操作
- - - 1.页面JS分析(user.vue)
点击修改按钮之后的弹出框中的按钮
//1.修改的JS
<span slot="footer" class="dialog-footer">
<el-button @click="updateDialogVisible = false" >取 消</el-button>
<el-button type="primary" @click="updateUser">确 定</el-button>
</span>
点击确定按钮之后触发的函数,页面JS 发送情况.
- - - 2.业务接口文档说明
- 请求路径: /user/updateUser
- 请求类型: PUT
- 请求参数: User对象结构
参数名称 | 参数说明 | 备注 |
---|---|---|
ID | 用户ID号 | 不能为null |
phone | 手机信息 | 不能为null |
邮箱地址 | 不能为null |
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | null |
- JSON格式如下:
{
"status":200,
"msg":"服务器调用成功!",
"data":{}
}
- - - 3.编辑UserController
package com.jt.controller;
import com.jt.pojo.User;
import com.jt.service.UserService;
import com.jt.vo.PageResult;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 业务说明:实现用户跟新操作
* url:/user/updateUser
* 参数:对象提交 JSON串 注解接收
* 返回值:SysResult
*/
@PutMapping("/updateUser")
public SysResult updateUser(@RequestBody User user){
userService.updateUser(user);
return SysResult.success(user);
}
}
- - - 4.编辑UserServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import com.jt.vo.PageResult;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public void updateUser(User user) {
userMapper.updateById(user);
}
}
- 6.用户删除操作
- - 1.页面分析(user.vue)
页面删除按钮
//1.页面删除按钮
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="small" @click="updateUserBtn(scope.row)"></el-button>
<el-button type="danger" icon="el-icon-delete" size="small" @click="deleteUser(scope.row)"></el-button>
</template>
js页面删除按钮触发的函数
//2.发起ajax请求
async deleteUser(user){
//1.消息确认框
const result = await this.$confirm('此操作将永久删除 '+user.username+', 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).catch(error => error)
//点击删除按钮时,会出现弹出框
//如果点击确认 则返回confirm 如果点击取消 则返回cancel
if(result !== 'confirm'){
return this.$message.info("删除取消")
}
const {data: result2} = await this.$http.delete(`/user/${user.id}`)
if(result2.status !== 200) return this.$message.error("删除失败")
this.$message.success("删除成功")
//重新加载 数据
this.getUserList()
}
},
- - 2.业务接口文档
- 请求路径: /user/{id}
- 请求类型: delete
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
ID | 用户ID号 | 不能为null |
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | null |
- - 3.编辑UserController
package com.jt.controller;
import com.jt.pojo.User;
import com.jt.service.UserService;
import com.jt.vo.PageResult;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 业务说明:根据Id删除用户数据
* url:/user/{id}
* 参数:id
* 返回值:SysResult对象
*/
@DeleteMapping("/{id}")
public SysResult daleteById(@PathVariable Integer id){
userService.daletebyId(id);
return SysResult.success();
}
}
- - 4.编辑UserServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import com.jt.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public void daletebyId(Integer id) {
userMapper.deleteById(id);
}
}
- 7.关于事务说明
- - 1.什么是事务
如果后台服务器发生问题,则数据信息应该回滚,而不是提交操作.
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
事务作用: 可以保证数据的 /持久性(永久性)/一致性/隔离性/原子性
- - 2.现有代码的业务测试
说明: 如图如果程序执行过程中有报错信息.应该实现事务的回滚. 但是发现现有代码有2个问题
1: 没有添加事物.
2. 后台服务器报错之后,用户没有提示.
- - 3.全局异常处理机制
- - - 1.业务需求说明
说明: 如果后台服务器发生运行时异常,应该第一时间通知用户.否则用户没有任何提示.影响用户体验.
分析:
1.页面没有提示的原因是服务器没有返回201的报错数据.
2.后端服务器报错之后,没有异常处理机制
- - - 2.常规操作
说明: 一般控制异常信息. 通常情况下需要添加try-catch 用法.
弊端: 所有的方法都需要try-catch的控制. 必然导致代码的结构复杂.
解决方案: Spring内部提供了一种规则 全局异常的处理机制.
总结:
异常的处理是项目中经常用到的机制,但是如果将大量的异常处理代码写在方法中,则影响程序的结构.导致代码混乱.
- - - 3. 全局异常处理用法
- - - - 1.实现原理 AOP
AOP说明:
名称: “面向切面编程”
作用: 在不影响源码的条件下,对方法进行扩展,降低了业务的耦合性.
通知:
- 前置通知: before
- 后置通知: afterReturning
- 异常通知: afterThrowing
- 最终通知: after
上述的四大通知,不能改变程序的运行的状态.
- 环绕通知: around
环绕通知是功能最为强大的通知方法,可以控制程序的流转过程.
- - - - 2.全局异常处理实现
package com.jt.aop;
import com.jt.vo.SysResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.sql.SQLException;
/**
* Spring为了整合全局异常的处理 开发了如下的注解
* 1.@RestControllerAdvice //定义全局异常的处理类 返回值JSON串
* 2.@ExceptionHandler 标识拦截的异常的类型,如果类型匹配,则执行方法
* 注解的作用
* 1.该注解只拦截Controller层抛出的异常信息!!!
* controller --- service --- mapper 异常向上抛出
* 2.需要配合指定异常的类型
*/
@RestControllerAdvice
public class SystemAOP {
//当前的controller层,只拦截运行时异常
// @ExceptionHandler({RuntimeException.class, SQLException.class})
@ExceptionHandler(RuntimeException.class)
public SysResult exception(Exception e){
//控制台打印异常
e.printStackTrace();
return SysResult.fail();
}
}
- - - 4.添加事务-@Transactional注解
@Transactional规则说明:
1.如果发生运行时异常时,则实现事务回滚.
2.如图发生编译异常(检查异常),默认规则事务不回滚
3.属性说明:
noRollbackFor = {} 遇到某种异常不会滚
rollbackFor = {} 遇到某种异常回滚
7.商品管理业务实现
一级列表的 id 是二级列表的 parent_id
二级列表的 id 是三级列表的 parent_id
- 1.商品分类业务实现
- - 1. 实现商品分类页面跳转
- - - 1.需求
需求: 当用户跳转/itemCat时,应该展现ItemCat.vue组件.
点击商品分类,展示页面
- - - 2.页面分析
编辑路由,实现商品分类操作
- - 2.ItemCat设计说明
- - - 1.ItemCat表设计说明
说明: 商品分类结构的字段信息. 通过parent_id 控制父子级关系.
- - - 2.商品分类业务说明
1.京东官网商品分类介绍
说明: 每个层级下,都会有自己的单独的子级. 1级下边有2级 2级下边有3级.
一般层级结构,不要超过3级.
2.如何维护父子关系
表: id与parent_id
对象: this–children
3.三级菜单说明–Sql查询
4.如何利用对象封装3级菜单结构?
一级菜单
children-----> 二级菜单信息
children-------> 三级菜单信息
- - - 3.ItemCat POJO说明
- - 3.层级代码结构展现
- - 4.商品分类列表展现
- - - 1.页面分析
1.生命周期函数
页面Ajax调用
- - - 2.商品分类业务接口文档
- 请求路径: /itemCat/findItemCatList/{level}
- 请求类型: get
- 请求参数: level
参数名称 | 参数说明 | 备注 |
---|---|---|
level | 查询级别 | 1查询一级分类 2查询1-2 级商品分类 3查询1-2-3级商品分类 |
- 业务说明: 查询3级分类菜单数据 要求三层结构嵌套
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 3级商品分类信息 |
- - - 3.编辑ItemCatController
package com.jt.controller;
import com.jt.pojo.ItemCat;
import com.jt.service.ItemCatService;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("itemCat")
public class ItemCatController {
@Autowired
private ItemCatService itemCatService;
@GetMapping("/getAll")
public List<ItemCat> getAll(ItemCat itemCat){
return itemCatService.getAll(itemCat);
}
/**
* 业务说明: 实现商品分类查询
* URL: /itemCat/findItemCatList/{level}
* 参数: level 查询的层级
* 返回值: SysResult(List)
*/
@GetMapping("/findItemCatList/{level}")
public SysResult findItemCatList(@PathVariable Integer level){
List<ItemCat> itemCatsList = itemCatService.findItemCatList(level);
return SysResult.success(itemCatsList);
}
}
- - - 4.编辑ItemCatServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.ItemCatMapper;
import com.jt.pojo.ItemCat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ItemCatServiceImpl implements ItemCatService{
@Autowired
private ItemCatMapper itemCatMapper;
@Override
public List<ItemCat> getAll(ItemCat itemCat) {
return itemCatMapper.selectList(new QueryWrapper<>(itemCat));
}
/**
* 1.查询所有的一级菜单
* 2.遍历一级查询所有的二级菜单
* 3.遍历二级查询所有的三级菜单
* 4. 2级封装3级菜单, 1级封装2级菜单
*/
@Override
public List<ItemCat> findItemCatList(Integer level) {
//记录开始时间
long startTime = System.currentTimeMillis();
QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id", 0);
List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
for(ItemCat oneItemCat : oneList){
queryWrapper.clear();
queryWrapper.eq("parent_id", oneItemCat.getId());
List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
for(ItemCat twoItemCat : twoList){
queryWrapper.clear();
queryWrapper.eq("parent_id", twoItemCat.getId());
List<ItemCat> threeList = itemCatMapper.selectList(queryWrapper);
twoItemCat.setChildren(threeList);
}
oneItemCat.setChildren(twoList);
}
long endTime = System.currentTimeMillis();
System.out.println("程序耗时"+(endTime - startTime));
return oneList;
}
}
- - - 5.页面效果展现
- - - 6.问题说明
原始代码结构.出现2层循环结构. 如果外层循环10个,每个内层循环也是10个.完成这项业务需要查询100次数据库.
矛盾点: 多次查询数据库!
优化的策略: 数据库只查询一次,就可以获取商品分类三级嵌套结构
程序设计:
1.数据结构 Map<父级ID,子级列表> 列表信息中不包含嵌套关系
例如: Map<0,一级列表信息> 一级列表不包含二级/三级
Map<一级ID,二级列表信息> 只有2级列表 不包含3级
Map<二级ID,三级列表信息> 只有3级列表信息.
2.根据数据结构动态根据level查询子级.
- - - 7.业务实现
- - - - 1.业务封装原理图
- - - - 2.代码优化
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.ItemCatMapper;
import com.jt.pojo.ItemCat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ItemCatServiceImpl implements ItemCatService{
@Autowired
private ItemCatMapper itemCatMapper;
/**
* 1.数据结构:Map<k,v> key:parentId value:"List<ItemCat>"
* 2.封装Map数据类型
* 3.如果level=1 表示只获取一级
* 4.如果level=2 表示获取一级,一级嵌套二级
* 5.如果level=3 表示获取一级,一级嵌套二级,二级嵌套三级
*/
@Override
public List<ItemCat> findItemCatList(Integer level) {
long start = System.currentTimeMillis();
//1.封装Map集合
Map<Integer,List<ItemCat>> map = getMap();
//2.判断level的值
if(level == 1){
return map.get(0);//一级菜单的parentId=0(map中的key是parentId)
}
if(level == 2){
return getTwoList(map);
}
//如果level不是一级和二级,则一定是三级
List<ItemCat> list = getThreeList(map);
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end - start));
return list;
}
private List<ItemCat> getThreeList(Map<Integer, List<ItemCat>> map) {
//获取一级和二级菜单
List<ItemCat> oneList = getTwoList(map);
//封装三级,遍历二级菜单,之后封装
for(ItemCat oneItemCat : oneList){
//获取二级菜单集合
List<ItemCat> twoList = oneItemCat.getChildren();
if(twoList == null || twoList.size() == 0){
//由于业务数据不合理,跳过本次循环,执行下一次
continue;
}
for(ItemCat twoItemCat : twoList){
//查询三级列表,所以三级菜单的 parentId 是二级菜单的 Id
int parentId = twoItemCat.getId();
List<ItemCat> threeList = map.get(parentId);
twoItemCat.setChildren(threeList);
}
}
return oneList;
}
private List<ItemCat> getTwoList(Map<Integer, List<ItemCat>> map) {
//1.先获取一级列表
List<ItemCat> oneList = map.get(0);
//2.根据一级查询二级
for(ItemCat oneItemCat : oneList){
//查询二级,所以二级菜单的 parentId 是一级菜单的 Id
int parentId = oneItemCat.getId();
List<ItemCat> twoList = map.get(parentId);
//封装数据
oneItemCat.setChildren(twoList);
}
return oneList;
}
/**
* 1.封装Map集合 Map<Key=父级ID,value=List<ItemCat对象>>
* 2.说明: 将所有的数据库的父子关系,进行封装.(没有嵌套!!!!)
* 3.优势: 只查询一次数据库,就可以完成父子关系的封装.
* 策略:
* 1. key不存在, 准备一个新List集合,将自己当作第一个元素追加
* 2. key存在, 获取原有List集合,将自己追加.
*/
public Map<Integer,List<ItemCat>> getMap(){
//Map中包含了所有的父子级关系.
Map<Integer,List<ItemCat>> map = new HashMap<>();
//1.查询item_cat表中的所有的记录(1/2/3级菜单)
List<ItemCat> itemCatList = itemCatMapper.selectList(null);
//2.实现数据的封装
for(ItemCat itemCat : itemCatList){
//把列表中的 parentId 当作map中的 key
int key = itemCat.getParentId();
//判断map集合中 key 是否存在
if(map.containsKey(key)){ //若key存在
//先获取key
List<ItemCat> list = map.get(key);
//再将自己追加到其中
list.add(itemCat);
}else{ //若key不存在: 准备List集合,将自己作为第一个元素封装
List<ItemCat> list = new ArrayList<>();
list.add(itemCat);
map.put(key,list);
}
}
//将封装的数据进行返回.
return map;
}
}
- - 5.商品分类状态修改
- - - 1.业务分析
说明: 当用户点击状态码时,应该实现数据的修改操作.
- - - 2.页面JS分析
1.作用域插槽
<el-table-column prop="status" label="状态">
<!-- 定义作用域插槽 展现数据 scope.row展现行级元素 -->
<template slot-scope="scope">
<el-switch v-model="scope.row.status" active-color="#13ce66" inactive-color="#ff4949"
@change="updateStatus(scope.row)"></el-switch>
</template>
</el-table-column>
2.ajax调用
//根据ID修改状态信息
async updateStatus(itemCat) {
const {
data: result
} = await this.$http.put(`/itemCat/status/${itemCat.id}/${itemCat.status}`)
if (result.status !== 200) return this.$message.error("修改状态失败")
this.$message.success("状态修改成功")
},
- - - 3.业务接口文档说明
- 请求路径: /itemCat/status/{id}/{status}
- 请求类型: put
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
id | 用户ID值 | 不能为null |
status | 用户的状态信息 | 不能为null |
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
- - - 4.编辑ItemCatController
package com.jt.controller;
import com.jt.pojo.ItemCat;
import com.jt.service.ItemCatService;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("itemCat")
public class ItemCatController {
@Autowired
private ItemCatService itemCatService;
/**
* 业务说明:ItemCat状态的修改
* 类型:PUT
* url:/itemCat/status/{id}/{status}
* 参数:id/status
* 返回值:SysResult对象
*/
@PutMapping("/status/{id}/{status}")
public SysResult updateStatus(ItemCat itemCat){
itemCatService.updateStatus(itemCat);
return SysResult.success();
}
}
- - - 5.编辑ItemCatServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.ItemCatMapper;
import com.jt.pojo.ItemCat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ItemCatServiceImpl implements ItemCatService{
@Autowired
private ItemCatMapper itemCatMapper;
/**
* 修改状态信息
*/
@Override
@Transactional //事务控制
public void updateStatus(ItemCat itemCat) {
itemCatMapper.updateById(itemCat);
}
}
- - 6.商品分类新增
- - - 1.业务说明
说明: 商品分类实现中,需要添加一级/二级/三级分类信息. 但是父级下拉框中勾选1-2级菜单. 因为三级菜单不能当作父级. 当用户编辑完成之后,点击确定实现商品分类入库.
- - - 2.页面分析
<!-- 2.1定义一行 使用栅格-->
<el-row>
<el-col :span="24">
<el-button type="primary" @click="showAddItemCatDialog">新增分类</el-button>
</el-col>
</el-row>
//当展现新增商品分类时,应该渲染级联框数据
showAddItemCatDialog() {
this.findParentItemCatList()
this.addItemCatDialogVisible = true
},
async findParentItemCatList() {
//动态获取商品分类信息 type=2表示获取2级商品分类信息
const {
data: result
} = await this.$http.get("/itemCat/findItemCatList/2")
if (result.status !== 200) return this.$message.error("获取商品分类列表失败!!")
this.parentItemCatList = result.data
},
//商品分类确定按钮实现
<span slot="footer" class="dialog-footer">
<el-button @click="addItemCatDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="addItemCatForm">确 定</el-button>
</span>
//新增商品分类JS
async addItemCatForm() {
//先将整个表单进行校验
this.$refs.itemCatFormRef.validate(async validate => {
if (!validate) return
const {
data: result
} = await this.$http.post("/itemCat/saveItemCat", this.itemCatForm)
if (result.status !== 200) return this.$message.error("新增商品分类失败")
this.$message.success("新增商品分类成功!!!")
//新增成功,则刷新分类列表信息
this.findItemCatList();
this.addItemCatDialogVisible = false
})
},
- - - 3.业务接口文档
- 请求路径: /itemCat/saveItemCat
- 请求类型: post
- 请求参数: 表单数据
参数名称 | 参数说明 | 备注 |
---|---|---|
name | 商品分类名称 | 不能为null |
parentId | 用户父级ID | 不能为null |
level | 分类级别 | 1 2 3 商品分类级别 |
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
- - - 4.编辑ItemCatController
package com.jt.controller;
import com.jt.pojo.ItemCat;
import com.jt.service.ItemCatService;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("itemCat")
public class ItemCatController {
@Autowired
private ItemCatService itemCatService;
/**
* 业务说明:实现商品分类入库
* 分类:POST
* url:/itemCat/saveItemCat
* 参数:表单数据 itemCat对象~~~JSON
* 返回值:SysResult对象
*/
@PostMapping("/saveItemCat")
@Transactional
public SysResult saveItemCat(@RequestBody ItemCat itemCat){
itemCatService.saveItemCat(itemCat);
return SysResult.success();
}
}
- - - 5.编辑ItemCatServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.ItemCatMapper;
import com.jt.pojo.ItemCat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ItemCatServiceImpl implements ItemCatService{
@Autowired
private ItemCatMapper itemCatMapper;
/**
* 新增操作
*/
@Override
public void saveItemCat(ItemCat itemCat) {
itemCat.setStatus(true);
itemCatMapper.insert(itemCat);
}
}
- - 7.商品分类修改
- - - 1.页面JS分析
- - - - 1 修改按钮JS分析
<el-table-column label="操作">
<!-- 定义作用域插槽 定义标签等级-->
<template slot-scope="scope">
<el-button type="success" icon="el-icon-edit" @click="updateItemCatBtn(scope.row)">编辑</el-button>
<el-button type="danger" icon="el-icon-delete" @click="deleteItemCatBtn(scope.row)">删除</el-button>
</template>
</el-table-column>
//由于有层级关系,所有修改只能修改名称
updateItemCatBtn(itemCat) {
this.updateItemCatForm = itemCat
this.updateItemCatDialogVisible = true
},
- - - - 2 修改-确定按钮实现
<!-- 添加修改分类对话框 -->
<el-dialog title="修改商品分类" :visible.sync="updateItemCatDialogVisible" width="50%">
<!-- 定义分类表单 -->
<el-form :model="updateItemCatForm" :rules="rules" ref="upDateItemCatForm" label-width="100px">
<el-form-item label="分类名称:" prop="name">
<el-input v-model="updateItemCatForm.name"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="updateItemCatDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="updateItemCat">确 定</el-button>
</span>
</el-dialog>
async updateItemCat() {
//修改商品分类信息
const {
data: result
} = await this.$http.put('/itemCat/updateItemCat', this.updateItemCatForm)
if (result.status !== 200) return this.$message.error("更新商品分类失败")
this.$message.success("更新商品分类成功")
this.findItemCatList();
this.updateItemCatDialogVisible = false;
},
- - - 2.业务接口文档说明
- 请求路径url: /itemCat/updateItemCat
- 请求类型: put
- 请求参数: 表单数据 ItemCat对象
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
- - - 3.编辑ItemCatController
package com.jt.controller;
import com.jt.pojo.ItemCat;
import com.jt.service.ItemCatService;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("itemCat")
public class ItemCatController {
@Autowired
private ItemCatService itemCatService;
/**
* 业务说明:实现商品分类修改操作
* 分类:PUT
* url:/itemCat/updateItemCat
* 参数:form表单 json 对象接收
* 返回值:SysResult对象
*/
@PutMapping("/updateItemCat")
public SysResult updateItemCat(@RequestBody ItemCat itemCat){
itemCatService.updateItemCat(itemCat);
return SysResult.success();
}
}
- - - 4.编辑ItemCatServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.ItemCatMapper;
import com.jt.pojo.ItemCat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ItemCatServiceImpl implements ItemCatService{
@Autowired
private ItemCatMapper itemCatMapper;
@Override
@Transactional
public void updateItemCat(ItemCat itemCat) {
itemCatMapper.updateById(itemCat);//有主键,所以直接加进去就可以
}
}
- - 8.商品分类删除
- - - 1.业务说明
规则:
1.如果删除的商品分类是三级,则可以直接删除.
2.如果删除的商品分类是二级,则先删除三级,在删除二级.
3.如果删除的商品分类是一级,则先删除三级/二级/一级
注意事务的控制.
- - - 2.页面JS分析
- - - 3.业务接口文档说明
- 请求路径: /itemCat/deleteItemCat
- 请求类型: delete
- 参数: id/level
- 业务描述: 当删除节点为父级时,应该删除自身和所有的子节点
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
id | 用户id号 | 不能为null |
level | 商品分类级别 一级,二级,三级 |
- 返回值结果 SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
- - - 4.编辑ItemCatController
package com.jt.controller;
import com.jt.pojo.ItemCat;
import com.jt.service.ItemCatService;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("itemCat")
public class ItemCatController {
@Autowired
private ItemCatService itemCatService;
/**
* 业务说明:实现商品分类删除
* url:/itemCat/deleteItemCat?id=xxx&level=2
* 参数:id/level
* 返回值:SysResult对象
*/
@DeleteMapping("/deleteItemCat")
public SysResult deleteItemCat(ItemCat itemCat){
itemCatService.deleteItemCat(itemCat);
return SysResult.success();
}
}
- - - 5.编辑ItemCatServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.ItemCatMapper;
import com.jt.pojo.Item;
import com.jt.pojo.ItemCat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ItemCatServiceImpl implements ItemCatService{
@Autowired
private ItemCatMapper itemCatMapper;
/**
* 需求:删除商品分类信息
* 条件:如果有子级,应该先删除子级
*/
@Override
@Transactional
public void deleteItemCat(ItemCat itemCat) {
//level只可能是 1 2 3
int level = itemCat.getLevel();
//表示需要删除的数据是三级菜单,可以直接删除
if(level == 3){
itemCatMapper.deleteById(itemCat.getId());
}
//表示需要删除的数据是二级菜单,则先删除三级
if(level == 2){
QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id", itemCat.getId())//三级列表的 parent_id = 二级列表的 id
.or()
.eq("id", itemCat.getId());
itemCatMapper.delete(queryWrapper);
}
//表示需要删除的数据是一级菜单,则先删除三级和二级
if(level == 1){
//1.必须先获取二级菜单的id
QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id", itemCat.getId());//二级列表的 parent_id = 一级列表的 id
//2.获取结果的第一列字段(主键) 二级id
List<Object> twoIdList = itemCatMapper.selectObjs(queryWrapper);//二级列表的id集合
//3.删除三级的数据
queryWrapper.clear();
//DELETE FROM item_cat WHERE (parent_id IN (?,?) OR parent_id = ? OR id = ?)
queryWrapper.in(twoIdList.size()>0,"parent_id", twoIdList)//3
.or()
.eq("parent_id", itemCat.getId())//2
.or()
.eq("id", itemCat.getId());//1
itemCatMapper.delete(queryWrapper);
}
}
}
- 2.商品列表业务实现
- - 1.业务分析
- - - 1.表设计
- - - 2.编辑POJO对象
package com.jt.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
@TableName("item")
@Data
@Accessors(chain = true)
public class Item extends BasePojo{
@TableId(type = IdType.AUTO)
private Integer id; //商品Id号
private String title; //商品标题信息
private String sellPoint; //卖点信息
private Integer price; //商品价格
private Integer num; //商品数量
private String images; //商品图片
private Integer itemCatId; //商品分类ID号
private Boolean status; //状态信息 0 下架 1 上架
}
- - - 3.编辑Item的层级代码
- - - 4.实现前台页面跳转
页面效果展现:
- - 2.商品列表展现
- - - 1.页面JS分析
1.生命周期函数
created() {
//1.获取商品列表数据
this.getItemList()
},
2.实现页面Ajax请求
//实现商品信息分页查询
async getItemList() {
const {
data: result
} = await this.$http.get("/item/getItemList", {
params: this.queryItemInfo
})
if (result.status !== 200) return this.$message.error("商品列表查询失败")
this.itemList = result.data.rows
this.total = result.data.total
},
- - - 2.业务接口说明
- 请求路径: /item/getItemList?query=&pageNum=1&pageSize=10
- 请求类型: get
- 请求参数: 使用pageResult对象接收
参数名称 | 参数说明 | 备注信息 |
---|---|---|
query | 用户查询的数据 | 可以为null |
pageNum | 分页查询的页数 | 必须赋值不能为null |
pageSize | 分页查询的条数 | 必须赋值不能为null |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 商品分页对象 |
- - - 3.编辑ItemController
package com.jt.controller;
import com.jt.pojo.Item;
import com.jt.service.ItemService;
import com.jt.vo.PageResult;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/item")
public class ItemController {
@Autowired
private ItemService itemService;
@GetMapping("/findAll")
private List<Item> findAll(){
return itemService.findAll();
}
/**
* 业务说明:实现商品列表展现 分页查询
* 类型:GET
* url:/item/getItemList?query=&pageNum=1&pageSize=10
* 参数:pageResult
* 返回值:SysResult对象(pageResult)
*/
@GetMapping("/getItemList")
public SysResult getItemList(PageResult pageResult){//传过来3个参数
pageResult = itemService.getItemList(pageResult);//返回5个参数
return SysResult.success(pageResult);
}
}
- - - 4.编辑ItemServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jt.mapper.ItemMapper;
import com.jt.pojo.Item;
import com.jt.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
@Service
public class ItemServiceImpl implements ItemService{
@Autowired
private ItemMapper itemMapper;
@Override
public List<Item> findAll() {
return itemMapper.selectList(null);
}
/**
* 需求:实现商品的分页查询
*/
@Override
public PageResult getItemList(PageResult pageResult) {
Page<Item> itemPage = new Page<>(pageResult.getPageNum(), pageResult.getPageSize());//参数:当前页 每页多少条
//条判断件:若用户传递query 则添加where条件
Boolean flag = StringUtils.hasLength(pageResult.getQuery());
QueryWrapper<Item> queryWrapper = new QueryWrapper<>();
queryWrapper.like(flag, "title", pageResult.getQuery());
//itempage接口原来只有2个,经过分页查询之后有4个结果
itemPage = itemMapper.selectPage(itemPage, queryWrapper);//使用动态SQL进行分页查询
List<Item> itemList = itemPage.getRecords();
long total = itemPage.getTotal();
pageResult.setRows(itemList).setTotal(total);
return pageResult;
}
}
- - - 5.页面效果展现
- - 3.VUE过滤器用法
- - - 1.需求说明
说明: 需要将价格信息缩小100倍. 保留2位小数.
- - - 2.过滤器使用
- - - 3.过滤器定义
/* 定义过滤器
1.参数: 将|左侧的数据,当作参数传递给函数.
2.返回值: 必须添加返回值!!!
Vue.filter("过滤器名称",过滤器动作函数(参数){
})
*/
Vue.filter("priceFormat",function(price){
return (price / 100).toFixed(2)
})
- - 4.商品列表商品状态修改
- - - 1.业务说明
说明: 点击状态码时,应该实现数据状态的修改操作.
- - - 2.页面分析
<el-table-column prop="status" label="状态" width="80px">
<template slot-scope="scope">
<el-switch v-model="scope.row.status" active-color="#13ce66" inactive-color="#ff4949"
@change="updateStatus(scope.row)"></el-switch>
</template>
</el-table-column>
async updateStatus(item) {
const {
data: result
} = await this.$http.put("/item/updateItemStatus", {
id: item.id,
status: item.status
})
if (result.status !== 200) return this.$message.error("更新状态失败")
this.$message.success("更新状态成功")
},
- - - 3.业务接口文档
- 请求路径: /item/updateItemStatus
- 请求类型: put
- 请求参数: 使用对象接收
参数名称 | 参数说明 | 备注 |
---|---|---|
id | 商品id | 不能为null |
status | 状态信息 | 不能为null |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
- - - 4.编辑ItemController
package com.jt.controller;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.service.ItemService;
import com.jt.vo.ItemVO;
import com.jt.vo.PageResult;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/item")
public class ItemController {
@Autowired
private ItemService itemService;
/**
* 业务说明:实现商品状态修改
* 类型:PUT
* url:/item/updateItemStatus
* 参数:{id,status} json串
* 返回值:SysResult对象
* 问题:
*/
@PutMapping("/updateItemStatus")
public SysResult updateItemStatus(@RequestBody Item item){
itemService.updateItemStatus(item);
return SysResult.success();
}
}
- - - 5.编辑ItemServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jt.mapper.ItemMapper;
import com.jt.pojo.Item;
import com.jt.vo.ItemVO;
import com.jt.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
@Service
public class ItemServiceImpl implements ItemService{
@Autowired
private ItemMapper itemMapper;
/**
* 修改商品状态
*/
@Override
@Transactional
public void updateItemStatus(Item item) {
itemMapper.updateById(item);
}
}
- - 5.商品列表删除商品
- - - 1.业务说明
- - - 2.页面分析
<el-table-column label="操作">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="mini" @click="updateItemBtn(scope.row)">修改</el-button>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="deleteItemBtn(scope.row)">删除</el-button>
</template>
</el-table-column>
async deleteItemBtn(item) {
//消息确认框
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
//根据id删除数据
const {
data: result
} = await this.$http.delete("/item/deleteItemById", {
params: {
id: item.id
}
})
if (result.status !== 200) return this.$message.error("商品删除失败")
this.$message.success("商品删除成功")
//重新获取商品列表信息
this.getItemList()
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
- - - 3.业务接口文档
- 请求路径: /item/deleteItemById
- 请求类型: delete
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
id | 商品id | 不能为null |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
- - - 4.编辑ItemController
package com.jt.controller;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.service.ItemService;
import com.jt.vo.ItemVO;
import com.jt.vo.PageResult;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/item")
public class ItemController {
@Autowired
private ItemService itemService;
/**
* 业务说明:删除商品
* 类型:delete
* url: /item/deleteItemById
* 参数:id
* 返回值:SysResult对象
*/
@DeleteMapping("/deleteItemById")
public SysResult deleteItemById(Integer id){
itemService.deleteItemById(id);
return SysResult.success();
}
}
- - - 5.编辑ItemServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jt.mapper.ItemMapper;
import com.jt.pojo.Item;
import com.jt.vo.ItemVO;
import com.jt.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
@Service
public class ItemServiceImpl implements ItemService{
@Autowired
private ItemMapper itemMapper;
@Override
@Transactional
public void deleteItemById(Integer id) {
itemMapper.deleteById(id);
}
}
- - 6.商品列表修改商品信息
- - - 1.商品信息修改-数据回显
- - - - 1.业务说明
- - - - 2.页面分析
<el-table-column label="操作">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="mini" @click="updateItemBtn(scope.row)">修改</el-button>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="deleteItemBtn(scope.row)">删除</el-button>
</template>
</el-table-column>
async updateItemBtn(item){
this.updateDialogVisible = true //弹框 true表示显示
const {data: result} = await this.$http.get("/item/"+item.id)
if(result.status !== 200) return this.$message.error("商品信息查询失败")
this.updateItemModel = result.data
},
closeUpdateDialog(){
//重置表格数据
this.$refs.updateItemRef.resetFields()
},
- - - - 3.业务接口文档
- 请求路径: /item/{id}
- 请求类型: GET
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 返回user对象 |
- - - - 4.编辑ItemController
package com.jt.controller;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.service.ItemService;
import com.jt.vo.ItemVO;
import com.jt.vo.PageResult;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/item")
public class ItemController {
@Autowired
private ItemService itemService;
/**
* 业务说明:根据id查询信息
* 类型:get
* url:/item/{id}
* 参数:id
* 返回值:SysResult对象(Item)
*/
@GetMapping("/{id}")
public SysResult findItemById(@PathVariable Integer id){
Item item = itemService.findItemById(id);
return SysResult.success(item);
}
}
- - - - 5.编辑ItemServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jt.mapper.ItemMapper;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.vo.ItemVO;
import com.jt.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
@Service
public class ItemServiceImpl implements ItemService{
@Autowired
private ItemMapper itemMapper;
@Override
public Item findItemById(Integer id) {
return itemMapper.selectById(id);
}
}
- - - 2.商品信息修改-数据更新操作
- - - - 1.页面分析
<!-- 定义修改页面 当对话框关闭时,将表格数据重置-->
<el-dialog title="修改商品信息" :visible.sync="updateDialogVisible" width="50%" @close="closeUpdateDialog">
<!-- 定义用户提交表单数据-->
<el-form :model="updateItemModel" :rules="rules" ref="updateItemRef" label-width="100px">
<el-form-item label="商品id" prop="id">
<el-input v-model="updateItemModel.id" disabled></el-input>
</el-form-item>
<el-form-item label="商品标题" prop="title">
<el-input v-model="updateItemModel.title"></el-input>
</el-form-item>
<el-form-item label="卖点信息" prop="sellPoint">
<el-input v-model="updateItemModel.sellPoint"></el-input>
</el-form-item>
<el-form-item label="价格(元)" prop="price">
<el-input v-model="updateItemModel.price"></el-input>
</el-form-item>
<el-form-item label="数量" prop="num">
<el-input v-model="updateItemModel.num"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="updateDialogVisible = false" >取 消</el-button>
<el-button type="primary" @click="updateItem">确 定</el-button>
</span>
</el-dialog>
updateItem(){
//1.预校验数据
this.$refs.updateItemRef.validate(async valid => {
if(!valid) return this.$message.error("表单验证没有通过")
//根据接口文档要求封装数据
let item = {}
item.id = this.updateItemModel.id
item.title = this.updateItemModel.title
item.sellPoint = this.updateItemModel.sellPoint
item.price = this.updateItemModel.price
item.num = this.updateItemModel.num
const {data: result} = await this.$http.put(`/item/updateItem`,item)
if(result.status !== 200) return this.$message.error("商品信息修改失败")
this.$message.success("商品信息更新成功")
this.updateDialogVisible = false
this.getItemList()
})
}
- - - - 2.业务接口文档
- 请求路径: /item/updateItem
- 请求类型: PUT
- 请求参数: Item对象结构
参数名称 | 参数说明 | 备注 |
---|---|---|
ID | 商品序号 | 不能为null |
title | 商品标题 | 可以为null |
sellPoint | 卖点信息 | 可以为null |
price | 价格(元) | 可以为null |
num | 数量 | 可以为null |
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | null |
- JSON格式如下:
{
"status":200,
"msg":"服务器调用成功!",
"data":{}
}
- - - - 3.编辑ItemController
package com.jt.controller;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.service.ItemService;
import com.jt.vo.ItemVO;
import com.jt.vo.PageResult;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/item")
public class ItemController {
@Autowired
private ItemService itemService;
/**
* 业务说明:修改商品信息
* 类型:PUT
* url:/item/updateItem
* 参数:对象提交 JSON串 注解接收
* 返回值:SysResule对象
*/
@PutMapping("/updateItem")
public SysResult updateItem(@RequestBody Item item){
itemService.updateItem(item);
return SysResult.success(item);
}
}
- - - - 4.编辑ItemServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jt.mapper.ItemDescMapper;
import com.jt.mapper.ItemMapper;
import com.jt.pojo.Item;
import com.jt.vo.ItemVO;
import com.jt.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
@Service
public class ItemServiceImpl implements ItemService{
@Autowired
private ItemMapper itemMapper;
@Override
public void updateItem(Item item) {
itemMapper.updateById(item);
}
}
- - 7.商品列表新增商品
- - - 1.商品新增页面跳转
当用户点击添加商品时,应该跳转到商品添加页面
2. 页面跳转
页面效果展现
- - - 2.商品新增分析
1.点击按钮分析
<!-- 定义添加商品按钮-->
<el-button type="primary" class="addItemBtnClass" @click="addItemBtn">添加商品</el-button>
2.检查JS函数
/* 添加商品按钮 */
async addItemBtn(){
//console.log(this.addItemForm)
//1.完成表单校验
this.$refs.addItemFormRef.validate( valid => {
if(!valid) return this.$message.error("请输入商品必填项")
})
//2.完成商品参数的封装
//2.0 将商品价格扩大100倍
this.addItemForm.price = this.addItemForm.price * 100
//2.1 将商品图片的数据转化为字符串
this.addItemForm.images = this.addItemForm.images.join(",")
//2.5 实现商品数据提交
let submitAddItem = {
item : this.addItemForm,
itemDesc: this.itemDesc
}
console.log(submitAddItem)
let {data: result} = await this.$http.post("/item/saveItem",submitAddItem)
if(result.status !== 200) return this.$message.error("商品添加失败")
this.$message.success("商品添加成功")
//2.5添加完成之后,将数据重定向到商品展现页面
this.$router.push("/item")
}
- - - 3.业务接口说明
- 请求路径: http://localhost:8091/item/saveItem
- 请求类型: post
- 前端传递参数分析
{
item: {
images: "/2021/05/20/da0c1d4781c1499399f090da8b60f359.jpg,/2021/05/20/2ac1c34776a7465887eb019655354c3c.jpg"
itemCatId: 560
num: "100"
price: 718800
sellPoint: "【华为官方直供,至高12期免息0首付,原装正品】送华为原装无线充+运动蓝牙耳机+蓝牙音箱+三合一多功能数据线+钢化膜等!"
title: "华为P40 Pro 5G手机【12期免息可选送豪礼】全网通智能手机"
},
itemDesc: {
itemDesc: "<ul><li>品牌: <a href=https://list.jd.com/list.html"....... "
},
itemParam: {
dynamicArray: [
{paramId: 1, paramVals: "亮黑色,釉白色"},
{paramId: 2, paramVals: "8GB+128GB,8GB+256GB"}
],
staticArray: [
{"paramId": 3,"paramVals": "华为Mate 40 Pro"},
{"paramId": 4,"paramVals": "0.575kg"}.....
]
}
}
- 请求参数: 使用ItemVO对象接收
参数名称 | 参数类型 | 参数说明 | 备注 |
---|---|---|---|
item | Item | 商品基本信息对象封装 | 不能为null |
itemDesc | ItemDesc | 商品详情信息 | 不能为null |
itemParam | ItemParam | 商品参数信息 | 不能为null |
- ItemVO参数详解:
- Item对象
参数名称 | 参数类型 | 参数说明 | 备注 |
---|---|---|---|
title | String | 商品标题信息 | 不能为null |
sellPoint | String | 商品卖点信息 | 不能为null |
price | Integer | 商品价格信息 | 不能为null 需要将数据扩大100倍 |
num | Integer | 商品数量信息 | 不能为null |
images | String | 商品图片地址信息 | 不能为null |
itemCatId | Integer | 商品父级分类ID | 不能为null |
status | Boolean | 商品状态信息 | 不能为null |
- itemDesc 对象
-
为了降低商品提交代码的耦合性,将大字段信息详情,采用ItemDesc对象进行封装
参数名称 | 参数类型 | 参数说明 | 备注 |
---|---|---|---|
id | Integer | 商品Id信息 | 因为Item和ItemDesc是一对一关系 所以需要依赖Item对象的Id值 |
itemDesc | String | 商品详情信息 | 内部包含了大量的html语句 |
- - - 4.封装ItemVO对象
package com.jt.vo;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.pojo.ItemParam;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ItemVO { //该对象封装商品所有的参数信息
private Item item;
private ItemDesc itemDesc;
private ItemParam itemParam;
}
- - 8.商品详情说明
- - - 1.业务说明
说明: Item中存储的是商品的基本信息,通常用于检索,或者数据展现. itemDesc一般存储的都是商品的详情信息. 为了用户的检索效率更高,所以一般先查询item数据,(itemDesc一般采用大字段的形式,存储html代码的片段).
要求: 一个商品对应一个详情信息, 所以 item和itemDesc一对一
一个商品对应一个详情
一个详情对应一个商品
隐藏属性: item.id = itemDesc.id
补充知识: 数据库中常见对应关系
一对一: 老婆和老公(通常)
一对多: 老公和情人
多对多: 老师和学生
一个老师对应多个学生
一个学生对应多个老师
- - - 2.表设计
- - - 3.编辑POJO ItemDesc.java
package com.jt.pojo;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
@TableName("item_desc")
@Data
@Accessors(chain = true)
public class ItemDesc extends BasePojo{
//要求:item和itemDesc一对一 id相同(商品id和详情id一一对应)
// 简化了外键约束!!!
@TableId
private Integer id;
private String itemDesc;
}
- - - 4.编辑ItemController
package com.jt.controller;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.service.ItemService;
import com.jt.vo.ItemVO;
import com.jt.vo.PageResult;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/item")
public class ItemController {
@Autowired
private ItemService itemService;
/**
* 业务说明:实现商品新增操作
* 类型:post
* url:/item/saveItem
* 参数:ItemVO的JSON串 {item,itemDesc}
* 返回值:SysResult对象
*/
@PostMapping("/saveItem")
public SysResult saveItem(@RequestBody ItemVO itemVO){
itemService.saveItem(itemVO);
return SysResult.success();
}
}
- - - 5.编辑ItemServiceImpl
package com.jt.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jt.mapper.ItemDescMapper;
import com.jt.mapper.ItemMapper;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.vo.ItemVO;
import com.jt.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
@Service
public class ItemServiceImpl implements ItemService{
@Autowired
private ItemMapper itemMapper;
@Autowired
private ItemDescMapper itemDescMapper;
//实现商品入库操作
@Override
@Transactional
public void saveItem(ItemVO itemVO) {
//item 主键自增 数据库入库之后,才会有主键!!!
Item item = itemVO.getItem();
item.setStatus(true);
itemMapper.insert(item);
//问题:入库之后才有Id,现阶段item.id=null
//mybatis实现业务功能,自动回显主键!!! -- xml文件中手动配置
//MP自动将主键的回显功能实现!!!
//规则:itemId与ItemDescId是一样的
ItemDesc itemDesc = itemVO.getItemDesc();
itemDesc.setId(item.getId());
itemDescMapper.insert(itemDesc);
}
}
- - 9. 富文本编辑器
说明: 使用富文本编辑器,可以得到 “所见即所得”
用法:
- 引入JS
/* 导入富文本编辑器 */
import VueQuillEditor from 'vue-quill-editor'
/* 导入富文本编辑器对应的样式 */
import 'quill/dist/quill.core.css' // import styles
import 'quill/dist/quill.snow.css' // for snow theme
import 'quill/dist/quill.bubble.css' // for bubble theme
- 页面展现
<!-- 定义富文本编辑器-->
<quill-editor ref="myQuillEditor" v-model="itemDesc.itemDesc"></quill-editor>
注意事项: 图片/视频默认转化为01字节信息,并且以16进制的方式展现.
- 页面效果
- - 10.文件上传
- - - 1.官网API
<!-- 图片上传操作
file-list="fileList" 双向数据绑定 控制图片的数量数组结构[],
:on-preview="handlePreview" 点击图片时候调用的函数
:on-remove="handleRemove" 当用户点击删除按钮时,触发函数
multiple 配置多选
drag 是否启用拖拽
action="图片提交的地址信息"
-->
<el-upload
class="upload-demo"
action="https://jsonplaceholder.typicode.com/posts/"
:on-preview="handlePreview"
:on-remove="handleRemove"
:file-list="fileList"
list-type="picture"
multiple
drag>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
- - - 2.文件上传的业务接口文档
- 请求路径: http://localhost:8091/file/upload
- 请求类型: post
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
file | 文件上传的参数名称 | file中携带的是二进制信息 |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 返回ImageVO对象 |
- ImageVO对象说明
参数名称 | 参数类型 | 参数说明 | 备注 |
---|---|---|---|
virtualPath | String | 图片实际路径 不包含磁盘信息 | 例如: 2021/11/11/a.jpg 不需要写磁盘地址 |
urlPath | String | 图片url访问地址 | http://image.jt.com/2021/11/11/a.jpg 需要指定域名地址 |
fileName | String | 文件上传后的文件名称 | UUID.type |
- - - 3.封装ImageVO对象
package com.jt.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ImageVO implements Serializable {
private String virtualPath; //虚拟地址,不包含磁盘地址
private String urlPath; //url地址信息
private String fileName; //文件名称
}
- - - 4.文件上传入门案例
package com.jt.controller;
import com.jt.vo.SysResult;
import org.springframework.web.bind.annotation.CrossOrigin;
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;
import java.io.File;
import java.io.IOException;
@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
/**
* 业务说明:实现文件上传
* url:/file/upload
* 类型:POSt
* 参数:file
* 返回值:SysResult对象(imageVO)
* 高级API:MultipartFile 自动维护了缓存流/自动开关
*
* 文件上传的步骤:
* 1.获取文件的名称
* 2.准备上传文件的目录
* 3.封装文件全路径 目录/文件名称
* 4.实现文件的上传(输出)
*/
@PostMapping("/upload")
public SysResult upload(MultipartFile file) throws IOException {
//1.获取文件的名称 a.jpg
String fileName = file.getOriginalFilename();
//2.准备上传文件的目录 Linux系统不能识别\
String fileDir = "D:/images/";
//2.1判断目录是否存在
File dir = new File(fileDir);
if(!dir.exists()){
//若目录不存在,则创建多级目录
dir.mkdirs();
}
//3.封装文件全路径 目录/文件名称
String localPath = fileDir + fileName;
//4.实现文件的输出
file.transferTo(new File(localPath));
System.out.println("文件上传成功!!!");
return SysResult.success();
}
}
- - 11.图片上传业务实现
- - - 1.文件上传的注入事项
- 控制文件上传的类型(后台为主)
- 控制恶意程序的上传 通过宽度和高度
- 为了提高检索的速度,应该分目录存储.
- 动态生成文件名称 UUID,防止文件重名.
- 实现文件上传 注意路径.
- - - 2.正则表达式
- - - - 1.正则说明
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
- - - - 2.语法介绍
- 标识开头/结尾/匹配多次
regex: abc 标识只能匹配固定的字符abc
regex: abc* 标识匹配 ab,c可以任意次.
regex: abc? 标识匹配 ab,c可以1次或者0次.
- 匹配确定次
regex: c{3} c只能出现3次
regex: c{3,} c出现>=3次
regex: c{3,10} c出现>=3 <=10次
regex: .* 匹配所有字符.
- 匹配固定字符
regex:
a[ab] 匹配2个字符 第一个字符必须为a, 第二个字符 必须a 或者b
[a-z][0-9] 第一个字符是a-z 第二个字符必须是0-9
- 分组匹配
demo: (png|jpg|gif) 字符要么是png,要么是ipg 要么是gif.
- - - 3.编辑FileController
package com.jt.controller;
import com.jt.service.FileService;
import com.jt.vo.ImageVO;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
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;
@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
@Autowired
private FileService fileService;
/**
* 需求分析: 文件上传完成之后,需要返回ImageVO对象
*
* MultipartFile 接的是输入流 输出的是输出流
*/
@PostMapping("/upload")
public SysResult upload(MultipartFile file){
ImageVO imageVO = fileService.upload(file);
if(imageVO == null){//说明业务的执行有误
return SysResult.fail();
}
return SysResult.success(imageVO);
}
}
- - - 4.编辑FileServiceImpl
package com.jt.service;
import com.jt.vo.ImageVO;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
@Service
public class FileServiceImpl implements FileService{
/**
* 封装路径的前缀
* 规则说明:
* 文件磁盘地址: D:/images/yyyy/MM/dd/uuid.jpg
* 网络访问地址: http://image.jt.com/yyyy/MM/dd/uuid.jpg
*/
private String localDir = "D:/images";
private String preURLPath = "http://image.jt.com";
/**
* 完成校验:
* 1.校验是否为图片
* 2.木马.exe.jpg 判断是否满足图片固有属性 (高度/宽度) 若宽高有一项为0,则证明不是图片
* 3.为了提高查询效率,要求分目录存储.
* 3.1 按照后缀名分配 jpg,png,gif 效率提升不能满足要求
* 3.2 按照日期分 yyyy/MM/dd/HH 可以
* 3.3 商品分类 出现分布不均现象.
* 3.4 根据名称hash 之后截串
* demo: hash(a)=qw|er|as|dg/a.jpg
* 弊端: hash码可能出现分布不均的现象.
* 4.防止文件重名 使用uuid代替名称
*/
@Override
public ImageVO upload(MultipartFile file) {
//1.获取图片名称 demo: abc.jpg abc.JPG
String fileName = file.getOriginalFilename();
//bug说明: 由于windows系统不区分大小写,所以将字母全部转化为小写
fileName = fileName.toLowerCase();
//利用正则判断是否为图片.
if(!fileName.matches("^.+\\.(jpg|png|gif)$")){
//如果不是图片,则返回null
return null;
}
try {
//2.检查文件是否为恶意程序.
BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
if(width == 0 || height == 0){
//说明文件不是图片.
return null;
}
//3.根据时间实现目录的创建 时间--yyyy/MM/dd
String dateDir = new SimpleDateFormat("/yyyy/MM/dd/")
.format(new Date());
// D:/images/2021/9/8
String localDirPath = localDir + dateDir;
//创建目录
File dirFile = new File(localDirPath);
if(!dirFile.exists()){
dirFile.mkdirs();
}
//4. 使用uuid替换文件名称 唯一:系统内部唯一
String uuid = UUID.randomUUID().toString()
.replace("-","");
//截取文件的后缀 aa.bb.cc.jpg
int index = fileName.lastIndexOf(".");
//获取类型 .jpg
String fileType = fileName.substring(index);
String newFileName = uuid + fileType;
//5.实现文件上传操作 目录/文件名称
String realFilePath = localDirPath + newFileName;
file.transferTo(new File(realFilePath));
System.out.println("文件上传成功!!!");
//6.封装返回值
/**
* 6.1 封装虚拟路径,在各个系统之间可以灵活切换,只保存动态变化的目录
* path = 时间/uuid.type
*/
String virtualPath = dateDir + newFileName;
// http://image.jt.com/2011/09/09/uuid.jpg
String urlPath = preURLPath + virtualPath;
System.out.println("磁盘地址:"+realFilePath);
System.out.println("网络地址:"+urlPath);
return new ImageVO(virtualPath,urlPath,newFileName);
} catch (IOException e) {
e.printStackTrace();
return null; //表示程序有问题
}
}
}
- - - 5.代码调试
图片网络地址: http://image.jt.com/2021/09/09/190cc3f6e5f4452fa310668beb40b6bc.jpg
之后切换为磁盘地址:
磁盘地址:D:/images/2021/09/09/190cc3f6e5f4452fa310668beb40b6bc.jpg
- - 12.关于商品新增文件模块说明
重点: 业务需求. 业务顺序
- - - 1.基本信息
- 用户点击添加商品按钮时,
1.获取商品分类3级列表信息.
2.跳转到商品新增页面. - 用户录入基本的商品信息.
- 用户录入价格时 需要在后期将数据扩大100倍.
- - - 2.商品上传
- 当用户点击上传图片时,根据 属性名称 file=“图片的字节信息”,实现数据的传递.
- 后端通过特定的接口MultipartFile(mvc专门接收字节信息,内部封装IO流)
- 上传文件时 首先使用正则表达式保证文件的类型正确
- 判断图片是否为恶意程序.由于图片有特定的属性width/height 获取文件的宽度和高度,如果有一项为0则上传的文件不是图片.
- 为了提高文件的查询效率,将文件进行分目录存储. 采用的策略以"时间为维度"进行的目录分隔.
- 为了防止文件重名,采用UUID的方式重新定义文件名称.
- 最终实现文件上传.返回特定的VO数据供前端展现.
- - - 3.商品详情
- 通常商品信息和详情信息是分开维护的.由于商品详情是大字段(文本域),查询的效率低.
- 商品表与详情表是一对一的对应关系. 设计时 item.id=itemDesc.id.
- 之后利用富文本编辑器(所见即所得),展现详情信息,其中存储的是html代码片段.
最后将item/itemDesc封装之后提交入库.
- - 13.实现图片功能
- - - 1.图片的删除功能
- - - 1.业务需求说明
当用户点击删除图片时,应该发起请求同时删除服务器的数据.
重点: 如果需要删除数据,则应该传递虚拟路径信息
- - - 2.JS页面分析
//移除图片的方法
async handleRemove(file) {
//移除数组中的数据
let virtualPath = file.response.data.virtualPath
//通过findIndex函数 获取数组中指定数据的位置
let index = this.addItemForm.images.findIndex(x => x === virtualPath)
//删除数组中指定的数据
this.addItemForm.images.splice(index, 1)
//删除服务中的文件
let {
data: result
} = await this.$http.delete("/file/deleteFile", {
params: {
virtualPath: virtualPath
}
})
if (result.status !== 200) return this.$message.error("删除图片失败")
this.$message.success("删除图片成功")
},
- - - 3.业务接口说明
- 请求路径: http://localhost:8091/file/deleteFile
- 请求类型: delete
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
virtualPath | 文件上传的虚拟的路径 | 删除时需要磁盘路径一起删除 |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
- - - 4.编辑FileController
package com.jt.controller;
import com.jt.service.FileService;
import com.jt.vo.ImageVO;
import com.jt.vo.ItemVO;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
/**
* 业务说明:删除图片
* url:file/deleteFile?virtualPath=xxx
* 参数:virtualPath
* 返回值:SysResult对象
*/
@DeleteMapping("/deleteFile")
public SysResult deleteFile(String virtualPath){
fileService.deleteFile(virtualPath);
return SysResult.success();
}
}
- - - 5.编辑FileServiceImpl
package com.jt.service;
import com.jt.vo.ImageVO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
@Service
public class FileServiceImpl implements FileService{
private String localDir = "D:/images";
/**
* 实现思路:
* 1.根据虚拟地址,拼接磁盘地址
* 2.判断文件是否存在
* 3.实现文件删除
*/
@Override
public void deleteFile(String virtualPath) {
//1.生成本地磁盘地址
String path = localDir + virtualPath;
File file = new File(path);
if(file.exists()){
file.delete();
}
}
}
- - - 2.后台数据优化
- - - - 1.业务说明
说明: 如果路径信息会发生变化,则最好的方式通过动态赋值的方式完成.
解决方案:
- 编辑properties文件
- 通过@Value注解动态赋值.
- - - - 2.编辑image.properties文件
# 没有引号,没有空格
# 通过配置文件,动态赋值
image.localDir=D:/images
image.preURLPath=http://image.jt.com
- - - - 3.动态为属性赋值
- - - 14.代理机制
- - - - 1.业务说明
网络地址: http://image.jt.com/2021/09/09/8a3e2666cfbc4f1c9f0136339d42a562.jpg
磁盘地址: G:/images/2021/09/09/8a3e2666cfbc4f1c9f0136339d42a562.jpg
如果用户直接通过网络地址进行访问,是无法直接获取图片信息. 如果需要获取图片则应该实现 域名与磁盘地址的映射.
- - - - 2.正向代理
正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。
特点:
- 正向代理服务器位于用户和服务器之间.
- 用户发起请求时,非常明确自己访问的服务器到底是谁
- 真实的服务器不清楚真实的用户是谁.保护了用户的信息.
所以称之为客户端代理.
- - - - 3.反向代理
反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时,用户不需要知道目标服务器的地址,也无须在用户端作任何设定。反向代理服务器通常可用来作为Web加速,即使用反向代理作为Web服务器的前置机来降低网络和服务器的负载,提高访问效率。
特点:
1.反向代理服务器位于用户和服务器之间.
2.用户访问反向代理服务器,就可以获取真实的资源.
3.反向代理机制 用户无需了解真实的服务器信息.
4.反向代理保护了服务器端信息,也称之为服务器端代理.
- - - - 2.正向代理/反向代理说明
正向代理:客户端想要访问一个服务器,但是它可能无法直接访问这台服务器,这时候这可找一台可以访问目标服务器的另外一台服务器,而这台服务器就被当做是代理人的角色 ,称之为代理服务器,于是客户端把请求发给代理服务器,由代理服务器获得目标服务器的数据并返回给客户端。客户端是清楚目标服务器的地址的,而目标服务器是不清楚来自客户端,它只知道来自哪个代理服务器,所以正向代理可以屏蔽或隐藏客户端的信息。
反向代理:从上面的正向代理,你会大概知道代理服务器是为客户端作代理人,它是站在客户端这边的。其实反向代理就是代理服务器为服务器作代理人,站在服务器这边,它就是对外屏蔽了服务器的信息,常用的场景就是多台服务器分布式部署,像一些大的网站,由于访问人数很多,就需要多台服务器来解决人数多的问题,这时这些服务器就由一个反向代理服务器来代理,客户端发来请求,先由反向代理服务器,然后按一定的规则分发到明确的服务器,而客户端不知道是哪台服务器。常常用nginx来作反向代理。
说明: 用户每一次请求都包含了正向代理和反向代理.
正向代理一般适用于网络的通信.
反向代理一般适用于服务器获取信息.
- - - 15.Nginx
- - - - 1.Nginx简介
Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发布于2004年10月4日。
其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、简单的配置文件和低系统资源的消耗而闻名。2011年6月1日,nginx 1.0.4发布。
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。
Nginx的负载均衡:
负载:就是Nginx接受请求
均衡:Nginx将收到的请求按照一定的规则分发到不同的服务器进行处理
- - - - 2.Nginx特点
- nginx是非常优秀的反向代理服务器.
- 占用内存少 不到2M tomcat服务器占用多大内存 200M左右
- 并发(负载)能力强 3-5万次/秒 tomcat服务器并发能力 250-500次/秒 调优之后可以实现1000次/秒
- nginx可以当作负载均衡服务器使用.
- - - - 3.Nginx下载
注意事项:
1.nginx开发语言 C语言,对中文不友好.所以注意程序员操守
2.nginx启动时会默认占用80端口
访问测试:
- - - - 4.nginx启动报错说明
说明:nginx启动时会占用80端口.所以需要释放80资源.
步骤1: 查询 80端口被哪个进程占用
步骤2: 关闭进程
步骤3: 如果80端口 被PID=4占用,则需要升级驱动配置.
- - - - 5.nginx进程项说明
说明: 在windows中nginx服务每次点击启动之后,都会生成2个进程项.
注意事项: 在windows中nginx只能启动一次.
异常信息如下:
关于启动2项说明:
进程项1: nginx主要进程信息.
进程项2: nginx的守护进程 主要的任务防止主进程意外关闭.
关闭nginx 应该先关闭守护(内存晓得)再关闭主进程(内存大的).
- - - - 6.nginx命令
说明: nginx命令执行 需要nginx.exe所在的根目录中执行.
命令:
- 启动命令 start nginx
- 重启命令 nginx -s reload
- 关闭命令 nginx -s stop
- - - - 7.反向代理入门案例
知识点:
- nginx反向代理需要http协议支持.
- server 每个反向代理服务都是一个server.
- listen 关键字 默认监听80端口.
- server_name 根据指定的域名进行反向代理
- location 反向代理时拦截的策略 / 所有的请求
- root 代表反向代理的是一个目录
- index 默认访问的页面
反向代理配置:
http {
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
}
}
- - - - 8.实现图片的反向代理
- - - - - 1.业务说明
磁盘地址:G:/images/2021/09/09/a.jpg
网络地址:http://image.jt.com/2021/09/09/a.jpg
问题: 如何通过网络地址访问磁盘的图片?
1.用户通过域名访问真实的服务器.
2.nginx根据自身的配置进行拦截,根据配置文件将域名http://image.jt.com转化为具体的磁盘地址 G:/
3.根据磁盘地址访问真实的服务器资源.
4/5. 服务器将数据交给nginx,之后nginx将数据返回给用户.至此实现了反向代理.
- - - - - 2.图片服务器代理配置
注意事项:
1.启动时没有报错信息
2.重启时才会有报错. 所有最好先执行启动,再执行重启
# 配置图片服务器
# 拦截域名:http://image.jt.com:80
# 代理路径:G:/images
server {
listen 80;
server_name image.jt.com;
location / {
root G:/images;
}
}
- - - - - 3.图片回显流程
业务说明: 操作系统为了测试方便,在计算中保留了hosts文件. 该文件的主要的作用就是实现域名与IP地址的映射.但是该映射,只对本机有效.
- - - - - 4.修改hosts文件
# IP 与 域名映射
# 127.0.0.1 localhost
# ::1 localhost
#图片服务器配置
127.0.0.1 image.jt.com
#前端服务器配置
127.0.0.1 www.jt.com
#后端服务器配置
127.0.0.1 manage.jt.com
- - - - - 5.页面效果展现
- - - 16.京淘项目前端项目发布
- - - - 1. 关于前端说明
vue项目开发阶段采用脚手架的方式进行的开发,如果开发完成应该将项目打包编译.编译为浏览器可以识别的静态资源文件 (HTML/CSS/JS)
Nginx可以作为web服务器.并且默认端口号80.
- - - - 2.前端项目发布
- - - - - 1.路径修改
说明: 前端向后端发起请求时,网址 http://localhost:8091/xxxx,实际开发中服务器都是通过域名的方式访问,所以需要将前端的网址统一改为域名.
1.修改main.js 修改ajax请求的前缀
2. 修改addItem.vue文件 修改文件上传的路径
- - - - - 2.前端项目打包
通过build方式,将前端项目编译打包.
- - - - - 3.前端项目发布
说明: 将前端生成的dist目录 复制到nginx根目录中 如图所示:
- - - - - 4.前端反向代理
需求: 用户通过域名http://www.jt.com 访问系统的首页index.html
在nginx.conf中配置信息:
#配置前端服务器 www.jt.com
server {
listen 80;
server_name www.jt.com;
location / {
root dist;
index index.html;
}
}
页面效果:
- - - 17.京淘项目后端发布
- - - - 1.项目部署流程
- - - - 2.部署2台tomcat服务器
- - - - - 1.去除热部署
说明: 现在需要准备2台tomcat服务器,需要执行main方法2次.如果有热部署,则修改代码之后重启会影响配置流程. 所有关闭热部署.
- - - - - 2.动态获取端口
说明: 由于nginx中有负载均衡的策略 所以需要动态获取端口,验证是否负载均衡.
package com.jt.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PortController {
@Value("${server.port}")
private Integer port;
@GetMapping("/getPort")
public String getPort(){
return "访问端口:" + port;
}
}
- - - - - 3.代码调试
1.根据8091启动服务器.
2.修改yml文件中的端口号8092,之后再次启动服务
如图所示:
3. 根据端口号测试
如果上述的测试通过,则表示后台服务器配置正确.
- - - - - 4.IDEA主启动项说明
如图需要将允许多个运行勾选,之后可以运行多个java程序.
- - - - 3.Nginx实现tomcat集群部署
- - - - - 1.配置nginx服务器
# 定义tomcat集群
# 负载均衡策略: 1.轮询策略
upstream tomcats {
server 127.0.0.1:8091;
server 127.0.0.1:8092;
}
# 配置后台服务器 manage.jt.com 8091/8092
server {
listen 80;
server_name manage.jt.com;
location / {
# 代理的是一个请求路径
proxy_pass http://tomcats;
}
}
- - - - - 2. 测试效果
- - - - 4.Nginx负载均衡策略
- - - - - 1.轮询策略
说明: tomcat服务器依次访问.
# 定义tomcat集群
# 负载均衡策略: 1.轮询策略
upstream tomcats {
server 127.0.0.1:8091;
server 127.0.0.1:8092;
}
- - - - - 2.权重策略
说明: 根据权重设定,分配网络请求到不同的服务器中. 值越大访问越多.
# 定义tomcat集群
# 负载均衡策略: 1.轮询策略 2.权重策略
upstream tomcats {
server 127.0.0.1:8091 weight=10;
server 127.0.0.1:8092 weight=1;
}
- - - - - 3. IPHASH策略
需求: 如果需要让用户与服务器绑定.则可以使用ip_hash策略
使用说明:
1.方便进行压力测试.
2.某些用户的数据保存到服务器的Session中时,需要绑定数据.
3.公司特殊业务场景可能用到iphash.
# 定义tomcat集群
# 负载均衡策略: 1.轮询策略 2.权重策略 3.iphash策略
upstream tomcats {
ip_hash;
server 127.0.0.1:8091;
server 127.0.0.1:8092;
}
算法说明:
- - - 18.Linux
- - - - 1.部署JDK
- - - - - 1. 部署JDK流程
- 上传JDK安装包 /usr/local/src
- 解压安装包
- 修改Linux环境变量
- JDK环境测试
- - - - - 2.上传JDK
说明: 通过远程工具中的sftp协议,实现文件上传.
- - - - - 3.解压命令
命令: tar -xvf jdk-8u51-linux-x64.tar.gz
2.修改文件名称 mv jdk1.8.0_51 jdk1.8
- - - - - 4.编辑Linux环境变量
1.路径: /etc/profile
2.修改环境变量:
# 设定jdk环境
export JAVA_HOME=/usr/local/src/jdk1.8
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib