📢📢📢📣📣📣
哈喽!大家好,我是一位上进心十足,拥有极强学习力的在校大学生😜😜😜
我所写的博客的领域是面向后端技术的学习,未来会持续更新更多的【后端技术】以及【学习心得】。 偶尔会分享些前端基础知识,会更新实战项目,以及开发应用!
❤️❤️❤️ 感谢各位大可爱小可爱! ❤️❤️❤️
一、前言
一对一,一对多以及我们的多对多数据库关联查询在我们写项目的时候是经常会用到的。例如我们的宿舍管理系统,在进行寝室管理的时候,会给我们的楼宇分配我们的宿舍管理员,这个管理员的数据当然不能是一个死数据,那我们怎么办呢?我们可以把用户进行分类,分为管理员和普通用户,然后让管理员的数据出现在我们分配人员的名单里面,这是根据身份和权限来进行查询的。
但是在我们进行项目的时候,并不可能每一个表格都会设置身份和权限,也不能每一个项目都适合这种查询方式。像我们的商城,我们在订单详情页里面不可能加上所以的数据,我们也只能是从其他表里面查询数据给另一个表格使用。
那么下面,我就拿我的2个项目来进行举例,分别是宿舍管理系统和我们的在线商城。
二、技术栈
-
前端: Vue,axios
-
后端 : SpringBoot, mybatis,mybatis-plus,JWT,Swagger
-
开发环境: IDEA 、SpringBoot 2.5.9、Maven
-
数据库:MySQL 8.0
三、宿舍管理系统
1.功能展示
我们在添加用户之后,对楼宇进行管理员分配,人员身份来自于我们的管理员,我们在进行卫生管理的时候,会选择我们的楼宇,楼宇数据来自我们的楼栋管理的数据。
2.数据库SQL
(1).User表:
CREATE TABLE `sys_user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '用户id',
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci DEFAULT NULL COMMENT '用户名',
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci DEFAULT NULL COMMENT '昵称',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci DEFAULT NULL COMMENT '密码',
`sex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci DEFAULT '男' COMMENT '性别',
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci DEFAULT NULL COMMENT '地址',
`phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci DEFAULT NULL COMMENT '电话',
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci DEFAULT NULL COMMENT '邮箱',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '用户注册的时间',
`role` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci DEFAULT 'USER' COMMENT '身份,1为管理员,USER为普通用户',
`avatar_url` varchar(255) COLLATE utf8mb4_turkish_ci DEFAULT NULL COMMENT '头像',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_turkish_ci COMMENT='用户列表';
(2).Sanitation表
CREATE TABLE `sanitation` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '楼栋名称',
`room_num` int DEFAULT NULL COMMENT '房间号',
`grade` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '等级',
`user` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '检查人员',
`content` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '具体描述',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
(3).Role表
CREATE TABLE `role` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
`role` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '角色',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
3.后台Java
(1).User.Java
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
*
* </p>
*
* @author 小肖
* @since 2022-12-02
*/
@Getter
@Setter
@TableName("sys_user")
@ApiModel(value = "User对象", description = "")
@ToString
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("ID")
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("性别")
private String sex;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("昵称")
private String nickname;
@ApiModelProperty("邮箱")
private String email;
@ApiModelProperty("联系方式")
private String phone;
@ApiModelProperty("地址")
private String address;
@ApiModelProperty("创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@ApiModelProperty("角色")
private String role;
@ApiModelProperty("头像")
private String avatarUrl;
}
其他的几个表跟User表一样,但是可以把
@Getter
@Setter
@TableName("sys_user")
@ApiModel(value = "User对象", description = "")
@ToString
改成这样
@Data
@TableName("build")
其中,@TableName里面跟的是你的数据库表面,是为了更精确的找到你数据库该表的位置
(2).UserController.Java
最主要的就是我们的role接口 ,我们是根据用户的身份来进行一对一查询的,前提是必须对用户进行身份确定
@GetMapping("/role/{role}")
public Result findNames(@PathVariable String role) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("role", role);
List<User> list = userService.list(queryWrapper);
return Result.success(list);
}
完整代码
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.common.Constants;
import com.example.demo.common.Result;
import com.example.demo.controller.dto.UserDTO;
import com.example.demo.entity.Build;
import com.example.demo.entity.User;
import com.example.demo.service.IUserService;
import com.example.demo.utils.TokenUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.List;
@CrossOrigin
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private IUserService userService;
@PostMapping("/login")
public Result login(@RequestBody UserDTO userDTO) {
String username = userDTO.getUsername();
String password = userDTO.getPassword();
if (StrUtil.isBlank(username) || StrUtil.isBlank(password)) {
return Result.error(Constants.CODE_400,"参数错误");
}
UserDTO dto = userService.login(userDTO);
return Result.success(dto);
}
@PostMapping("/register")
public Result register(@RequestBody UserDTO userDTO) {
String username = userDTO.getUsername();
String password = userDTO.getPassword();
if (StrUtil.isBlank(username) || StrUtil.isBlank(password)) {
return Result.error(Constants.CODE_400,"参数错误");
}
userDTO.setNickname(userDTO.getUsername());
return Result.success(userService.register(userDTO));
}
//新增或者更新
@PostMapping
public Result save(@RequestBody User user) {
String username = user.getUsername();
if (StrUtil.isBlank(username)) {
return Result.error(Constants.CODE_400, "参数错误");
}
if (user.getId() != null) {
user.setPassword(null);
} else {
user.setNickname(user.getUsername());
if (user.getPassword() == null) {
user.setPassword("123456");
}
}
return Result.success(userService.saveOrUpdate(user));
}
//删除
// @DeleteMapping("/{id}")
// public Result delete(@PathVariable Integer id) {
// return Result.success(userService.removeById(id));
// }
@PostMapping("/del/batch")
public Result deleteBatch(@RequestBody List<Integer> ids) {//批量删除
return Result.success(userService.removeByIds(ids));
}
//查询所有数据
@GetMapping
public Result findAll() {
return Result.success(userService.list());
}
@GetMapping("/role/{role}")
public Result findNames(@PathVariable String role) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("role", role);
List<User> list = userService.list(queryWrapper);
return Result.success(list);
}
@GetMapping("/{id}")
public Result findOne(@PathVariable Integer id) {
return Result.success(userService.getById(id));
}
@GetMapping("/username/{username}")
public Result findByUsername(@PathVariable String username) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
return Result.success(userService.getOne(queryWrapper));
}
@GetMapping("/page")
public Result findPage(@RequestParam Integer pageNum,
@RequestParam Integer pageSize,
@RequestParam(defaultValue = "") String username) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");
if (!"".equals(username)) {
queryWrapper.like("username", username);
}
return Result.success(userService.page(new Page<>(pageNum, pageSize), queryWrapper));
}
/**
* 导出接口
*/
@GetMapping("/export")
public void export(HttpServletResponse response) throws Exception {
//从数据库查询出所有的数据
List<User> list = userService.list();
//通过工具类创建writer 写出到磁盘路径
//ExcelWriter writer = ExcelUtil.getWriter(filesUploadPath + "/用户信息.xlsx");
//在内存操作,写出到浏览器
ExcelWriter writer = ExcelUtil.getWriter(true);
//自定义标题别名
writer.addHeaderAlias("username", "用户名");
writer.addHeaderAlias("password", "密码");
writer.addHeaderAlias("nickname", "昵称");
writer.addHeaderAlias("sex", "性别");
writer.addHeaderAlias("phone", "联系方式");
writer.addHeaderAlias("address", "地址");
writer.addHeaderAlias("email", "邮箱");
writer.addHeaderAlias("createTime", "创建时间");
writer.addHeaderAlias("avatarUrl", "头像");
writer.addHeaderAlias("role", "身份");
//一次性写出list类的对象到excel,使用默认样式,强制输出标题
writer.write(list, true);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
String fileName = URLEncoder.encode("用户详情","UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName +".xlsx");
ServletOutputStream out = response.getOutputStream();
writer.flush(out, true);
out.close();
writer.close();
}
/**
* excel导入
* @param
* @throws Exception
*/
@PostMapping("/import")
public Result imp(MultipartFile file) throws Exception {
InputStream inputStream = file.getInputStream();
ExcelReader reader = ExcelUtil.getReader(inputStream);
//通过JavaBean的方式读取Excel内的对象,但要求表头必须是英文,跟JavaBean的属性要对应
List<User> list = reader.readAll(User.class);
userService.saveBatch(list);
return Result.success(true);
}
}
4.前台调用接口
methods: {
load: function () {
this.request.get("/sanitation/page", {
params: {
pageNum: this.pageNum,
pageSize: this.pageSize,
name: this.name,
}
}).then(res => {
this.tableData = res.data.records
this.total = res.data.total
})
this.request.get("/user/role/ADMIN").then(res => {
this.users = res.data
})
},
}
我们在前台调用后台UserController里面的/user/role接口,就能查询出User表中身份是ADMIN的用户,然后就可以进行数据调用存放到卫生管理后台了。
四、在线商城
1.功能展示
我们在进行加入购物车之后,会获取到商品的数据以及用户的数据,当然,我们的数据很多,完全不可能一一的填入到我们的数据库表格里面。我们只能进行一对一查询,然后实现数据迁移,在我们进行结算的时候,把我们的数据带到我们的订单里面去,然后进行渲染处理。
2.数据库SQL
(1).Cart表
CREATE TABLE `cart` (
`id` int NOT NULL AUTO_INCREMENT,
`goods_id` int DEFAULT NULL COMMENT '商品id',
`user_id` int DEFAULT NULL COMMENT '用户id',
`number` int DEFAULT NULL COMMENT '商品数量',
`creat_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `goods_user_uni` (`user_id`,`goods_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=88 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_turkish_ci;
(2).Orders表
CREATE TABLE `orders` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int DEFAULT NULL COMMENT '用户ID',
`alipay_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci DEFAULT NULL COMMENT '支付宝流水号',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci DEFAULT NULL COMMENT '名称',
`no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci DEFAULT NULL COMMENT '编号',
`total_price` decimal(10,2) DEFAULT NULL COMMENT '总金额',
`state` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci DEFAULT '待付款' COMMENT '状态',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '订单创建时间',
`pay_time` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci DEFAULT NULL COMMENT '订单付款时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=71 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_turkish_ci;
3后台Java
(1).Cart.Java
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 com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.models.auth.In;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@TableName("cart")
public class Cart {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private Integer userId;
private Integer goodsId;
private Integer number;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime creatTime;
//商品
@TableField(exist = false)
private String goodsName;
@TableField(exist = false)
private String goodsUrl;
@TableField(exist = false)
private BigDecimal price;
//用户
@TableField(exist = false)
private String username;
@TableField(exist = false)
private String nickname;
}
(2).Orders.Java
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 java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
@Data
@TableName("orders")
public class Orders {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private Integer userId;
private String alipayNo;
private String name;
private String no;
private BigDecimal totalPrice;
private String state;
private Date createTime;
private String payTime;
@TableField(exist = false)
private String username;
@TableField(exist = false)
private String nickname;
@TableField(exist = false)
private String phone;
@TableField(exist = false)
private String address;
@TableField(exist = false)
private List<Cart>
好了,到我们的重点了,我们需要进行数据库查询,然后将其他数据库里面的数据迁移过来,实现数据关联。我们这里就选择了用xml文件来处理,也就是mybatis-plus或者是mybatis来进行数据一对一关联。
(3).CartMapper.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.example.demo.mapper.CartMapper">
<select id="page" resultType="com.example.demo.entity.Cart">
select c.*, g.name as goodsName, g.url as goodsUrl, g.price, u.username, u.nickname from cart c
left join goods g on c.goods_id = g.id
left join sys_user u on c.user_id = u.id
<where>
<if test="name != null and name != ''">
and g.name = like concat('%', #{username}, '%')
</if>
<if test="role == 'USER'">
and c.user_id = #{userId}
</if>
</where>
</select>
</mapper>
(4).OrdersMapper.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.example.demo.mapper.OrdersMapper">
<select id="page" resultType="com.example.demo.entity.Orders">
select o.*, u.username, u.nickname, u.address, u.phone from orders o left join sys_user u on o.user_id = u.id
<where>
<if test="name != ''">
and o.name = like concat('%', #{name}, '%')
</if>
<if test="role == 'USER'">
and o.user_id = #{userId}
</if>
</where>
</select>
</mapper>
然后我们就可以在购物车里面查询到用户的数据,并且传到我们的订单里面去
我们在订单页也可以查看到购物车进行结算传来的数据,以及之前获取的用户数据也一并带入到我们的订单页表里面
最后我们就可以在前端进行数据渲染,获取自己想要的数据
完整代码
<template>
<div style="padding: 20px">
<div style="margin: 10px 0">
<el-popconfirm
class="ml-5"
confirm-button-text='确认'
cancel-button-text='取消'
icon="el-icon-info"
icon-color="red"
title="确定批量删除这些信息吗?"
@confirm="delBatch">
<el-button type="danger" slot="reference" style="margin-left: 250px">
<i class="el-icon-remove-outline" />删除</el-button>
</el-popconfirm>
</div>
<el-table :data="tableData" border stripe :header-cell-class-name="headerBg"
@selection-change="handleSelectionChange" style="width: 1000px; margin-left: 250px">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="goodsName" label="商品名称" ></el-table-column>
<el-table-column prop="goodsId" label="商品图片" >
<template slot-scope="scope">
<el-image style="width: 50%; height: 50%" :src="scope.row.goodsUrl" :preview-src-list="[scope.row.goodsUrl]"></el-image>
</template>
</el-table-column>
<el-table-column prop="goodsId" label="商品价格" >
<template slot-scope="scope">
<span style="color: orangered; font-weight: bold; font-size: 14px;">¥ {{scope.row.price}}</span>
</template>
</el-table-column>
<el-table-column prop="number" label="商品数量" >
<template slot-scope="scope">
<el-input-number style="width: 70%; height: 70%" v-model="scope.row.number" :min="1" :max="100" @change="changeNum(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="creatTime" label="添加时间" ></el-table-column>
</el-table>
<div style="padding: 10px 0; margin-left: 250px">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-sizes="[ 5, 10, 15]"
:page-size="pageSize"
layout="total, prev, pager, next"
:total="total">
</el-pagination>
</div>
<div style="margin: 10px 0; text-align: right">
<div style="padding: 10px 0; margin-right: 250px;">
当前选择商品总价:<span style="color:red;">¥ {{ totalPrice }}</span></div>
<el-button style="color: white; background-color: orangered; margin-right: 250px" @click="payChat">
<i class="el-icon-coin"/> 结 算
</el-button>
</div>
</div>
</template>
<script>
export default {
name: "Cart",
data() {
return {
tableData: [],
total: 0,
pageNum: 1,
pageSize: 5,
name: "",
form: {},
dialogFormVisible: false,
multipleSelection: [],
headerBg: "headerBg",
roles: [],
totalPrice: 0
}
},
created() {
this.load()
},
methods: {
payChat() {
if (!this.multipleSelection || !this.multipleSelection.length) {
this.$message.error("请选择需要结算的商品")
return
}
let data = {name: this.multipleSelection[0].goodsName, totalPrice: this.totalPrice, carts: this.multipleSelection}
// this.multipleSelection.forEach(v => {
// data.carts.push({ goodsId: v.goodsId, number: v.number })
// })
this.request.post('/orders', data).then(res => {
if (res.code === '200') {
this.$message.success("结算成功,请支付订单!")
this.load()
this.$router.push("/front/orders")
} else {
this.$message.error(res.msg)
}
})
},
changeNum(row) {
this.request.post("/cart/number/" + row.id + "/" + row.number).then(res => {
// this.load()
this.totalPrice = 0
//计算总价格
this.multipleSelection.forEach(item => {
item.number = row.number //更新选中的数组的数量
this.totalPrice += item.price * row.number //小坑,注意数量更新的选择对象是哪一个
})
})
},
load: function () {
this.request.get("/cart/page", {
params: {
pageNum: this.pageNum,
pageSize: this.pageSize,
name: this.name,
}
}).then(res => {
this.tableData = res.data.records
this.total = res.data.total
})
},
save() {
this.request.post("/cart", this.form).then(res => {
if (res.code === '200') {
this.$message.success("保存成功")
this.dialogFormVisible = false
this.load()
} else {
this.$message.error("保存失败")
}
})
},
handleSelectionChange(val) {
this.totalPrice = 0
this.multipleSelection = val;
if (val && val.length) {
val.forEach(item => {
this.totalPrice += item.number * item.price
})
this.totalPrice = this.totalPrice.toFixed(2)
}
},
delBatch() {
if (!this.multipleSelection.length) {
this.$message.warning("请选择需要删除的内容")
return
}
let ids = this.multipleSelection.map(v => v.id) //[{}, {}, {}] => [1,2,3]
this.request.post("/cart/del/batch", ids).then(res => {
if (res.code === '200') {
this.$message.success("删除商品成功")
this.load()
} else {
this.$message.error("删除商品失败")
}
})
},
reset() {
this.name = ""
this.load()
},
handleSizeChange(pageSize) {
console.log(pageSize)
this.pageSize = pageSize
this.load()
},
handleCurrentChange(pageNum) {
console.log(pageNum)
this.pageNum = pageNum
this.load()
},
}
}
</script>
<style>
.headerBg {
background: #eee!important;
}
</style>
⛵小结
以上就是对SpringBoot实现一对一关联查询功能简单的概述,使得我们的项目更加的趋于完美,也提升了我们对于编程的能力和脑力思维!
如果这篇文章有帮助到你,希望可以给作者点个赞👍,创作不易,如果有对后端技术、前端领域感兴趣的,也欢迎关注 ,我将会给你带来巨大的收获与惊喜💝💝💝!