京淘项目
文章目录
- 京淘项目
- 一. 项目调用流程图
- 三. 用户登录实现
- 四. 页面布局
- 五. 用户模块管理
- 六. 业务辅助功能
- 七. MybatisPlus(MP)
- 八. 商品模块管理
- 九. 正反向代理
- 十.Windows 项目发布
- 十一. Linux项目发布
一. 项目调用流程图
关于前后端调用说明
前端服务器
说明1: 前端服务器通过脚手架的工具 启动了tomcat服务器. 端口号默认为8080/8081 该服务器中只部署前端项目.
说明2: 前端的数据从后端服务器中获取的. http://localhost:8091 2个不同类型的服务器.
性能问题:
前端和后端谁对服务器性能(处理速度更快)要求更好: 后端服务器.
前端和后端谁对服务器并发能力要求更好 前端服务器
2.1 后端项目搭建
2.1.1 创建项目
2.1.2 编辑pom.xml文件
<!--mybatis依赖包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!--jdbc依赖包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--添加lombok的包-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2.1.3 按照规则下载项目
说明: 根据码云中的代码, 粘贴复制pom.xml 和src文件 即可
2.1.4 代码启动测试
2.2 搭建前端项目
2.2.1 检查node.js是否正确
2.2.2 安装vue客户端工具
命令: npm install -g @vue/cli --force
2.2.3 启动客户端工具
命令: vue ui
浏览器效果:
2.2.4 前端项目导入
1.去码云中下载指定的前端项目
- 在IDEA的工作目录中解压
- 利用UI工具导入项目
- 项目启动
2.3 关于脚手架目录结构说明
2.3.1 关于VUE知识讲解
目录结构说明:
-
assets 引入第三方js/css
-
components 组件定义的目录 组件: 将html/css/js统一封装到一起,可以提高前端代码的扩展性.
-
plugins 引入elementUI.js的文件
-
router 代表路由
-
App.vue 代表整个项目的根组件
-
main.js 项目中核心JS文件
2.3.2 使用Axios的说明
- 定义共同 的请求前缀
/* 设定axios的请求根目录 */
axios.defaults.baseURL = 'http://localhost:8091/'
- 父子组件之间参数传递
/* 向vue对象中添加全局对象 以后发送ajax请求使用$http对象 */
/* 父组件将参数传递给子组件 需要声明 prototype.key= xxx */
Vue.prototype.$http = axios
2.4 关于Vue 路由说明
//使用路由机制
Vue.use(VueRouter)
const routes = [
{path: '/', redirect: '/login'},
{path: '/login', component: Login},
{path: '/elementUI', component: ElementUI}
]
三. 用户登录实现
3.1 表设计
1.表结构定义
2.编辑POJO
package com.jt.pojo;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class User extends BasePojo{
private Integer id;
private String username;
private String password;
private String phone;
private String email;
private Boolean status; //true 正常 false 停用
}
3.2 业务接口文档
- 请求路径: /user/login
- 请求方式: POST
- 请求参数
参数名称 | 参数说明 | 备注 |
---|---|---|
username | 用户名 | 不能为空 |
password | 密码 | 不能为空 |
- 响应数据 SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 返回密钥token信息 |
返回值格式如下:
{"status":200,"msg":"服务器调用成功!","data":"1e893a97634847b3a8b499b173bea620"}
3.3 创建SysResult对象
说明: 前端与后端进行业务交互.需要有一个统一的返回数据的结构,而这种负责前后端交互的媒介.称之为vo对象.
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);
}
}
3.4 关于登录业务流程
说明: 当用户完成登录时,前端系统会向后端进行访问.后端服务器经过数据库查询.如果查询正确 则返回token密钥信息. 如果查询失败 说明用户名和密码错误 返回null
3.5 页面Ajax请求路径说明
- URL地址
- 参数说明
3.6 编辑UserController
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.*;
@RestController
@RequestMapping("/user")
@CrossOrigin //前后端跨域的操作
public class UserController {
@Autowired
private UserService userService;
/*
* 业务:完成用户登录的操作
* URL:user/login
* 请求参数:username/password json串
* 返回值:SysResult对象 token密钥
* */
@PostMapping("/login")
public SysResult login(@RequestBody User user){
//业务逻辑:根据u/p查询数据库 true:用户登陆正常 token false:null
String token=userService.login(user);
if(token==null){
//表示后端查询失败,返回用户201
return SysResult.fail();
}
//表示有数据,返回值为200
return SysResult.success(token);
}
}
3.7 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公开密钥认证或是数字签名等用途。
常识性问题:
1.如果数据相同 算法相同 结果必然相同.
2.如果数据不同 算法相同 结果可能相同. hash碰撞问题.
规则:
数据可以穷举查询.但是不可以被破解.
3.8 编辑UserService
/**
* 思路:
* user数据:User(id=null, username=abc, password=123, phone=null, email=null, status=null)
* 1.将密码明文,转化为密文 sha1/md5算法/md5hash
* 2.根据用户名和密码查询用户信息
* 3.有结果: 返回token UUID
* 4.没有结果: 返回null
* @param user
* @return
*/
@Override
public String login(User user) {
String password = user.getPassword();
byte[] bytes = password.getBytes();
//1.将密码加密
String md5Password = DigestUtils.md5DigestAsHex(bytes);
user.setPassword(md5Password);
//2.根据用户名和密码查询数据库
User userDB = userMapper.findUserByUP(user);
//3.判断userDB是否有值
if(userDB == null){
//用户名和密码查询错误
return null;
}
//程序走到这里,说明用户名和密码正确 返回token
String token = UUID.randomUUID().toString();
return token;
}
3.9 编辑UserMapper
- 编辑Mapper接口
package com.jt.mapper;
import com.jt.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
//@Mapper 已经在主启动类中配置了 @MapperScan("com.jt.mapper") 注解//将该路径下的mapper接口交给容器管理
public interface UserMapper {
User findUserByUP(User user);
}
- 编辑Mapper映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jt.mapper.UserMapper">
<select id="findUserByUP" resultType="User">
select username,password from user where username=#{username} and password = #{password}
</select>
</mapper>
3.10 关于报错解决方案
3.10.1 F12开发者工具查询请求路径
3.10.2 根据报错信息检查路径
310.3 检查返回值问题
通过response选项 检查返回值 是否与预期一致.
3.11 用户首页跳转
3.11.1 业务说明
说明: 当用户登录成功之后,需要跳转到系统首页中.
url地址: /home
组件地址: Home.vue组件
前端login.vue组件局部代码
//获取表单对象之后进行数据校验
//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信息
let token = result.data
window.sessionStorage.setItem("token",token)
//用户登录成功之后,跳转到home页面
this.$router.push("/home")
3.11.2 修改路由信息
路径:router/index.js中
易错项说明: 如果修改代码之后,没有任何的效果。则需要检查操作的代码与脚手架中运行的代码是否为同一个。
四. 页面布局
4.1 左侧菜单列表展现
4.1.1 表设计
原理说明: 左侧菜单划分为3级.但是显示时只显示2级.
一级菜单获取: select * from rights where parent_id = 0
二级菜单获取: select * from rights where parent_id = 一级ID
三级菜单获取: select * from rights where parent_id = 二级ID
4.2 编辑POJO
@Data
@Accessors(chain = true)
public class Rights extends BasePojo{
private Integer id;
private String name;
private Integer parentId;
private String path;
private Integer level;
private List<Rights> children; //不是表格固有属性
}
4.3 构建层级代码
Mapper/Service/Controller 2-2-1
4.4 左侧菜单列表业务接口
- 请求路径 /rights/getRightsList
- 请求类型 GET
- 请求参数 无
- 响应数据 SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 返回权限List集合 |
响应数据如图所示
4.5 编辑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.*;
import java.util.List;
@RestController
@CrossOrigin
@RequestMapping("/rights")
public class RightsController {
@Autowired
private RightsService rightsService;
/**
* URL: /rights/getRightsList
* 参数: 无
* 类型: get类型
* 返回值: SysResult对象(list)集合
*/
@GetMapping("/getRightsList")
public SysResult getRightsList(){
List<Rights> list = rightsService.getRightsList();
return SysResult.success(list);
}
}
4.6 Sql查询语法
SELECT * FROM
rights p
LEFT JOIN
rights c
ON
p.id = c.parent_id
WHERE p.parent_id = 0
二选一即可
SELECT * FROM
(SELECT * FROM rights WHERE parent_id = 0)p
LEFT JOIN
rights c
ON p.id = c.parent_id
能在Mybatis使用版本:
SELECT
p.*,
c.id c_id,c.NAME c_name,c.parent_id c_parent_id,c.path c_path,
c.LEVEL c_level,c.created c_created,c.updated c_updated
FROM rights p LEFT JOIN rights c ON p.id=c.parent_id WHERE p.parent_id=0
或
SELECT * FROM (SELECT * FROM rights WHERE parent_id=0) p LEFT JOIN
(SELECT id c_id,NAME c_name,parent_id c_parent_id,path c_path,LEVEL c_level,created c_created,updated c_updated FROM rights) c
ON p.id=c.c_parent_id
4.7 编辑RightsService
package com.jt.service;
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;
/**
* 思路: 获取1-2级的菜单信息.
* 一级菜单: parent_id = 0
* 二级菜单: parent_id = 一级ID
* 将二级菜单数据,封装到一级菜单的children属性
* 方案一: 通过代码 先查询一级数据,再查询二级数据,之后完成封装
* 方案二: 通过关联查询,利用mybatis实现数据一对多的封装.
* @return
*/
@Override
public List<Rights> getRightsList() {
//只获取1级和2级数据
return rightsMapper.getRightsList();
}
}
4.8 编辑RightsMapper
- RightsMapper接口
public interface RightsMapper {
List<Rights> getRightsList();
}
- 添加RightsMapper.xml映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jt.mapper.RightsMapper">
<select id="getRightsList" resultMap="rightsRM">
SELECT * FROM
(SELECT * FROM rights WHERE parent_id = 0)p
LEFT JOIN
(SELECT id c_id,NAME c_name,parent_id c_parent_id,
path c_path,LEVEL c_level,created c_created,
updated c_updated FROM rights) c
ON p.id = c.c_parent_id
</select>
<resultMap id="rightsRM" type="Rights" autoMapping="true">
<id column="id" property="id"/>
<!--一对多-->
<collection property="children" ofType="Rights">
<id column="c_id" property="id"/>
<result column="c_name" property="name"/>
<result column="c_parent_id" property="parentId"/>
<result column="c_path" property="path"/>
<result column="c_level" property="level"/>
<result column="c_created" property="created"/>
<result column="c_updated" property="updated"/>
</collection>
</resultMap>
</mapper>
补充一个三表联查
<select id="getRightsList" resultMap="rightsRM">
SELECT * FROM
(SELECT p.id p_id,p.name p_name,p.parent_id p_parent_id,p.path p_path,p.level p_level,p.created p_created, p.updated p_updated FROM rights p) p
RIGHT JOIN
(SELECT * FROM rights WHERE parent_id = 0) g
ON g.id=p.p_parent_id
LEFT JOIN
(SELECT c.id c_id,c.name c_name,c.parent_id c_parent_id,c.path c_path,c.level c_level,c.created c_created, c.updated c_updated FROM rights c) c
ON p.p_id=c.c_parent_id
</select>
<resultMap id="rightsRM" type="Rights" autoMapping="true">
<id column="id" property="id"></id>
<collection property="children" ofType="Rights" autoMapping="true">
<id column="p_id" property="id"></id>
<result column="p_name" property="name"></result>
<result column="p_parent_id" property="parentId"></result>
<result column="p_path" property="path"></result>
<result column="p_level" property="level"></result>
<result column="p_created" property="created"></result>
<result column="p_updated" property="updated"></result>
<collection property="children" ofType="Rights" autoMapping="true">
<id column="c_id" property="id"></id>
<result column="c_name" property="name"></result>
<result column="c_parent_id" property="parentId"></result>
<result column="c_path" property="path"></result>
<result column="c_level" property="level"></result>
<result column="c_created" property="created"></result>
<result column="c_updated" property="updated"></result>
</collection>
</collection>
</resultMap>
4.9 页面效果展现
五. 用户模块管理
5.1 用户列表展现
5.1.1 vue中路由嵌套问题
5.1.1.1 路由入门案例
说明: 如果需要进行路由的嵌套. 则需要在路由定义的位置,指定路由的填充位
说明2: 如果路由中有父子的嵌套关系.则需要通过children属性标识
5.1.2 京淘后台父子组件跳转
说明: 编辑router/index.js文件 通过children属性实现父子组件的嵌套
页面效果展现:
5.1.3 业务接口文档
- 请求路径: /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
}
]
}
}
5.1.4 编辑PageResult对象
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; //分页查询的结果
}
5.1.5 编辑UserController
/**
* 需求: 将用户列表进行分页查询
* URL: /user/list
* 参数: query=查询关键字&pageNum=1&pageSize=10
* 请求类型: get
* 返回值: SysResult(pageResult)
*/
@GetMapping("/list")
public SysResult findUserList(PageResult pageResult){//3
pageResult = userService.findUserList(pageResult);//+2
return SysResult.success(pageResult);
}
5.1.6 编辑UserService
- 编辑业务接口
public interface UserService {
PageResult findUserList(PageResult pageResult);
}
- 编辑UserServiceImpl实现类
/**
* 分页查询的Sql:
* SELECT * FROM USER LIMIT 起始位置,查询条数
* 第一页: 每页10条
* SELECT * FROM USER LIMIT 0,10
* 第二页: 每页10条
* SELECT * FROM USER LIMIT 10,10
* 第N页:
* SELECT * FROM USER LIMIT (n-1)*10,10
* @param pageResult
* @return
*/
@Override
public PageResult findUserList(PageResult pageResult) {
//1.总记录数
long total = userMapper.findTotal();
//2.获取分页查询的结果
int start = (pageResult.getPageNum() - 1) * pageResult.getPageSize();
int size = pageResult.getPageSize();
String query = pageResult.getQuery();
List<User> rows = userMapper.findUserList(start,size,query);
return pageResult.setTotal(total).setRows(rows);
}
5.1.7 编辑UserMapper
- 编辑Mapper接口
//只支持单值传参 封装为map集合
List<User> findUserList(@Param("start") int start,@Param("size") int size,@Param("query") String query);
- 编辑UserMapper.xml映射文件
<!--表名必须小写 简化操作全部小写 ctrl+shift+y/u
业务需求: query属性可能有值/可能没有值 形成动态sql
-->
<select id="findUserList" resultType="User">
select * from user
<where>
<if test="query !=null and query !=''">username like "%"#{query}"%"</if>
</where>
limit #{start},#{size}
</select>
5.2 用户状态修改
5.2.1 业务说明
说明: 点击页面开关,修改数据表中的status
知识点讲解:
- 数据库中没有boolean类型 可以使用数值类型代替 true 代替数值1, false 代替数值0
- 数据库中可以使用tinyint类型代替boolean. 并且2种类型可以自动转化.
5.2.2 业务接口文档
- 请求路径 /user/status/{id}/{status}
- 请求类型 PUT
- 请求参数: 用户ID/状态值数据
参数名称 | 参数类型 | 参数说明 | 备注信息 |
---|---|---|---|
id | Integer | 用户ID号 | 不能为null |
status | boolean | 参数状态信息 | 不能为null |
- 返回值结果: SysResult对象
{"status":200,"msg":"服务器调用成功!","data":null}
5.2.3 编辑UserController
/**
* 需求: 实现状态修改
* URL: /user/status/{id}/{status}
* 参数: id/status
* 类型: put
* 返回值: SysResult对象
*/
@PutMapping("/status/{id}/{status}")
public SysResult updateStatus(User user){
userService.updateStatus(user);
return SysResult.success();
}
5.2.4 编辑UserService
- 编辑接口
void updateStatus(User user);
- 编辑Service实现类
/**
* 需求: 修改状态信息
* 参数: id/status/updated修改时间
* @param user
*/
@Override
public void updateStatus(User user) {
user.setUpdated(new Date()); //设定当前时间
userMapper.updateStatus(user);
}
5.2.5 编辑UserMapper接口
@Update("update user set status=#{status},updated=#{updated} where id=#{id}")
void updateStatus(User user);
5.3 用户删除操作
5.3.1 业务接口说明
- 请求路径: /user/{id}
- 请求类型: delete
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
ID | 用户ID号 | 不能为null |
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | null |
5.3.2 编辑UserController
/**
* 业务说明: 实现用户数据删除操作
* URL: /user/{id}
* 参数: id
* 返回值: SysResult对象
*/
@DeleteMapping("/{id}")
public SysResult deleteUserById(@PathVariable Integer id){
userService.deleteUserById(id);
return SysResult.success();
}
5.3.3 编辑UserService
1.接口方法
void deleteUserById(Integer id);
2.实现类方法
@Override
public void deleteUserById(Integer id) {
userMapper.deleteUserById(id);
}
5.3.4 编辑UserMapper
@Delete("delete from user where id=#{id}")
void deleteUserById(Integer id);
5.3.5 修改页面JS
5.4 用户新增
5.4.1 业务接口文档
- 请求路径 /user/addUser
- 请求类型 POST
- 请求参数: 整个form表单数据封装为js对象进行参数传递
参数名称 | 参数类型 | 参数说明 | 备注信息 |
---|---|---|---|
username | String | 用户名 | 不能为null |
password | String | 密码 | 不能为null |
phone | String | 电话号码 | 不能为null |
String | 密码 | 不能为null |
- 返回值结果: SysResult对象
{"status":200,"msg":"服务器调用成功!","data":null}
5.4.2 编辑UserController
/**
* 需求: 完成用户新增操作
* URL地址: /user/addUser
* 参数: 使用对象接收
* 返回值: SysResult
* 类型: post
*/
@PostMapping("/addUser")
public SysResult saveUser(@RequestBody User user){
userService.saveUser(user);
return SysResult.success();
}
5.4.3 编辑UserService
- 编辑UserService接口
void saveUser(User user);
- 编辑UserServiceImpl实现类
/**
* 业务:
* 1.密码进行加密处理
* 2.设定状态码信息
* 3.设定创建时间和修改时间
* @param user
*/
@Override
public void saveUser(User user) {
//将密码加密处理
String password = user.getPassword();
Date date = new Date();
String md5Pass = DigestUtils.md5DigestAsHex(password.getBytes());
user.setPassword(md5Pass)
.setStatus(true)
.setCreated(date)
.setUpdated(date);
userMapper.saveUser(user);
}
5.4.4 编辑Mapper/映射文件
- 编辑Mapper接口
void saveUser(User user);
- 编辑Mapper映射文件
<insert id="saveUser">
insert into user value (null,#{username},#{password},#{phone},
#{email},#{status},#{created},#{updated})
</insert>
5.5 用户修改-数据回显
5.5.1 业务接口文档说明
- 请求路径: /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
}
}
5.5.2 编辑UserController
/**
* 业务说明: 根据ID查询数据
* URL: /user/{id}
* 参数: id值
* 类型: get
* 返回值: SysResult(user对象)
*/
@GetMapping("/{id}")
public SysResult findUserById(@PathVariable Integer id){
User user = userService.findUserById(id);
return SysResult.success(user);
}
5.5.3 编辑UserService
1.编辑业务接口代码
User findUserById(Integer id);
2.编辑实现类代码
@Override
public User findUserById(Integer id) {
return userMapper.findUserById(id);
}
5.5.4 编辑UserMapper
@Select("select * from user where id=#{id}")
User findUserById(Integer id);
5.6 用户修改-数据提交
5.6.1 业务接口说明
- 请求路径: /user/updateUser
- 请求类型: PUT
- 请求参数: User对象结构
参数名称 | 参数说明 | 备注 |
---|---|---|
ID | 用户ID号 | 不能为null |
phone | 手机信息 | 不能为null |
邮箱地址 | 不能为null |
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | null |
- JSON格式如下:
{
"status":200,
"msg":"服务器调用成功!",
"data":{}
}
5.6.2 编辑UserController
/**
* 业务: 实现用户的修改操作
* URL地址: /user/updateUser
* 参数: 使用对象User接收
* 返回值: SysResult
* 类型:
*/
@PutMapping("/updateUser")
public SysResult updateUser(@RequestBody User user){
userService.updateUser(user);
return SysResult.success();
}
5.6.3 编辑UserService
1.编辑接口
void updateUser(User user);
2.编辑实现类
/**
* 说明: 实现用户的修改
* 数据: 添加更新时间操作
* @param user
*/
@Override
public void updateUser(User user) {
user.setUpdated(new Date());
userMapper.updateUser(user);
}
5.6.4 编辑UserMapper
1.编辑UserMapper接口
void updateUser(User user);
2. 编辑Mapper映射文件
<!--完成用户的更新操作-->
<update id="updateUser">
update user set
phone=#{phone},
email=#{email},
updated=#{updated}
where id=#{id}
</update>
六. 业务辅助功能
6.1 事务控制
6.1.1 现象说明
说明: 业务逻辑在执行的过程中,如果中间发生了异常,应该保证事务的一致性.事务应该回滚.但是经过测试,发现执行报异常,但是数据可以正常的入库. 说明方法没有事务的控制.
6.1.2 关于注解说明
名称: @Transactional //事务的注解
特点:
1.Spring中默认对事务进行支持.
2.Spring中默认控制的是运行时异常. 如果是检查异常 Spring不负责处理.
检查异常:try catch
核心原理: AOP
常见案例:
@Transactional(rollbackFor = Exception.class) 只要有异常,则全部回滚.
问题: 如果采用上述的代码,则AOP拦截所有的异常,运行效率低. 所以一般只拦截运行时异常.检查异常由程序员手动控制.
/*
* 业务:
* 1.密码进行加密处理
* 2.设定状态码信息
* 3.设定创建时间和修改时间
* */
@Override
// @Transactional(rollbackFor = SQLException.class) //事物的注解
@Transactional
public void addUser(User user) {
byte[] bytes = user.getPassword().getBytes();
String md5Password = DigestUtils.md5DigestAsHex(bytes);
user.setPassword(md5Password).setStatus(true).setCreated(new Date()).setUpdated(user.getCreated());
userMapper.addUser(user);
}
总结: 以后操作数据库时 ,新增/删除/修改操作 需要事务控制.
6.2 全局异常处理机制
6.2.1 业务说明
当用户在进行业务操作时,不管后端服务器执行是否正确,都应该给用户一个正向的回馈!!!
解决方案:
@PutMapping("/status/{id}/{status}")
public SysResult updateStatus(User user){
try {
userService.updateStatus(user);
return SysResult.success();
}catch (Exception e){
e.printStackTrace();
return SysResult.fail();
}
}
暴露问题: 由于在每个方法中都需要添加try-catch代码 必然会导致代码的可读性低.并且代码繁琐.不便于扩展.
6.2.2 全局异常处理机制实现
package com.jt.aop;
import com.jt.vo.SysResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice //advice 通知!!!
//全局异常处理机制,只拦截Controller层
public class SystemException {
//指定异常的类型进行拦截.
@ExceptionHandler(RuntimeException.class)
public SysResult exception(Exception e){
e.printStackTrace();//控制台打印异常
return SysResult.fail();//201
}
}
七. MybatisPlus(MP)
7.1 MP介绍
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
7.2 MP特点
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
7.3 ORM思想
7.3.1 ORM说明
对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。如今已有很多免费和付费的ORM产品,而有些程序员更倾向于创建自己的ORM工具。
总结:以面向对象的方式操作数据库
7.3.2 ORM特点
- 对象与数据库中的表什么关系? 一一映射
- 对象中的属性与数据库中的字段什么关系? 一一映射
- 以面向对象的方式操作数据库。由框架动态生成Sql语句。实现了跨数据库的操作.
7.4 MP入门案例
7.4.1 导入jar包
<!--spring整合mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
7.4.2 编辑POJO对象
package com.jt.pojo;
@Data
@Accessors(chain = true)
@TableName("user") //让对象与表名一一映射
public class User extends BasePojo{
@TableId(type = IdType.AUTO/*主键自增*/) //标识主键
private Integer id;
// @TableField("username") 如果属性与字段名称一致(包含驼峰规则),那么注解可以省略不写
private String username;
private String password;
private String phone;
private String email;
private Boolean status;
}
7.4.3 编辑Mapper接口
说明: MP将常用的CURD操作进行封装.以后用户需要使用,只需要继承公共的业务接口即可.
//继承时,必须要添加泛型,并且该泛型必须与表关联
//MP提供了强大的单表的CRUD操作,多表自己写
//此处BaseMapper<User>中的泛型限制了UserMapper的()不管是实现类还是代理类实例对象调用的构造器泛型都必须是User;
public interface UserMapper extends BaseMapper<User> {}
7.4.4 编辑YML文件
7.4.5 入门案例
package com.jt;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
import java.util.List;
@SpringBootTest
class JtApplicationTests {
@Autowired
private UserMapper userMapper;
/*
* 实现数据的新增
* */
@Test
void contextLoads() {
User user = new User();
user.setUsername("admin888").setPassword("123123").setEmail("11@qq.com") .setPhone("13412345678").setStatus(true).setCreated(newDate()).setUpdated(user.getCreated());
int rows = userMapper.insert(user);
System.out.println("影响的行数:"+rows);
}
}
}
7.5 MP案例
7.5.1 根据ID查询
/**
* 案例1: 根据ID进行查询
*/
@Test
public void test01(){
int id = 1;
User user = userMapper.selectById(id);
System.out.println(user);
}
7.5.2 对象封装数据
/*
* 案例2:根据其它属性查询数据
* 根据username="admin888"进行查询
* 组件: 条件构造器 用来封装where条件的
* select * from user where username="admin888"
* 如果可以保证查询的结果为一个,则用对象接
* 方式一:利用对象去封装数据 只能满足=号逻辑运算符
* User user = userMapper.selectOne(queryWrapper);
* 如果不能可以保证查询的结果为一个,则用集合接
* List<User> userList = userMapper.selectList(null);
*
* */
@Test
public void test2(){
User temp =new User();
temp.setUsername("admin888");//=号拼接
//条件构造器,会根据对象中不为null的元素动态拼接sql 注意此处要传入temp对象,否则就会把所有的值都查询出来
QueryWrapper<User> queryWrapper =new QueryWrapper<>(temp);
/*如果可以保证查询的结果为一个,则用对象接*/
User user = userMapper.selectOne(queryWrapper);
System.out.println(user);
}
7.5.3 转译字符方式
/*
* 案例3:查询Id>5de的数据
* select * from user where id >5;
* 转义字符 > > < < = eq; >= ≥ <= le; != ne;
* 利用逻辑运算符 实现sql操作
* */
@Test
public void test3(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("id",5);
List<User> userList = userMapper.selectList(queryWrapper);
System.out.println(userList);
}
7.5.4 模糊查询
/*
* 案例4:查询username 包含admin的数据,并且按照id降序排列
* select * from user where username like "%admin%" order by id desc;
* 知识点:
* 1.like "%admin%" 左右都有%号
* 2.likeLeft "%admin" 左边有%号
* 3.likeRight "admin%" 右边有%号
* */
@Test
public void test4(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("username","admin").orderByDesc("id");
List<User> userList = userMapper.selectList(queryWrapper);
System.out.println(userList);
}
7.5.5 in关键字
/*
* 案例5:查询id=2,4,5,7,8的数据
* select * from user where id in(2,4,5,7,8);
* 注意事项:在使用in关键字时候,最好使用包装类型.
* */
@Test
public void test5(){
//int[] ids={2,4,5,7,8}
Integer[] ids={2,4,5,7,8};
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.in("id",ids);
//方式1:利用条件构造器查询
List<User> userList = userMapper.selectList(queryWrapper);
System.out.println(userList);
//方式2:利用API查询
List<Integer> list = Arrays.asList(ids);
List<User> userList1 = userMapper.selectBatchIds(list);
System.out.println(userList1);
Integer[] integers = (Integer[]) list.toArray();
System.out.println(Arrays.toString(integers));
}
7.5.6 动态Sql查询
/*
* 案例6:动态查询数据,利用电话/邮箱查询数据
* MP特点 根据对象(pojo对象,而不是QueryWrapper条件构造器对象)中不为null的属性充当条件
*
* */
@Test
public void test6(){
//空串和空不一样,空串是有值只不过是空串(长度为0),调用API不会出现空指针情况,空则会出现空指针情况,所以判断条件phone!=null && phone!=""
String phone = "13412345678";
String email = "11@qq.com";
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// queryWrapper.eq("phone", phone).eq("email", email); 非动态查询
//动态查询
// queryWrapper.eq(phone!=null && phone!="","phone", phone).eq(email!=null && email!="","email", email);
queryWrapper.eq(StringUtils.hasLength(phone),"phone", phone).eq(StringUtils.hasLength(email),"email", email);
List<User> userList = userMapper.selectList(queryWrapper);
System.out.println(userList);
}
7.5.7 根据主键查询
/*
* 案例7:只查询主键信息
* API: selectObjs: 获取主键信息
* 实际用途: 关联查询
* */
@Test
public void test7(){
//查询所有:select * from user
List<User> userList = userMapper.selectList(null);
System.out.println(userList);
//只查询主键信息 构造器设置为空,c
List<Object> ids = userMapper.selectObjs(null);
System.out.println(ids);
}
7.5.8 根据主键更新
/*
* 案例8:用户的修改操作
* 1.根据id修改数据
* 将id=19 的name 改为 "admin999",phone="110110"
* 原理:Id当作唯一的where条件,其他的不为null的属性当作set条件
* userMapper.updateById(user);
* */
@Test
public void test8(){
User user = new User();
user.setId(19).setUsername("admin999").setPhone("110110");
int i = userMapper.updateById(user);
System.out.println("更新成功,影响行数"+i);
}
7.5.9 根据其它条件更新
/*
* 案例9:用户的修改操作
* 1.根据id修改数据
* 将password=123456 的phone 改为 "10086",email="10086@qq.com"
* API: userMapper.update(xxx, 条件构造器)
* 参数说明:
* 1.entity: 主要的目的是封装set条件
* 2.updateWrapper: 封装修改条件的条件构造器
* */
@Test
public void test9(){
User user = new User();
user.setPhone("10086").setEmail("10086@qq.com");
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("password", "123123");
int i = userMapper.update(user, updateWrapper);
System.out.println("修改成功,影响行数"+i);
}
八. 商品模块管理
8.1 查询3级商品分类信息
8.1.1 路由跳转页面
需求: 当用户点击商品分类时,应该跳转到商品分类页面中.
编辑路由文件
8.1.2 商品分类层级代码搭建
编辑POJO
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;
@Data
@Accessors(chain = true)
@TableName("item_cat")
public class ItemCat extends BasePojo{
@TableId(type = IdType.AUTO) //主键自增
private Integer id; //定义主键
private Integer parentId; //定义父级菜单 开启驼峰规则映射
private String name; //分类名称
private Boolean status; //分类状态 0 停用 1 正常
private Integer level; //商品分类等级 1 2 3
/*children是业务数据,并不是数据库字段*/
@TableField(exist = false) //标识当前属性不参与MP操作
private List<ItemCat> children;
}
8.1.2 搭建层级代码
8.1.4 商品分类业务说明(难点知识!!!)
表设计
8.1.5 数据结构设计
一级/二级一定有children/ 三级的children为null
@Data
@Accessors(chain = true)
@TableName("item_cat")
public class ItemCat extends BasePojo{
@TableId(type = IdType.AUTO) //主键自增
private Integer id; //定义主键
private Integer parentId; //定义父级菜单 开启驼峰规则映射
private String name; //分类名称
private Boolean status; //分类状态 0 停用 1 正常
private Integer level; //商品分类等级 1 2 3
/*children是业务数据,并不是数据库字段*/
@TableField(exist = false) //标识当前属性不参与MP操作
private List<ItemCat> children;
}
8.1.6 商品分类列表展现
8.1.6.1 业务接口说明
- 请求路径: /itemCat/findItemCatList/{level}
- 请求类型: get
- 请求参数: level
参数名称 | 参数说明 | 备注 |
---|---|---|
level | 查询级别 | 1查询一级分类 2查询1-2 级商品分类 3查询1-2-3级商品分类 |
- 业务说明: 查询3级分类菜单数据 要求三层结构嵌套
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 3级商品分类信息 |
8.1.6.2 页面URL说明
8.1.6.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;
/*
* 需求:查询三级商品分类信息
* URL:/itemCat/findItemCatList/{level}
* 参数: level 1/2/3
* 返回值: SysResult(三级菜单)
* */
@GetMapping("/findItemCatList/{level}")
public SysResult findItemCatList(@PathVariable Integer level){
List<ItemCat> itemCatList = itemCatService.findItemCatList(level);
return SysResult.success(itemCatList);
}
}
8.1.6.4 升级MPjar包
<!--spring整合mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
8.1.6.5 编辑ItemCatService
package com.jt.service;
import com.jt.pojo.ItemCat;
import java.util.List;
public interface ItemCatService {
List<ItemCat> findItemCatList(Integer level);
}
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;
/*
* 只查询一级菜单 parent_id=0或level=1
* 查询二级菜单 parent_id = 一级Id
* */
@Override
public List<ItemCat> findItemCatList(Integer level) {
long start = System.currentTimeMillis();
QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id", 0);
List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
for(ItemCat oneItemCat : oneList){
Integer oneId = oneItemCat.getId();
//清空多余的条件
queryWrapper.clear();
//查询二级数据
queryWrapper.eq("parent_id", oneId);
List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
//将二级封装到一级对象中
oneItemCat.setChildren(twoList);
for (ItemCat twoItemCat : twoList){
Integer twoId = twoItemCat.getId();
queryWrapper.clear();
queryWrapper.eq("parent_id",twoId);
List<ItemCat> threeList = itemCatMapper.selectList(queryWrapper);
twoItemCat.setChildren(threeList);
}
}
long end = System.currentTimeMillis();
System.out.println(end-start);
return oneList;
}
}
8.1.6.6 编辑ItemCatMapper
package com.jt.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jt.pojo.ItemCat;
import java.util.List;
public interface ItemCatMapper extends BaseMapper<ItemCat> {
}
8.1.6.7 页面效果展现
8.1.7 商品分类实现优化
8.1.7.1 优化策略
数据结构: Map<parentId, 当前父级下的子级>
例子:
Map<0, 所有的一级菜单>
Map<一级ID, 当前一级下的二级菜单>
Map<二级ID, 当前二级下的三级菜单>
设计的优势:
如果将数据保存到Map集合中,则可以有效的降低数据库的访问的次数. 提高查询效率.
8.1.7.2 封装Map集合
/**
* 思路:
* 1.判断map集合中是否存在key
* 2.如果key 不存在 准备一个新list集合,将自己作为第一个元素添加
* 3.如果key 存 在 获取list集合,将自己追加.
* @return
*/
public Map<Integer,List<ItemCat>> getMap(){
Map<Integer,List<ItemCat>> map = new HashMap<>();
//1.查询数据库的所有的记录
List<ItemCat> list = itemCatMapper.selectList(null);
for(ItemCat itemCat : list){
int key = itemCat.getParentId();
if(map.containsKey(key)){ //true 有数据
map.get(key).add(itemCat);
}else{ //false 没有数据
List<ItemCat> childrenList = new ArrayList<>();
childrenList.add(itemCat);
map.put(key,childrenList);
}
}
return map;
}
8.1.7.3 商品分类列表实现完整代码
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集合中是否存在key
* 2.如果不存在,准备一个新的list集合,将自己作为第一个元素添加
* 3.如果key存在,获取list集合,将自己追加进去;
*
* */
public Map<Integer,List<ItemCat>> getMap(){
Map<Integer,List<ItemCat>> map =new HashMap<>();
//1.查询数据库的所有记录
List<ItemCat> list = itemCatMapper.selectList(null);
for(ItemCat itemCat : list){ //true 有数据 将自己添加
int key= itemCat.getParentId();
if(map.containsKey(key)){
map.get(key).add(itemCat);
}else {//false 没有数据 准备新集合,添加数据
List<ItemCat> childrenList =new ArrayList<>();
childrenList.add(itemCat);
map.put(key, childrenList);
}
}
return map;
}
@Override
public List<ItemCat> findItemCatList(Integer level) {
//1.获取数据信息
Map<Integer,List<ItemCat>> map =getMap();
if(level==1){//值获取一级菜单数据
return map.get(0);
}
if(level==2){//获取一级和二级数据
return getTwoList(map);
}
return getThreeList(map);
}
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){
//说明该一级下里面没有二级的数据,所以跳过本次的循环,进入下一次
continue;
}
for(ItemCat twoItemCat : twoList){
//使用二级的Id查询三级数据
Integer key = twoItemCat.getId();
List<ItemCat> threeList = map.get(key);
twoItemCat.setChildren(threeList);
}
}
return oneList;
}
private List<ItemCat> getTwoList(Map<Integer, List<ItemCat>> map) {
List<ItemCat> oneList = map.get(0);
for(ItemCat oneItemCat : oneList){
//根据一级Id去查二级集合
Integer /*oneId*/key = oneItemCat.getId();//id map中的key
List<ItemCat> twoList = map.get(key);
//将二级数据封装到一级中
oneItemCat.setChildren(twoList);
}
return oneList;
}
/*
* 只查询一级菜单 parent_id=0或level=1
* 查询二级菜单 parent_id = 一级Id
* */
// @Override
// public List<ItemCat> findItemCatList(Integer level) {
// long start = System.currentTimeMillis();
// QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
// queryWrapper.eq("parent_id", 0);
// List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
// for(ItemCat oneItemCat : oneList){
// Integer oneId = oneItemCat.getId();
// //清空多余的条件
// queryWrapper.clear();
// //查询二级数据
// queryWrapper.eq("parent_id", oneId);
// List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
// //将二级封装到一级对象中
// oneItemCat.setChildren(twoList);
// for (ItemCat twoItemCat : twoList){
// Integer twoId = twoItemCat.getId();
// queryWrapper.clear();
// queryWrapper.eq("parent_id",twoId);
// List<ItemCat> threeList = itemCatMapper.selectList(queryWrapper);
// twoItemCat.setChildren(threeList);
// }
// }
// long end = System.currentTimeMillis();
// System.out.println(end-start);
// return oneList;
// }
}
8.1.7.4 递归代码
@Override
public List<ItemCat> findItemCatList(Integer level) {
Map<Integer, List<ItemCat>> map = getMap();
if(level==1) return map.get(0);
level--;
List<ItemCat> itemCatList = findItemCatList(level-);
for(ItemCat itemCat : itemCatList){
List<ItemCat> childrenList = itemCat.getChildren();
/*此处还可以分开用判定再优化,感兴趣的可以优化一下*/
if(childrenList==null){
Integer id = itemCat.getId();
itemCat.setChildren(map.get(id));
continue;
}
for(ItemCat childrenItemCat : childrenList){
Integer id = childrenItemCat.getId();
childrenItemCat.setChildren(map.get(id));
}
}
return itemCatList;
}
8.2 商品分类新增
8.2.1 业务接口说明
- 请求路径: /itemCat/saveItemCat
- 请求类型: post
- 请求参数: 表单数据
参数名称 | 参数说明 | 备注 |
---|---|---|
name | 商品分类名称 | 不能为null |
parentId | 用户父级ID | 不能为null |
level | 分类级别 | 1 2 3 商品分类级别 |
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
8.2.2 前端页面URL请求
1.页面请求
2.页面传递的JSON串
8.2.3 编辑ItemCatController
/**
* 业务需求:
* 实现商品分类新增操作
* url地址: /itemCat/saveItemCat
* 参数: JSON串
* 提交方式: post
* 返回值: SysResult对象
*/
@PostMapping("/saveItemCat")
public SysResult saveItemCat(@RequestBody ItemCat itemCat){
itemCatService.saveItemCat(itemCat);
return SysResult.success();
}
8.2.4 编辑ItemCatService
void saveItemCat(ItemCat itemCat);
/**
* 有些数据应该提前填充 status/创建时间/修改时间
* @param itemCat
*/
@Override
@Transactional //事务注解
public void saveItemCat(ItemCat itemCat) {
itemCat.setStatus(true)
.setCreated(new Date())
.setUpdated(itemCat.getCreated());
itemCatMapper.insert(itemCat);
}
8.3 数据自动填充
8.3.1 官网介绍
8.3.2 编辑自动填充项
//pojo基类,完成2个任务,2个日期,实现序列化
@Data
@Accessors(chain=true)
public class BasePojo implements Serializable{
//只有公共字段的属性参可以开启自动填充
@TableField(fill = FieldFill.INSERT)
private Date created; //表示入库时需要赋值
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updated; //表示入库/更新时赋值.
}
8.3.3 配置自动填充功能
说明: 在com.jt.config 中编辑配置类
//将该类交给Spring容器管理(this只在运行时候有效,指当前对象而不是当前类)
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@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) {
this.setFieldValByName("updated",new Date(),metaObject);
}
}
8.3.4 优化商品分类新增service层
@Override
@Transactional
public void saveItemCat(ItemCat itemCat) {
//已开启自动填充创建时间和更新时间
itemCat.setStatus(true);
itemCatMapper.insert(itemCat);
}
8.4 修改商品分类状态
8.4.1 业务接口说明
- 请求路径: /itemCat/status/{id}/{status}
- 请求类型: put
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
id | 用户ID值 | 不能为null |
status | 用户的状态信息 | 不能为null |
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
8.4.2 编辑ItemCatController
/*
* 需求:修改商品分类状态
* 请求路径: /itemCat/status/{id}/{status}
* 请求类型: put
* 请求参数: id/status
* 返回值: SysResult对象
* */
@PutMapping("/status/{id}/{status}")
public SysResult updateStatus(@PathVariable Integer id,@PathVariable Boolean status){
itemCatService.updateStatus(id,status);
return SysResult.success();
}
8.4.3 编辑ItemCatService
void updateStatus(Integer id, Boolean status);
@Override
@Transactional
public void updateStatus(Integer id, Boolean status) {
ItemCat itemCat = new ItemCat();
// itemCat.setStatus(status);
// UpdateWrapper<ItemCat> updateWrapper = new UpdateWrapper<>();
// updateWrapper.eq("id", id);
// itemCatMapper.update(itemCat, updateWrapper);
itemCat.setId(id).setStatus(status);
itemCatMapper.updateById(itemCat);
}
8.5 商品分类修改
8.5.1 业务接口说明
- 请求路径: /itemCat/updateItemCat
- 请求类型: put
- 请求参数: 表单数据 ItemCat对象
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
8.5.2 编辑ItemCatController
/*
* 需求:商品分类修改
* 请求路径: /itemCat/updateItemCat
* 请求类型: put
* 请求参数: 表单数据 ItemCat对象
* 返回值: SysResult对象
* */
@PutMapping("/updateItemCat")
public SysResult updateItemCat(@RequestBody ItemCat itemCat){
itemCatService.updateItemCat(itemCat);
return SysResult.success();
}
8.5.3 编辑ItemCatService
void updateItemCat(ItemCat itemCat);
@Override
@Transactional
public void updateItemCat(ItemCat itemCat) {
itemCatMapper.updateById(itemCat);
}
8.6 商品分类删除
8.6.1 业务说明
当用户删除商品分类的时候,应该将它的子级数据全部删除.
- 请求路径: /itemCat/deleteItemCat
- 请求类型: delete
- 业务描述: 当删除节点为父级时,应该删除自身和所有的子节点
- 请求参数:
参数名称 | 参数说明 | 备注 |
id | 分类ID号 | 不能为null |
level | 商品分类级别 一级,二级,三级 |
- 返回值结果 SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
8.6.2 编辑ItemCatController
/*
* 需求:商品分类删除
* 请求路径: /itemCat/deleteItemCat
* 请求类型: delete
* 请求参数: id/level
* 返回值: SysResult对象
* */
@DeleteMapping("/deleteItemCat")
public SysResult deleteItemCat(ItemCat itemCat){
itemCatService.deleteItemCat(itemCat);
return SysResult.success();
}
8.6.3 编辑ItemCatService
/*
* 删除思路:
* 1.判断是否为3级 如果是,直接删除
* 2.判断是否为2级 如果是2级,先删除3级,再删除2级
* 3.如果是1级,先查询2级,再删除3级/2级/1级
* */
@Override
public void deleteItemCat(ItemCat itemCat) {
if(itemCat.getLevel()==3){
itemCatMapper.deleteById(itemCat.getId());
return;
}
if (itemCat.getLevel()==2){
Integer twoId = itemCat.getId();
QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id",twoId);
itemCatMapper.delete(queryWrapper);
itemCatMapper.deleteById(twoId);
return;
}
/*
* 删除一级菜单
* 1.应该先查询二级id
* */
Integer oneId = itemCat.getId();
QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id", oneId);
/*返回值有可能是空集合,但不可能为null*/
List<Object> twoIdList =itemCatMapper.selectObjs(queryWrapper);
if(twoIdList.size()==0){
//如果没有二级数据,则直接删除一级信息
itemCatMapper.deleteById(oneId);
}else {
//表示有二级数据,如何快速删除1级2级3级数据(3级无需判断是否有值,大不了影响行数为0,sql语句不会报错)
//sql : delete from item_cat where parent_id in (x,x,x,x) or id in (x,x,x,x) or id = oneId;
queryWrapper.clear();
queryWrapper.in("parent_id", twoIdList).or().in("id", twoIdList).or().eq("id", oneId);
itemCatMapper.delete(queryWrapper);
}
}
8.7 商品列表展现
8.7.1 表设计
1.商品表设计(item)
2.商品详情表设计(item_desc)
表关系: 一个商品对应一个商品详情, item.id = item_desc.id 商品表的Id和详情表的ID是一致的
8.7.2 POJO设计
8.7.2.1 编辑Item类
package com.jt.pojo;
@Data
@Accessors(chain = true)
@TableName("item")
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 上架
}
8.7.2.2 编辑ItemDesc
package com.jt.pojo;
@Data
@Accessors(chain = true)
@TableName("/item_desc")
public class ItemDesc extends BasePojo{
@TableId //只标识主键即可
private Integer id; //item.id一致
private String itemDesc; //html代码片段
}
8.7.3 商品页面跳转
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../components/Login.vue'
import ElementUI from '../components/ElementUI.vue'
import Home from '../components/Home.vue'
import User from '../components/user/user.vue'
import ItemCat from '../components/items/ItemCat.vue'
import Item from '../components/items/Item.vue'
import AddItem from '../components/items/addItem.vue'
//使用路由机制
Vue.use(VueRouter)
const routes = [
{path: '/', redirect: '/login'},
{path: '/login', component: Login},
{path: '/elementUI', component: ElementUI},
{path: '/home', component: Home, children: [
{path: '/user', component: User},
{path: '/itemCat', component: ItemCat},
{path: '/item', component: Item},
{path: '/item/addItem', component: AddItem}
]},
]
8.7.4 商品列表展现
8.7.4.1 业务接口说明
- 请求路径: /item/getItemList?query=&pageNum=1&pageSize=10
- 请求类型: get
- 请求参数: 使用pageResult对象接收
参数名称 | 参数说明 | 备注信息 |
---|---|---|
query | 用户查询的数据 | 可以为null |
pageNum | 分页查询的页数 | 必须赋值不能为null |
pageSize | 分页查询的条数 | 必须赋值不能为null |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 商品分页对象 |
8.7.4.2 编辑ItemController
@CrossOrigin
@RestController
@RequestMapping("/item")
public class ItemController {
@Autowired
private ItemService itemService;
/*
* 需求:商品列表展现
* 请求路径: /item/getItemList?query=&pageNum=1&pageSize=10
* 请求类型: get
* 请求参数: 使用pageResult对象接收
* 返回值结果:SysResult(pageResult)
* */
@GetMapping("/getItemList")
public SysResult getItemList(PageResult pageResult){//3个参数
pageResult=itemService.getItemList(pageResult);//3+2
return SysResult.success(pageResult);
}
}
8.7.4.3 编辑ItemService
package com.jt.service;
import com.jt.vo.PageResult;
public interface ItemService {
PageResult getItemList(PageResult pageResult);
}
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.ItemDescMapper;
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.util.StringUtils;
import java.util.List;
@Service
public class ItemServiceImpl implements ItemService{
@Autowired
private ItemMapper itemMapper;
@Autowired
private ItemDescMapper itemDescMapper;
/*
* sql : select * from item where 条件 limit 起始位置,显示条数
* 参数说明:
* 1.page:用来封装分页参数 第几页/每页多少条
* 2.queryWrapper 查询的条件构造器
* */
@Override
public PageResult getItemList(PageResult pageResult) {
//构建模糊查询
QueryWrapper<Item> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StringUtils.hasLength(pageResult.getQuery()),"title", pageResult.getQuery());
//2定义分页对象
// IPage<Item> page = new Page<>(pageResult.getPageNum(), pageResult.getPageSize());
/*用这个也可以,Page<>实现了IPage<>接口*/
Page<Item> page = new Page<>(pageResult.getPageNum(), pageResult.getPageSize());
//page的参数由原来的页数/条数,经过业务添加了 总记录数和分页的结果
page = itemMapper.selectPage(page, queryWrapper); //2+2
long total = page.getTotal(); //获取总数
List<Item> rows = page.getRecords(); //获取分页结果
return pageResult.setTotal(total).setRows(rows);
}
}
8.7.4.4 编辑分页配置类
说明: MybatisPlus可以实现跨数据库. 用户操作的是对象,但是由MP动态的生成对应的Sql语句.
如果需要使用分页,则需要额外的指定数据库版本. 需要编辑配置类.
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 {
//将自定义对象交给Spring容器管理,告诉MP,使用的是MySql和MariaDB数据库
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
8.8 商品新增实现
8.8.1 关于商品信息说明
说明: item的基本信息与ItemDesc的详情信息 可以采用对象的方式进行包裹.
例如: {item: item的数据信息, itemDesc: itemDesc的数据信息}
8.8.2 关于商品新增接口说明
- 请求路径: 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"....... "
}
}
- 请求参数: 使用ItemVO对象接收
参数名称 | 参数类型 | 参数说明 | 备注 |
---|---|---|---|
item | Item | 商品基本信息对象封装 | 不能为null |
itemDesc | ItemDesc | 商品详情信息 | 不能为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语句 |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
8.8.3 封装ItemVO对象
package com.jt.pojo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Accessors(chain = true)
public class ItemVO implements Serializable {
private Item item;
private ItemDesc itemDesc;
}
8.8.4 修改表字段类型
说明: 文本操作需要大量的存储空间,所以改为mediumtext
8.8.5 页面参数传递
8.5.6 编辑ItemCatController
/*
* 商品新增业务接口
* 请求路径: http://localhost:8091/item/saveItem
* 请求类型: post
* 前端传递参数分析 ItemVO
* */
@PostMapping("/saveItem")
public SysResult saveItem(@RequestBody ItemVO itemVO){//Item/ItemDesc
itemService.saveItem(itemVO);
return SysResult.success();
}
8.8.7 编辑ItemCatService
void saveItem(ItemVO itemVO);
/*
* 需求:完成两部分的入库操作
* 步骤一:完成Item入库操作
* 步骤二:完成ItemDesc入库操作Item.id=ItemDesc.id
* mybatis 知识讲解:
* xml映射文件中配置主键或者数据的回显
* <insert id="xxxx" useGeneratedKeys="true"
* keyColumn="id"
* keyProperty="id">
* 新增sql
* </insert>
* MP的知识点讲解:
* MP基于对象的方式操作数据,如果实现了数据的入库操作
* 则数据库里的数据会与对象进行绑定,动态回显
* 难点知识:如何实现数据的回显!!!!
* */
@Override
@Transactional
public void saveItem(ItemVO itemVO) {
//1.实现Item对象的入库
Item item = itemVO.getItem().setStatus(true);
//主键自增:刚开始id为空,但入库之后,id在数据库中会自动赋值
//那么赋值之后,对象中的id是否有值? 没有
itemMapper.insert(item);
//2.实现ItemDesc对象入库
ItemDesc itemDesc = itemVO.getItemDesc();
itemDesc.setId(item.getId());
itemDescMapper.insert(itemDesc);
}
8.9 商品删除操作
8.9.1 商品删除业务接口
- 请求路径: /item/deleteItemById
- 请求类型: delete
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
id | 商品id | 不能为null |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
8.9.2 编辑ItemController
/*
* 商品数据删除
* 请求路径: /item/deleteItemById
* 请求类型: delete
* 请求参数:id
* 返回值结果:SysResult
* */
@DeleteMapping("/deleteItemById")
public SysResult deleteItemById(Integer id){
itemService.deleteItemById(id);
return SysResult.success();
}
8.9.3 编辑ItemService
void deleteItemById(Integer id);
@Override
@Transactional
public void deleteItemById(Integer id) {
itemMapper.deleteById(id);
itemDescMapper.deleteById(id);
}
8.10 商品状态修改
8.10.1 商品删除业务接口
- 请求路径: /item/updateItemStatus
- 请求类型: put
- 请求参数: 使用对象接收
参数名称 | 参数说明 | 备注 |
---|---|---|
id | 商品id | 不能为null |
status | 状态信息 | 不能为null |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
8.10.2 编辑ItemController
/*
* 商品状态修改
* 请求路径: /item/updateItemStatus
* 请求参数: 使用对象接收 /id/status
* 返回值结果: SysResult
* */
@PutMapping("/updateItemStatus")
public SysResult updateItemStatus(@RequestBody Item item){
itemService.updateItemStatus(item);
return SysResult.success();
}
8.10.3 编辑ItemService
void updateItemStatus(Item item);
@Override
@Transactional
public void updateItemStatus(Item item) {
itemMapper.updateById(item);
}
8.11 文件上传操作
8.11.1 文件上传业务说明
说明:当用户选择多张图片时,则是一张一张的传输.
- 请求路径: 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 |
8.11.2 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; //图片名称
}
8.11.3 文件上传入门案例
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;
import java.io.File;
import java.io.IOException;
@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
/*
* URL:/file/upload
* 请求类型: post
* 请求参数:file
* 返回值:SysResult(ImageVo)
* 知识点:
* SpringMVC针对与IO操作开发了MultipartFile
* 底层实现就是常规的IO流,简化了用户的操作流程,无需手动关流
* SpringMVC中默认支持大小是1M
* 步骤:
* 1.获取文件名
* 2.准备文件路径
* 3.准备文件上传的全路径(1+2)
* 4.实现文件上传的操作
* */
@PostMapping("/upload")
public SysResult upload(MultipartFile file) throws IOException {//file 名不能乱写,要与传过来的变量名对应
//获取文件名称 a.jpg
String filename = file.getOriginalFilename();
//准备文件的目录
String dir = "E:/StudyPractise/images";
File dirFile = new File(dir);
if(!dirFile.exists()){//判断目录是否存在
dirFile.mkdirs(); //创建多级目录
}
String path = dir+"/"+filename;
//实现文件的上传
file.transferTo(new File(path));//此处异常属于检查异常
return SysResult.success();
}
}
8.11.4 文件上传完整代码
8.11.4.1 编辑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;
import java.io.File;
import java.io.IOException;
@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
@Autowired
private FileService fileService;
@PostMapping("/upload")
public SysResult upload(MultipartFile file){
ImageVO imageVO = fileService.upload(file);
if(imageVO==null){
return SysResult.fail();
}
return SysResult.success(imageVO);
}
}
8.11.4.2 编辑FileService
package com.jt.service;
import com.jt.vo.ImageVO;
import org.springframework.web.multipart.MultipartFile;
public interface FileService {
ImageVO upload(MultipartFile file);
}
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{
//定义文件根目录
private String rootDir="E:/StudyPractise/images";
//定义图片网络服务器地址
private String rootURL="http://image.jt.com";
/*
* 思路:
* 1.校验文件是否为图片
* 2.防止文件为恶意程序 木马.exe.jpg
* 3.分目录存储 按照时间维度划分
* 4.防止文件重名 UUID
* */
@Override
public ImageVO upload(MultipartFile file) {
//a.jpg jpg/png/gif
//获取图片名称全部转小写(windows系统中不区分大小写,所以当用户传的是A.JPG时就会出现Bug(文件正确却传不了),所以得转小写)
String filename = file.getOriginalFilename().toLowerCase();
if(!filename.matches("^.+\\.(jpg|png|gif)$")){
return null;
}
//2.通过校验宽度和高度判断是否为图片 bufferedImage代表图片的包装对象
try {
BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
if(width==0 || height==0){
return null;
}
//实现分目录的存储
String datePath = new SimpleDateFormat("/yyyy/MM/dd/").format(new Date());
//E:/StudyPractise/images/2025/12/12/
String fileDir = rootDir + datePath;
File dirFile = new File(fileDir);
if(!dirFile.exists()){
dirFile.mkdirs();
}
//4.动态生成UUID
String uuid = UUID.randomUUID().toString();
int index = filename.lastIndexOf(".");
String type = filename.substring(index);
filename=uuid+type;
//5.实现文件上传 准备文件的全路径
String path = fileDir + filename;
file.transferTo(new File(path));
//6.准备ImageVO对象并返回
// /2025/12/12/uuid.png
String virtualPath = datePath+filename;
// String urlPath = "https://gitee.com/mr-sun-drawing-bed/gallery/raw/master/study/tedu/cgb/Phase3/a.png";
//http://image.jt.com/2021/11/11/uuid.png
String urlPath = rootURL+virtualPath;
System.out.println(urlPath);
ImageVO imageVO = new ImageVO(virtualPath, urlPath, filename);
return imageVO;
} catch (IOException e) {
/*
* 一般的条件下,为了不影响代码得结构,将检查异常转化为运行时异常 throw new RuntimeException(e);
*
* 解释 : 如果直接向上抛出,会在接口和controller的方法上添加异常改变了代码结构
* 如果直接tryCatch,只有后端捕获异常,不去上报,没人知道,所以转成运行时异常由aop捕获,再由AOP返回SysResult.fail();
* tryCatch一般获取的是检查异常,会一直向上抛出,导致就是谁调用谁处理.
* */
e.printStackTrace();
throw new RuntimeException(e);
}
}
//删除文件 需要文件的全路径
@Override
public void delete(String virtualPath) {
String localPtah=rootDir+virtualPath;
File file = new File(localPtah);
if(file.exists()){
file.delete();
}
}
}
8.12 文件删除操作
8.12.1 业务接口说明
- 请求路径: http://localhost:8091/file/deleteFile
- 请求类型: delete
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
virtualPath | 文件上传的虚拟的路径 | 删除时需要磁盘路径一起删除 |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
前端请求参数说明:
8.12.2 编辑FileController
/**
* 完成图片的删除操作
* URL: http://localhost:8091/file/deleteFile
* 参数: virtualPath
* 返回值: SysResult对象
*/
@DeleteMapping("/deleteFile")
public SysResult deleteFile(String virtualPath){
fileService.deleteFile(virtualPath);
return SysResult.success();
}
8.12.3 编辑FileService
//删除文件 需要文件全路径
@Override
public void deleteFile(String virtualPath) {
String localPath = rootDir + virtualPath;
File file = new File(localPath);
if(file.exists()){ //如果文件存在,则删除
file.delete();
}
}
九. 正反向代理
9.1 业务需求
需求说明: 当用户完成图片上传之后 会根据网络地址访问图片,但是查找的图片一定存在于磁盘中.
URL地址: http://image.jt.com/2021/12/16/f9981c76-e6a7-49fa-88d9-3be0851dbf50.jpg
磁盘地址: D:/project3/images/2021/12/16/f9981c76-e6a7-49fa-88d9-3be0851dbf50.jpg
技术难点:
1.当用户访问URL网络地址时,应该按照磁盘地址进行查找!
如何将网络地址转化为磁盘地址?
9.2 反向代理
反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时,用户不需要知道目标服务器的地址,也无须在用户端作任何设定。反向代理服务器通常可用来作为Web加速,即使用反向代理作为Web服务器的前置机来降低网络和服务器的负载,提高访问效率。
3.2.1 知识铺垫
如果网站将真实地址直接告知用户.导致整个服务器不安全. 所以需要采用代理的技术规避该问题.
3.2.2 反向代理特点
- 反向代理服务器介于用户和目标服务器之间
- 用户从反向代理服务器获取资源, 用户以为反向代理服务器就是目标服务器.
- 用户不清楚真实的服务器到底是谁, 保护了真实服务器的信息. 也称之为 "服务器端代理"
9.3 正向代理
3.3.1 正向代理介绍
正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。
3.3.2 正向代理特点
- 正向代理服务器介于客户端和原始服务器之间.
- 用户访问正向代理服务器,并且指定目标服务器地址.
- 服务器端不清楚到底是谁访问的,以为是正向代理服务器访问的. 保护了用户信息. 也称之为客户端代理.
9.4 nginx
9.4.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网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。
9.4.2 Nginx特点
- nginx是一块开源免费的 轻量级的反向代理服务器/web服务器.
- nginx并发能力强 理论值: 5万次/秒 实际值: 3万次/秒
tomcat: 150-220 —>1000个/秒 内存占用200M - nginx占用内存少 不超过2M
9.4.3 Nginx下载
URL地址: http://nginx.org/en/download.html
9.4.4 Nginx 安装和使用
说明:
1.nginx启动会占用80端口!!
2.nginx启动路径不要有中文/空格/特殊字符 底层开发语言:C语言
访问测试:
9.4.5 关于nginx 80端占用问题说明
- 检查端口号占用
- 打开任务管理器
- 关于80端口 被PID=4占用说明
9.4.6 关于nginx 启动项说明
说明: nginx的启动每次都会启动2个进程项.
主进程: 主要提供反向代理服务. 占用内存大的
守护进程: 防止主进程意外关闭. 占用内存小的
9.4.7 nginx 命令(熟练掌握)
说明: nginx的命令需要在根目录中运行 nginx.exe 所在的路径就是根目录
命令:
- 启动nginx start nginx
- 重启nginx nginx -s reload
- 关闭nginx nginx -s stop
9.5 Nginx反向代理机制
http {
#每个反向代理服务,就是一个server
server {
#nginx默认监听端口号 默认都是80
listen 80;
#nginx要拦截的域名
server_name localhost;
#拦截所有的请求
location / {
# root 代表代理的是一个目录
root html;
# 配置默认访问的首页
index index.html index.htm;
}
}
}
9.5.1 Nginx实现图片代理
URL地址: http://image.jt.com/2021/12/16/f9981c76-e6a7-49fa-88d9-3be0851dbf50.jpg
磁盘地址: D:/project3/images/2021/12/16/f9981c76-e6a7-49fa-88d9-3be0851dbf50.jpg
代理机制:
域名: http://image.jt.com:80
代理为:
E:/StudyPractise/images
配置图片代理:
#配置图片代理
server {
#监听端口号
listen 80;
#服务名称
server_name image.jt.com;
#
location / {
#根目录
root E:/StudyPractise/images;
}
}
编辑完成之后,重启nginx
9.5.2 图片回显流程图
9.5.3 修改hosts文件
文件位置: C:\Windows\System32\drivers\etc
9.5.4 修改hosts文件
127.0.0.1 localhost
127.0.0.1 image.jt.com
127.0.0.1 manage.jt.com
127.0.0.1 web.jt.com
十.Windows 项目发布
10.1 项目发布检查
1.检查所有的sql中的表名是否存在大小写问题. 检查注解 检查Sql
2.根据码云 检查POM.xml文件内容
3.将项目打包 install 命令
4.前端项目打包
检查是否生成dist目录
)
10.2 前端发布准备
- 修改main.js的路径
- 编辑AddItem.vue文件
- 将前端项目进行编译
如果将上述的操作修改完成,之后需要将程序编译。如图所示
10.3 前端项目发布
10.3.1 业务说明
将编译之后的dist目录 复制到nginx的根目录中。
10.3.2 前端项目发布
需求: 用户通过http://web.jt.com 访问 dist/index.html
#配置前端代理
server {
listen 80;
server_name web.jt.com;
location / {
root dist;
index index.html;
}
}
重启nginx
10.3.3 前端代码测试
10.4 后端项目发布
10.4.1 动态获取端口号
package com.jt.controller;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@CrossOrigin
public class PortController {
//动态获取端口号
@Value("${server.port}")
private Integer port;
@GetMapping("/getPort")
public String getPort(){
return "当前端口号:"+port;
}
}
10.4.2 项目发布
命令: java -jar 8091.jar
问题说明: 如果启动失败 检查pom.xml文件 或者使用我的jar包
10.4.3 nginx完成后端发布
#配置后端代理
server {
listen 80;
server_name manage.jt.com;
location / {
#proxy_pass 映射的是请求的地址
proxy_pass http://localhost:8091;
}
}
10.4.4 后端项目测试
通过:http://manage.jt.com/itemCat/findItemCatList/3 测试后端域名是否可用
10.5 集群部署
10.5.1 集群部署流程图
10.5.2 部署集群
说明: 分别准备3台tomcat服务器 端口号分别为8091/8092/8093
10.5.3 启动多个进程
说明: 勾选其中的选项 ,可以平行运行多个进程
启动3台服务器 完成测试。
10.5.4 部署集群
#配置后端代理
server {
listen 80;
server_name manage.jt.com;
location / {
#proxy_pass 映射的是请求的地址
#proxy_pass http://localhost:8091;
#访问集群
proxy_pass http://tomcats;
}
}
#配置tomcat服务器集群 1.轮询策略
upstream tomcats {
server 127.0.0.1:8091;
server 127.0.0.1:8092;
server 127.0.0.1:8093;
}
10.6 nginx负载均衡策略
10.6.1 轮询策略
说明: 按照配置文件的顺序 依次访问
#配置tomcat服务器集群 1.轮询策略
upstream tomcats {
server 127.0.0.1:8091;
server 127.0.0.1:8092;
server 127.0.0.1:8093;
}
10.6.2 权重策略
说明: 可以根据服务器性能,灵活的设置配比
#配置tomcat服务器集群 1.轮询策略 2.权重策略
upstream tomcats {
server 127.0.0.1:8091 weight=6;
server 127.0.0.1:8092 weight=3;
server 127.0.0.1:8093 weight=1;
}
10.6.3 IP_HASH策略
说明: 如果需要让用户与服务器进行绑定. 则使用IPHASH
#配置tomcat服务器集群 1.轮询策略 2.权重策略 3.iphash策略
upstream tomcats {
ip_hash;
server 127.0.0.1:8091 weight=6;
server 127.0.0.1:8092 weight=3;
server 127.0.0.1:8093 weight=1;
}
10.6.4 nginx常见属性
-
down属性 如果down属性标识了服务器,则nginx不会再次访问该服务器
-
backup属性 设置备用机, 正常情况下,备用机不会被访问,但是当主机遇忙时/或者宕机时,备用机才会被访问.
#配置tomcat服务器集群 1.轮询策略 2.权重策略 3.iphash策略
upstream tomcats {
#ip_hash;
server 127.0.0.1:8091 weight=6 down;
server 127.0.0.1:8092 weight=3 down;
server 127.0.0.1:8093 weight=1 backup;
}
10.6.5 项目如何实现在线部署
说明:
1.首先需要制定上线计划 分批次上线部署.
2.首先可以先将一部分服务器down掉,之后替换新的jar包,重启服务器,测试通过之后,nginx正式接通.
3.重复执行多次,直至项目部署完成为止.
nginx启动的速度很快,所以几乎对用户没有影响.
十一. Linux项目发布
11.1 安装JDK
11.1.1 上传安装包
说明: 将windows中的tar.gz的文件上传到Linux系统中,如图所示
11.1.2 解压JDK
命令: tar -xvf jdk-8u51-linux-x64.tar.gz
删除/修改文件名称:
11.1.3 检查JDK是否有效
11.1.4 编辑JDK 环境配置
编辑:linux 环境配置 vim /etc/profile
环境变量生效:
- 重启Linux服务器。
- 刷新环境变量 source /etc/profile
#设定jdk环境
export JAVA_HOME=/usr/local/src/jdk1.8
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib
11.2. Linux 安装mariadb数据库
11.2.1 项目发布流程
11.2.2 Linux安装MariaDB数据库
11.2.3 导入数据库
11.2.4 Linux项目发布指南
- 修改文件上传的目录
- 检查Linux mysql密码是否正确
11.3 tomcat服务器发布
11.3.1 项目打包
说明:Linux项目部署准备2台tomcat服务器, 分别是8091/8092
11.3.2 jar包文件上传
上传jar包文件
11.3.3 启动项目
11.3.3.1 前端运行
命令: java -jar 8091.jar
测试启动是否正常
说明:通过IP地址+端口+测试路径 判断后端服务器发布是否正常
前端运行的弊端:
- 如果采用上述的指令,则当前的终端与tomcat服务器绑定。如果终端关闭或者退出。tomcat服务器也会关闭
- 可以通过末尾添加 &号的方式 缓解终端与tomcat绑定的问题。但是不能解决
11.3.3.2 后端运行
命令: nohup java -jar 8091.jar => 8091.log &
命令说明: 将运行的方式改为后端运行,所有的日志输出端到8091.log日志中。通过cat 8091.log 检查日志
检索java进程命令:jps
关闭进程:
- kill PID号 普通的关闭
- kill -15 PID号 较为强硬的关闭
- kill -9 PID号 强制关闭
11.3.3.3 脚本运行(了解)
说明:Linux中常见的脚本 shell脚本 以xxx.sh结尾
编辑脚本: vim start.sh
启动脚本: sh start.sh
11.4 Nginx安装步骤
11.4.1 官网介绍
http://nginx.org/en/download.html
11.4.2 上传安装包
上传到指定目录中 /usr/local/src
11.4.3 解压Nginx 压缩文件
1.删除安装文件
rm -f nginx-1.21.3.tar.gz
2.修改文件名称
mv nginx-1.19.4 nginx-source
改完名称之后的结果:
11.4.5 安装nginx服务器
说明:在nginx-source的根目录中执行如下命令
需要提前安装nginx的依赖项
yum -y install gcc pcre-devel zlib-devel openssl openssl-devel
1./configure
直接结果:
问题补充: 如果 ./configure 报错没有权限,则执行如下命令
chmod + x configure
- 在nginx-source的根目录中 执行 make
3.根目录中执行 make install
11.4.6 Nginx工作目录说明
说明: 查找工作目录
路径: whereis nginx
11.4.7 跳转到Nginx工作目录
11.4.8 nginx命令
进入nginx/sbin目录中执行
1.启动命令: ./nginx
2.重启命令: ./nginx -s reload
3.关闭命令: ./nginx -s stop
11.5上传前端静态资源
说明: 将前端的web资源上传到nginx的根目录下
- 前端文件位置
2.上传目录位置
11.6 修改nginx配置文件
需求说明:
1.实现图片反向代理
2.前端业务代理
3.后端tomcat集群代理
#配置图片代理 image.jt.com
server {
listen 80;
server_name image.jt.com;
location / {
root /usr/local/src/images;
}
}
#配置前端代理 www.jt.com
server {
listen 80;
server_name web.jt.com;
location / {
root dist;
index index.html;
}
}
#配置后台服务器集群
upstream tomcats{
server 192.168.126.129:8091;
server 192.168.126.129:8092;
}
#配置后端代理 manage.jt.com
server {
listen 80;
server_name manage.jt.com;
location / {
proxy_pass http://tomcats;
}
}
编辑成功之后,将nginx.conf文件上传到Linux系统中(删除原有文件),
上传之后重启nginx服务:
11.7 修改hosts文件
# 127.0.0.1 image.jt.com
# 127.0.0.1 manage.jt.com
# 127.0.0.1 web.jt.com
192.168.126.129 image.jt.com
192.168.126.129 manage.jt.com
192.168.126.129 web.jt.com
11.8 项目发布测试
自行测试相关功能: 登录,上传图片,图片回显,上传路径等