From ea1f69e4fb734a50695a4170f292061dde788111 Mon Sep 17 00:00:00 2001 From: ph6vsnc9i <2218893456@qq.com> Date: Thu, 12 Dec 2024 00:05:01 +0800 Subject: [PATCH 001/130] Add develop --- develop | 1 + 1 file changed, 1 insertion(+) create mode 100644 develop diff --git a/develop b/develop new file mode 100644 index 0000000..66dc905 --- /dev/null +++ b/develop @@ -0,0 +1 @@ +undefined \ No newline at end of file -- 2.34.1 From c897470532a6b8769b3fda63e9dd5e5645d3691f Mon Sep 17 00:00:00 2001 From: pzh69r42v Date: Thu, 12 Dec 2024 16:03:41 +0800 Subject: [PATCH 002/130] Add wangjinhao_branch --- wangjinhao_branch | 1 + 1 file changed, 1 insertion(+) create mode 100644 wangjinhao_branch diff --git a/wangjinhao_branch b/wangjinhao_branch new file mode 100644 index 0000000..66dc905 --- /dev/null +++ b/wangjinhao_branch @@ -0,0 +1 @@ +undefined \ No newline at end of file -- 2.34.1 From d81ac3f01cfb350a3e058ff730dcf7d5fe6a75e0 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Thu, 12 Dec 2024 16:10:32 +0800 Subject: [PATCH 003/130] =?UTF-8?q?=E6=9A=82=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/captcha/original/1.png | Bin 0 -> 36206 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/1.png diff --git a/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/1.png b/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/1.png new file mode 100644 index 0000000000000000000000000000000000000000..51573a0c4e1e72a0566fa1ba57422b5f06cd90d8 GIT binary patch literal 36206 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 E*~8IL`v3p{ literal 0 HcmV?d00001 -- 2.34.1 From d1e05100dd9f7b8b785a248dcbca1b64998a67a8 Mon Sep 17 00:00:00 2001 From: pyuv9atf8 <1768422698@qq.com> Date: Thu, 12 Dec 2024 18:47:08 +0800 Subject: [PATCH 004/130] Add LiiuZeYu_branch --- LiiuZeYu_branch | 1 + 1 file changed, 1 insertion(+) create mode 100644 LiiuZeYu_branch diff --git a/LiiuZeYu_branch b/LiiuZeYu_branch new file mode 100644 index 0000000..66dc905 --- /dev/null +++ b/LiiuZeYu_branch @@ -0,0 +1 @@ +undefined \ No newline at end of file -- 2.34.1 From 8b763524ea32ce5f3555307ec82112e8d090a4f4 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Thu, 12 Dec 2024 19:05:30 +0800 Subject: [PATCH 005/130] =?UTF-8?q?=E6=9A=82=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/captcha/original/1.png | Bin 0 -> 36206 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/1.png diff --git a/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/1.png b/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/1.png new file mode 100644 index 0000000000000000000000000000000000000000..51573a0c4e1e72a0566fa1ba57422b5f06cd90d8 GIT binary patch literal 36206 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 E*~8IL`v3p{ literal 0 HcmV?d00001 -- 2.34.1 From 4f3d66d6d138fe8a8a770796e6b56bd590e45f63 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Thu, 12 Dec 2024 19:08:13 +0800 Subject: [PATCH 006/130] =?UTF-8?q?=E6=9A=82=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/captcha/original/2.png | Bin 0 -> 43926 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/2.png diff --git a/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/2.png b/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/original/2.png new file mode 100644 index 0000000000000000000000000000000000000000..909dc39ef60eae026a09a82fde781ccc44858bd4 GIT binary patch literal 43926 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 Date: Sun, 15 Dec 2024 22:44:35 +0800 Subject: [PATCH 007/130] Add tangchuanxing_branch --- tangchuanxing_branch | 1 + 1 file changed, 1 insertion(+) create mode 100644 tangchuanxing_branch diff --git a/tangchuanxing_branch b/tangchuanxing_branch new file mode 100644 index 0000000..66dc905 --- /dev/null +++ b/tangchuanxing_branch @@ -0,0 +1 @@ +undefined \ No newline at end of file -- 2.34.1 From 7fff432472fbbc03de1de69ef24b3b204385abbf Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Sun, 15 Dec 2024 23:12:07 +0800 Subject: [PATCH 008/130] =?UTF-8?q?=E6=9A=82=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- screenshot/login.png | Bin 0 -> 121399 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 screenshot/login.png diff --git a/screenshot/login.png b/screenshot/login.png new file mode 100644 index 0000000000000000000000000000000000000000..3c532c8e5297069cfe10eb3d6d8fbcc3645dcea6 GIT binary patch literal 121399 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 AYXATM literal 0 HcmV?d00001 -- 2.34.1 From f8c09d48ce308e59dd83d28bfbaa510b952eef0c Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Sun, 15 Dec 2024 23:27:46 +0800 Subject: [PATCH 009/130] =?UTF-8?q?=E6=9A=82=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/common/bean/AliDaYu.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/bean/AliDaYu.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/bean/AliDaYu.java b/yami-shop-common/src/main/java/com/yami/shop/common/bean/AliDaYu.java new file mode 100644 index 0000000..55728af --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/bean/AliDaYu.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.common.bean; +// 导入lombok的Data注解,使用该注解可以自动生成类的一些常规方法,比如getter、setter、toString等方法,简化代码编写 +import lombok.Data; + +/** + * 阿里大鱼配置信息 + * 这个类用于存放与阿里大鱼相关的配置信息,比如访问密钥ID、访问密钥密码以及签名名称等。 + * 通过这个类可以方便地对阿里大鱼相关配置进行统一管理和传递。 + * @author LGH + */ +@Data +// 定义名为AliDaYu的公共类,用于封装阿里大鱼配置相关的属性 +public class AliDaYu { + // 以下是类的私有属性,用于存储具体的配置信息 + + // 用于存储阿里大鱼的访问密钥ID,通过该ID来标识访问阿里大鱼服务的身份凭证之一 + private String accessKeyId; + + // 用于存储阿里大鱼的访问密钥密码,与访问密钥ID配合使用,用于进行安全验证等操作,确保对阿里大鱼服务的合法访问 + private String accessKeySecret; + + // 用于存储阿里大鱼的签名名称,在涉及到一些需要签名验证的业务场景中会用到该名称 + private String signName; +} -- 2.34.1 From dcb19e70ac9cceadebadb963c90b23e78d1f38f6 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Sun, 15 Dec 2024 23:28:36 +0800 Subject: [PATCH 010/130] =?UTF-8?q?=E6=9A=82=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yami/shop/dao/AreaMapper.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/dao/AreaMapper.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/dao/AreaMapper.java b/yami-shop-service/src/main/java/com/yami/shop/dao/AreaMapper.java new file mode 100644 index 0000000..c4f33a4 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/dao/AreaMapper.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义了一个名为com.yami.shop.dao的包,用于组织代码 +package com.yami.shop.dao; + +// 导入了MyBatis Plus框架中的BaseMapper接口,用于提供基础的CRUD操作 +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +// 导入了Area实体类,这个类代表了数据库中的一个表 +import com.yami.shop.bean.model.Area; + +/** + * 这是一个Mapper接口,用于定义与Area实体相关的数据库操作。 + * + * @author lanhai 表示这个接口的作者是lanhai + */ +public interface AreaMapper extends BaseMapper { + // 这个接口继承了BaseMapper接口,因此已经包含了基础的CRUD操作,不需要额外定义方法 +} -- 2.34.1 From e0187fce3955cbb0d80a73034211ee984c55c874 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Sun, 15 Dec 2024 23:31:20 +0800 Subject: [PATCH 011/130] =?UTF-8?q?=E6=9A=82=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/service/AreaService.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/AreaService.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/AreaService.java b/yami-shop-service/src/main/java/com/yami/shop/service/AreaService.java new file mode 100644 index 0000000..778da2b --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/AreaService.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义了一个名为com.yami.shop.service的包,用于组织代码 +package com.yami.shop.service; + +// 导入了MyBatis Plus框架中的分页插件Page类 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +// 导入了MyBatis Plus框架中的IService接口,用于提供基础的CRUD操作 +import com.baomidou.mybatisplus.extension.service.IService; +// 导入了Area实体类,这个类代表了数据库中的一个表 +import com.yami.shop.bean.model.Area; + +import java.util.List; +import java.util.Map; + +/** + * 这是一个服务接口,用于定义与Area实体相关的业务操作。 + * + * @author lgh on 2018/10/26 表示这个接口的作者是lgh,创建时间为2018年10月26日。 + */ +public interface AreaService extends IService { + + /** + * 这是一个通过父id查找地址的方法。 + * + * @param pid 父id,用于指定要查找的地址的父级地址 + * @return 返回一个Area对象的列表,包含了所有父id为pid的地址信息 + */ + List listByPid(Long pid); + + /** + * 这是一个通过父id清除地址缓存的方法。 + * + * @param pid 父id,用于指定要清除缓存的地址的父级地址 + */ + void removeAreaCacheByParentId(Long pid); +} -- 2.34.1 From 47b9999c8a48ef191412f5b6b50e9710a5e01815 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Sun, 15 Dec 2024 23:32:46 +0800 Subject: [PATCH 012/130] =?UTF-8?q?=E6=9A=82=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/captcha/slidingBlock/1.png | Bin 0 -> 22049 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/1.png diff --git a/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/1.png b/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/1.png new file mode 100644 index 0000000000000000000000000000000000000000..1905026606d04c80eba0d7e03a5e286519257b9b GIT binary patch literal 22049 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 Cg^4== literal 0 HcmV?d00001 -- 2.34.1 From f5d217ce8d93c1e39bb09e61751afa7ed3324495 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Sun, 15 Dec 2024 23:35:22 +0800 Subject: [PATCH 013/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/captcha/slidingBlock/2.png | Bin 0 -> 21367 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/2.png diff --git a/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/2.png b/yami-shop-security/yami-shop-security-common/src/main/resources/captcha/slidingBlock/2.png new file mode 100644 index 0000000000000000000000000000000000000000..b1482d48b4f2f323979ead105e074fbcc6c0e6d9 GIT binary patch literal 21367 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 literal 0 HcmV?d00001 -- 2.34.1 From 177fc39e0e024dc3297b18d07fb9cda41731f982 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Sun, 15 Dec 2024 23:35:31 +0800 Subject: [PATCH 014/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yami/shop/common/util/Arith.java | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/util/Arith.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/util/Arith.java b/yami-shop-common/src/main/java/com/yami/shop/common/util/Arith.java new file mode 100644 index 0000000..4520c37 --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/util/Arith.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.common.util; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 算术运算工具类,用于提供精确的数值计算功能,避免浮点数计算时因精度问题产生误差。 + * 该类主要针对double类型以及BigDecimal类型的数值进行加、减、乘、除和四舍五入等常见算术运算操作。 + * @author lanhai + */ +public class Arith { + /** + * 默认除法运算精度,即当调用无指定精度的div方法时,除法运算结果将精确到小数点后2位, + * 后续的数字按照相应的舍入规则进行处理(此处为HALF_EVEN舍入模式)。 + */ + private static final int DEF_DIV_SCALE = 2; + + /** + * 将构造函数私有化,防止外部实例化该类,因为这个类的设计目的是作为工具类,提供静态方法供调用, + * 不需要创建类的实例对象,所以禁止外部实例化它。 + */ + private Arith() { + } + + /** + * 提供精确的加法运算。 + * 该方法接收两个double类型的参数,将它们转换为BigDecimal类型后进行精确加法运算, + * 最后返回计算结果的double值,这样可以避免直接使用double进行加法运算时可能出现的精度丢失问题。 + * + * @param v1 被加数,即参与加法运算的第一个数值。 + * @param v2 加数,即参与加法运算的第二个数值。 + * @return 两个参数的和,以double类型返回精确计算后的结果。 + */ + public static double add(double v1, double v2) { + // 必须转换成String,因为BigDecimal的构造函数建议使用基于字符串的构造方式来避免精度问题, + // 如果直接使用double类型传入构造函数,在某些情况下可能会出现精度丢失。 + String s1 = Double.toString(v1); + String s2 = Double.toString(v2); + BigDecimal b1 = new BigDecimal(s1); + BigDecimal b2 = new BigDecimal(s2); + return b1.add(b2).doubleValue(); + } + + /** + * 提供精确的减法运算。 + * 与加法运算类似,接收两个double类型的参数,先转换为BigDecimal类型,再进行精确的减法操作, + * 最后返回结果的double值,确保减法运算的精度准确性。 + * + * @param v1 被减数,参与减法运算的第一个数值,从中减去另一个数值。 + * @param v2 减数,参与减法运算的第二个数值,用于从被减数中减去。 + * @return 两个参数的差,以double类型返回精确计算后的结果。 + */ + public static double sub(double v1, double v2) { + String s1 = Double.toString(v1); + String s2 = Double.toString(v2); + BigDecimal b1 = new BigDecimal(s1); + BigDecimal b2 = new BigDecimal(s2); + return b1.subtract(b2).doubleValue(); + } + + /** + * 提供精确的乘法运算。 + * 同样,把传入的两个double类型参数转换为BigDecimal类型后进行乘法运算, + * 以保证乘法计算结果的精度符合预期,最后返回double类型的结果值。 + * + * @param v1 被乘数,参与乘法运算的第一个数值,与另一个数值相乘。 + * @param v2 乘数,参与乘法运算的第二个数值,用于和被乘数相乘。 + * @return 两个参数的积,以double类型返回精确计算后的结果。 + */ + public static double mul(double v1, double v2) { + String s1 = Double.toString(v1); + String s2 = Double.toString(v2); + BigDecimal b1 = new BigDecimal(s1); + BigDecimal b2 = new BigDecimal(s2); + return b1.multiply(b2).doubleValue(); + } + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到小数点以后10位,以后的数字四舍五入。 + * 这里实际调用了另一个带有精度参数的div方法,并传入默认的精度值(DEF_DIV_SCALE), + * 以实现默认精度下的除法运算功能。 + * + * @param v1 被除数,参与除法运算的第一个数值,作为被除的对象。 + * @param v2 除数,参与除法运算的第二个数值,用于除被除数。 + * @return 两个参数的商,以double类型返回按照默认精度进行四舍五入后的结果。 + */ + public static double div(double v1, double v2) { + return div(v1, v2, DEF_DIV_SCALE); + } + + /** + * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入。 + * 首先会对传入的参数进行合法性校验,如果指定的精度scale小于0,则抛出异常, + * 因为精度应该是正整数或者0,表示小数点后的保留位数。然后将传入的double类型参数转换为BigDecimal类型, + * 使用指定的精度和HALF_EVEN舍入模式进行除法运算,最后返回double类型的商。 + * + * @param v1 被除数,参与除法运算的第一个数值,作为被除的对象。 + * @param v2 除数,参与除法运算的第二个数值,用于除被除数。 + * @param scale 表示需要精确到小数点以后几位,用于指定除法运算结果的精度。 + * @return 两个参数的商,以double类型返回按照指定精度进行四舍五入后的结果。 + */ + public static double div(double v1, double v2, int scale) { + if (scale < 0) { + throw new IllegalArgumentException("The scale must be a positive integer or zero"); + } + String s1 = Double.toString(v1); + String s2 = Double.toString(v2); + BigDecimal b1 = new BigDecimal(s1); + BigDecimal b2 = new BigDecimal(s2); + return b1.divide(b2, scale, RoundingMode.HALF_EVEN).doubleValue(); + } + + /** + * 提供精确的小数位四舍五入处理。 + * 先对传入的参数进行合法性校验,要求scale(小数点后保留位数)不能小于0, + * 然后将需要四舍五入的数字转换为BigDecimal类型,再通过与表示数值1的BigDecimal对象进行除法运算, + * 按照指定的scale和HALF_EVEN舍入模式进行四舍五入,最后返回处理后的double类型结果。 + * + * @param v 需要四舍五入的数字,以double类型传入。 + * @param scale 小数点后保留几位,用于指定四舍五入的精度要求。 + * @return 四舍五入后的结果,以double类型返回经过处理后的数值。 + */ + public static double round(double v, int scale) { + if (scale < 0) { + throw new IllegalArgumentException("The scale must be a positive integer or zero"); + } + String s = Double.toString(v); + BigDecimal b = new BigDecimal(s); + BigDecimal one = new BigDecimal("1"); + return b.divide(one, scale, RoundingMode.HALF_EVEN).doubleValue(); + } + + + /** + * 重载的加法运算方法,用于对三个BigDecimal类型的数值进行相加操作, + * 最后返回相加结果的double值,方便在需要对多个BigDecimal数值求和时使用。 + * + * @param bigDecimal 参与加法运算的第一个BigDecimal数值。 + * @param bigDecimal2 参与加法运算的第二个BigDecimal数值。 + * @param bigDecimal3 参与加法运算的第三个BigDecimal数值。 + * @return 三个参数的和,以double类型返回精确计算后的结果。 + */ + public static double add(BigDecimal bigDecimal, BigDecimal bigDecimal2, BigDecimal bigDecimal3) { + return bigDecimal.add(bigDecimal2).add(bigDecimal3).doubleValue(); + } + + /** + * 另一个重载的加法运算方法,用于对两个BigDecimal类型的数值进行相加操作, + * 并返回相加结果的double值,满足不同场景下对两个BigDecimal数值求和的需求。 + * + * @param preDepositPrice 参与加法运算的第一个BigDecimal数值,通常可以理解为预存金额之类的数值(具体含义取决于业务场景)。 + * @param finalPrice 参与加法运算的第二个BigDecimal数值,通常可以理解为最终金额之类的数值(具体含义取决于业务场景)。 + * @return 两个参数的和,以double类型返回精确计算后的结果。 + */ + public static double add(BigDecimal preDepositPrice, BigDecimal finalPrice) { + return preDepositPrice.add(finalPrice).doubleValue(); + } +} \ No newline at end of file -- 2.34.1 From 04c811fb772e40ed86269c13645d94a29bda456a Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Sun, 15 Dec 2024 23:38:52 +0800 Subject: [PATCH 015/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/dao/AttachFileMapper.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/dao/AttachFileMapper.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/dao/AttachFileMapper.java b/yami-shop-service/src/main/java/com/yami/shop/dao/AttachFileMapper.java new file mode 100644 index 0000000..522144c --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/dao/AttachFileMapper.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义了一个名为com.yami.shop.dao的包,用于组织代码 +package com.yami.shop.dao; + +// 导入了MyBatis Plus框架中的BaseMapper接口,用于提供基础的CRUD操作 +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +// 导入了AttachFile实体类,这个类代表了数据库中的一个表 +import com.yami.shop.bean.model.AttachFile; + +/** + * 这是一个Mapper接口,用于定义与AttachFile实体相关的数据库操作。 + * + * @author lanhai 表示这个接口的作者是lanhai。 + */ +public interface AttachFileMapper extends BaseMapper { + // 这个接口继承了BaseMapper接口,因此已经包含了基础的CRUD操作,不需要额外定义方法 +} \ No newline at end of file -- 2.34.1 From 07249e380997f043a94dd4773e24e4e7b9c24bee Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Sun, 15 Dec 2024 23:39:51 +0800 Subject: [PATCH 016/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/service/AttachFileService.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/AttachFileService.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/AttachFileService.java b/yami-shop-service/src/main/java/com/yami/shop/service/AttachFileService.java new file mode 100644 index 0000000..ae12eb4 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/AttachFileService.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义了一个名为com.yami.shop.service的包,用于组织代码 +package com.yami.shop.service; + +// 导入了MyBatis Plus框架中的IService接口,用于提供基础的CRUD操作 +import com.baomidou.mybatisplus.extension.service.IService; +// 导入了AttachFile实体类,这个类代表了数据库中的一个表 +import com.yami.shop.bean.model.AttachFile; +// 导入了Spring框架中的MultipartFile类,用于处理上传的文件 +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +/** + * 这是一个服务接口,用于定义与AttachFile实体相关的业务操作。 + * + * @author lanhai 表示这个接口的作者是lanhai。 + * Created by lgh on 2018/07/27 表示这个接口是由lgh创建的,创建时间为2018年7月27日。 + */ +public interface AttachFileService extends IService { + + /** + * 上传文件到本地的方法。 + * + * @param file 要上传的文件对象,类型为MultipartFile + * @throws IOException 如果上传过程中发生I/O错误,会抛出IOException异常 + * @return 返回一个字符串,通常是上传文件后的一些信息,比如文件路径或者文件ID + */ + String uploadFile(MultipartFile file) throws IOException; + + /** + * 删除文件的方法。 + * + * @param fileName 要删除的文件名称 + */ + void deleteFile(String fileName); +} \ No newline at end of file -- 2.34.1 From cc5b9154b65f5dc5f4af2bc0f2e6c4f7b4a07920 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Sun, 15 Dec 2024 23:40:23 +0800 Subject: [PATCH 017/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yami/shop/dao/BasketMapper.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/dao/BasketMapper.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/dao/BasketMapper.java b/yami-shop-service/src/main/java/com/yami/shop/dao/BasketMapper.java new file mode 100644 index 0000000..f2b247b --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/dao/BasketMapper.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义了一个名为com.yami.shop.dao的包,用于组织代码 +package com.yami.shop.dao; + +// 导入了MyBatis Plus框架中的BaseMapper接口,用于提供基础的CRUD操作 +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +// 导入了项目中定义的DTO和参数类 +import com.yami.shop.bean.app.dto.ShopCartItemDto; +import com.yami.shop.bean.app.param.ShopCartParam; +// 导入了项目中的实体类 +import com.yami.shop.bean.model.Basket; +// 导入了MyBatis的@Param注解,用于在XML或注解中传递参数 +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + * 这是一个Mapper接口,用于定义与Basket实体(购物车)相关的数据库操作。 + * + * @author lanhai 表示这个接口的作者是lanhai。 + */ +public interface BasketMapper extends BaseMapper { + + /** + * 获取用户的购物项列表。 + * + * @param userId 用户id + * @return 返回一个包含用户购物项的DTO列表 + */ + List getShopCartItems(@Param("userId") String userId); + + /** + * 根据购物车id列表和用户id删除购物车项。 + * + * @param userId 用户id + * @param basketIds 购物车id列表 + */ + void deleteShopCartItemsByBasketIds(@Param("userId") String userId, @Param("basketIds") List basketIds); + + /** + * 删除用户的所有购物车项。 + * + * @param userId 用户id + */ + void deleteAllShopCartItems(@Param("userId") String userId); + + /** + * 获取失效的购物项列表。 + * + * @param userId 用户id + * @return 返回一个包含失效购物项的DTO列表 + */ + List getShopCartExpiryItems(@Param("userId") String userId); + + /** + * 删除失效的购物项。 + * + * @param userId 用户id + */ + void cleanExpiryProdList(@Param("userId") String userId); + + /** + * 更新购物车的满减活动id。 + * + * @param userId 用户id + * @param basketIdShopCartParamMap 购物项id与满减活动参数的映射 + */ + void updateDiscountItemId(@Param("userId")String userId, @Param("basketIdShopCartParamMap") Map basketIdShopCartParamMap); + + /** + * 根据商品id获取包含该商品的用户id列表。 + * + * @param prodId 商品id + * @return 返回一个包含用户id的列表 + */ + List listUserIdByProdId(@Param("prodId")Long prodId); + +} -- 2.34.1 From c5fd53485a37824594444adb7d82e9da3ff209b8 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Sun, 15 Dec 2024 23:40:43 +0800 Subject: [PATCH 018/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/service/BasketService.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/BasketService.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/BasketService.java b/yami-shop-service/src/main/java/com/yami/shop/service/BasketService.java new file mode 100644 index 0000000..fd75c1a --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/BasketService.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yami.shop.bean.app.dto.ShopCartDto; +import com.yami.shop.bean.app.dto.ShopCartItemDto; +import com.yami.shop.bean.app.param.ChangeShopCartParam; +import com.yami.shop.bean.app.param.OrderItemParam; +import com.yami.shop.bean.app.param.ShopCartParam; +import com.yami.shop.bean.model.Basket; + +import java.util.List; +import java.util.Map; + +/** + * @author lgh on 2018/10/18. + */ +public interface BasketService extends IService { + + /** + * 根据购物车id列表删除购物项 + * @param userId 用户id + * @param basketIds 购物车id列表 + */ + void deleteShopCartItemsByBasketIds(String userId, List basketIds); + + /** + * 添加购物车 + * @param param + * @param userId + */ + void addShopCartItem(ChangeShopCartParam param, String userId); + + /** + * 更新购物车 + * @param basket + */ + void updateShopCartItem(Basket basket); + + /** + * 删除所有购物车 + * @param userId + */ + void deleteAllShopCartItems(String userId); + + /** + * 根据用户id获取购物车列表 + * @param userId 用户id + * @return 购物车列表 + */ + List getShopCartItems(String userId); + + /** + * 获取购物车失效列表 + * @param userId 用户id + * @return 失效商品 + */ + List getShopCartExpiryItems(String userId); + + /** + * 清除失效的购物项 + * @param userId 用户id + */ + void cleanExpiryProdList(String userId); + + /** + * 更新满减活动id + * @param userId 用户id + * @param basketIdShopCartParamMap 购物车map + */ + void updateBasketByShopCartParam(String userId, Map basketIdShopCartParamMap); + + /** + * 删除购物车缓存 + * @param userId + */ + void removeShopCartItemsCacheByUserId(String userId); + + /** + * 获取购物车中拥有某件商品的用户,用于清除该用户购物车的缓存 + * @param prodId 商品id + * @return 用户id + */ + List listUserIdByProdId(Long prodId); + + /** + * 根据店铺组装购车中的商品信息,返回每个店铺中的购物车商品信息 + * @param shopCartItems 指定的购物项目 + * @return 每个店铺的购物车信息 + */ + List getShopCarts(List shopCartItems); + + /** + * 组装获取用户提交的购物车商品项 + * @param orderItem 提交订单时携带的商品信息 + * @param userId 当前用户的用户id + * @param basketId 购物车id + * @return 所有的商品购物项 + */ + List getShopCartItemsByOrderItems(List basketId, OrderItemParam orderItem,String userId); + +} -- 2.34.1 From c8132ad798cd09c8db2e31518340517e7046a1ee Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Sun, 15 Dec 2024 23:41:12 +0800 Subject: [PATCH 019/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yami/shop/dao/BrandMapper.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/dao/BrandMapper.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/dao/BrandMapper.java b/yami-shop-service/src/main/java/com/yami/shop/dao/BrandMapper.java new file mode 100644 index 0000000..8f715bd --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/dao/BrandMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yami.shop.bean.model.Brand; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @author lanhai + */ +public interface BrandMapper extends BaseMapper { + + /** + * 根据品牌名称获取品牌 + * @param brandName 品牌名称 + * @return 品牌信息 + */ + Brand getByBrandName(String brandName); + + /** + * 根据分类id获取品牌列表 + * @param categoryId 分类id + * @return 品牌列表 + */ + List listByCategoryId(@Param("categoryId")Long categoryId); +} \ No newline at end of file -- 2.34.1 From baa27f41bcea1911970c9431e949d72660848471 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Sun, 15 Dec 2024 23:41:29 +0800 Subject: [PATCH 020/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/service/BrandService.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/BrandService.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/BrandService.java b/yami-shop-service/src/main/java/com/yami/shop/service/BrandService.java new file mode 100644 index 0000000..69e51cf --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/BrandService.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yami.shop.bean.model.Brand; + +import java.util.List; + +/** + * @author lanhai + */ +public interface BrandService extends IService { + + /** + * 根据品牌名称获取该品牌 + * @param brandName + * @return + */ + Brand getByBrandName(String brandName); + + /** + * 删除品牌,同时删除品牌与分类之间的关联关系 + * @param brandId + */ + void deleteByBrand(Long brandId); + + /** + * 根据分类id获取品牌列表 + * @param categoryId 分类id + * @return + */ + List listByCategoryId(Long categoryId); + +} -- 2.34.1 From c789a1ed4610b6f7d6596da6dab385abd4f69c59 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Sun, 15 Dec 2024 23:46:34 +0800 Subject: [PATCH 021/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/mapper/BasketMapper.xml | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 yami-shop-service/src/main/resources/mapper/BasketMapper.xml diff --git a/yami-shop-service/src/main/resources/mapper/BasketMapper.xml b/yami-shop-service/src/main/resources/mapper/BasketMapper.xml new file mode 100644 index 0000000..65ee5e0 --- /dev/null +++ b/yami-shop-service/src/main/resources/mapper/BasketMapper.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + delete from tz_basket where user_id = #{userId} and basket_id in + + #{basketId} + + + + + + delete from tz_basket where user_id = #{userId} + + + + + + + + DELETE FROM tz_basket + WHERE basket_id IN( + SELECT basket_id FROM ( + SELECT tb.basket_id basket_id + FROM tz_basket tb + LEFT JOIN tz_shop_detail tsd + ON tb.shop_id = tsd.shop_id + LEFT JOIN tz_prod tp + ON tb.prod_id = tp.prod_id + LEFT JOIN tz_sku ts + ON tb.sku_id = ts.sku_id + WHERE (tp.status = 0 OR ts.status = 0) AND tb.user_id = #{userId}) AS temp) + + + + + + UPDATE tz_basket SET discount_id = #{shopCartParam.discountId} where basket_id = #{key} and user_id = #{userId} + + + + + + + \ No newline at end of file -- 2.34.1 From 1825daad59ec3d76328d4a5eb227b7ae59c151e3 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Sun, 15 Dec 2024 23:47:38 +0800 Subject: [PATCH 022/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/mapper/AreaMapper.xml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 yami-shop-service/src/main/resources/mapper/AreaMapper.xml diff --git a/yami-shop-service/src/main/resources/mapper/AreaMapper.xml b/yami-shop-service/src/main/resources/mapper/AreaMapper.xml new file mode 100644 index 0000000..5c4e5e8 --- /dev/null +++ b/yami-shop-service/src/main/resources/mapper/AreaMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file -- 2.34.1 From 14e3193145926180944cef01bb0ee4cbba362b88 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Sun, 15 Dec 2024 23:48:15 +0800 Subject: [PATCH 023/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/service/impl/AreaServiceImpl.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/impl/AreaServiceImpl.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/AreaServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/AreaServiceImpl.java new file mode 100644 index 0000000..17fa7f3 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/AreaServiceImpl.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义了一个名为com.yami.shop.service.impl的包,用于组织代码 +package com.yami.shop.service.impl; + +// 导入了MyBatis Plus框架中的LambdaQueryWrapper类,用于构建条件查询 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +// 导入了MyBatis Plus框架中的ServiceImpl类,用于提供基础的CRUD操作实现 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +// 导入了Area实体类,这个类代表了数据库中的一个表 +import com.yami.shop.bean.model.Area; +// 导入了AreaMapper接口,用于数据库操作 +import com.yami.shop.dao.AreaMapper; +// 导入了AreaService接口,用于定义业务操作 +import com.yami.shop.service.AreaService; +// 导入了Spring框架的注解,用于注入依赖和定义服务 +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 这是一个服务实现类,用于实现AreaService接口中定义的业务操作。 + * + * @author lgh on 2018/10/26 表示这个类的作者是lgh,创建时间为2018年10月26日。 + */ +@Service +public class AreaServiceImpl extends ServiceImpl implements AreaService { + + // 使用Spring的@Autowired注解自动注入AreaMapper + @Autowired + private AreaMapper areaMapper; + + /** + * 通过父id查找地址列表的方法实现。 + * 使用了MyBatis Plus的LambdaQueryWrapper来构建查询条件,查询父id为pid的地址。 + * 使用了Spring的@Cacheable注解,表示这个方法的结果会被缓存,缓存名称为"area",键为方法参数pid。 + * + * @param pid 父id,用于指定要查找的地址的父级地址 + * @return 返回一个Area对象的列表,包含了所有父id为pid的地址信息 + */ + @Override + @Cacheable(cacheNames = "area", key = "#pid") + public List listByPid(Long pid) { + return areaMapper.selectList(new LambdaQueryWrapper().eq(Area::getParentId, pid)); + } + + /** + * 通过父id清除地址缓存的方法实现。 + * 使用了Spring的@CacheEvict注解,表示这个方法会清除缓存,缓存名称为"area",键为方法参数pid。 + * + * @param pid 父id,用于指定要清除缓存的地址的父级地址 + */ + @Override + @CacheEvict(cacheNames = "area", key = "#pid") + public void removeAreaCacheByParentId(Long pid) { + // 方法体为空,清除缓存的逻辑由Spring Cache自动处理 + } +} -- 2.34.1 From b9198dbd71ae26866115c238fb39a77315c43961 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Sun, 15 Dec 2024 23:48:26 +0800 Subject: [PATCH 024/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/mapper/AttachFileMapper.xml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 yami-shop-service/src/main/resources/mapper/AttachFileMapper.xml diff --git a/yami-shop-service/src/main/resources/mapper/AttachFileMapper.xml b/yami-shop-service/src/main/resources/mapper/AttachFileMapper.xml new file mode 100644 index 0000000..56b8050 --- /dev/null +++ b/yami-shop-service/src/main/resources/mapper/AttachFileMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- 2.34.1 From 03cac6de008a42246dcfb0fe612a7e189b64d238 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Sun, 15 Dec 2024 23:48:31 +0800 Subject: [PATCH 025/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/AttachFileServiceImpl.java | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/impl/AttachFileServiceImpl.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/AttachFileServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/AttachFileServiceImpl.java new file mode 100644 index 0000000..f508520 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/AttachFileServiceImpl.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义了一个名为com.yami.shop.service.impl的包,用于组织代码 +package com.yami.shop.service.impl; + +// 导入了Hutool工具类,用于日期、文件和ID操作 +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.IdUtil; +// 导入了MyBatis Plus框架中的LambdaQueryWrapper类,用于构建条件查询 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +// 导入了MyBatis Plus框架中的ServiceImpl类,用于提供基础的CRUD操作实现 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +// 导入了七牛云存储相关类 +import com.qiniu.common.QiniuException; +import com.qiniu.http.Response; +import com.qiniu.storage.BucketManager; +import com.qiniu.storage.UploadManager; +import com.qiniu.storage.model.DefaultPutRet; +import com.qiniu.util.Auth; +// 导入了项目中的枚举、模型、工具类和配置类 +import com.yami.shop.bean.enums.UploadType; +import com.yami.shop.bean.model.AttachFile; +import com.yami.shop.common.bean.Qiniu; +import com.yami.shop.common.util.ImgUploadUtil; +import com.yami.shop.common.util.Json; +import com.yami.shop.dao.AttachFileMapper; +import com.yami.shop.service.AttachFileService; +// 导入了Spring框架的注解,用于注入依赖和定义服务 +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +// 导入了Spring框架的MultipartFile类,用于处理上传的文件 +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Date; +import java.util.Objects; + +/** + * 这是一个服务实现类,用于实现AttachFileService接口中定义的业务操作。 + * + * @author lanhai 表示这个类的作者是lanhai。 + */ +@Service +public class AttachFileServiceImpl extends ServiceImpl implements AttachFileService { + + // 使用Spring的@Autowired注解自动注入AttachFileMapper + @Autowired + private AttachFileMapper attachFileMapper; + // 使用Spring的@Autowired注解自动注入七牛云的UploadManager + @Autowired + private UploadManager uploadManager; + // 使用Spring的@Autowired注解自动注入七牛云的BucketManager + @Autowired + private BucketManager bucketManager; + // 使用Spring的@Autowired注解自动注入七牛云的配置信息 + @Autowired + private Qiniu qiniu; + // 使用Spring的@Autowired注解自动注入七牛云的认证工具 + @Autowired + private Auth auth; + // 使用Spring的@Autowired注解自动注入图片上传工具类 + @Autowired + private ImgUploadUtil imgUploadUtil; + // 定义了一个常量,用于格式化文件名 + public final static String NORM_MONTH_PATTERN = "yyyy/MM/"; + + /** + * 上传文件的方法实现。 + * 使用了Spring的@Transactional注解,表示这个方法是一个事务性操作,如果发生异常则回滚。 + * + * @param file 要上传的文件对象,类型为MultipartFile + * @throws IOException 如果上传过程中发生I/O错误,会抛出IOException异常 + * @return 返回一个字符串,通常是上传文件后的一些信息,比如文件路径或者文件ID + */ + @Override + @Transactional(rollbackFor = Exception.class) + public String uploadFile(MultipartFile file) throws IOException { + // 获取文件扩展名 + String extName = FileUtil.extName(file.getOriginalFilename()); + // 格式化文件名,包含日期和UUID + String fileName = DateUtil.format(new Date(), NORM_MONTH_PATTERN) + IdUtil.simpleUUID() + "." + extName; + // 创建AttachFile对象,并设置属性 + AttachFile attachFile = new AttachFile(); + attachFile.setFilePath(fileName); + attachFile.setFileSize(file.getBytes().length); + attachFile.setFileType(extName); + attachFile.setUploadTime(new Date()); + // 判断上传类型,如果是本地上传,则保存文件信息到数据库,并上传文件到本地 + if (Objects.equals(imgUploadUtil.getUploadType(), 1)) { + attachFileMapper.insert(attachFile); + return imgUploadUtil.upload(file, fileName); + } else { + // 如果是七牛云上传,则获取上传凭证,并上传文件到七牛云 + String upToken = auth.uploadToken(qiniu.getBucket(), fileName); + Response response = uploadManager.put(file.getBytes(), fileName, upToken); + // 解析上传响应 + Json.parseObject(response.bodyString(), DefaultPutRet.class); + return fileName; + } + } + + /** + * 删除文件的方法实现。 + * + * @param fileName 要删除的文件名称 + */ + @Override + public void deleteFile(String fileName){ + // 根据文件名删除数据库中的记录 + attachFileMapper.delete(new LambdaQueryWrapper().eq(AttachFile::getFilePath, fileName)); + try { + // 判断上传类型,如果是本地上传,则删除本地文件 + if (Objects.equals(imgUploadUtil.getUploadType(), UploadType.LOCAL.value())) { + imgUploadUtil.delete(fileName); + } else if (Objects.equals(imgUploadUtil.getUploadType(), UploadType.QINIU.value())) { + // 如果是七牛云上传,则删除七牛云上的文件 + bucketManager.delete(qiniu.getBucket(), fileName); + } + } catch (QiniuException e) { + // 如果删除失败,抛出运行时异常 + throw new RuntimeException(e); + } + } +} \ No newline at end of file -- 2.34.1 From c9175d98ec78629664dae174e01900d5c5a34d3b Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Mon, 16 Dec 2024 00:10:31 +0800 Subject: [PATCH 026/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/common/config/AuthConfig.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/AuthConfig.java diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/AuthConfig.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/AuthConfig.java new file mode 100644 index 0000000..cb6a3d1 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/AuthConfig.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.config; + +import cn.hutool.core.util.ArrayUtil; +// 导入自定义的授权配置适配器类,用于适配不同的授权配置相关逻辑,可根据具体业务需求进行扩展或定制 +import com.yami.shop.security.common.adapter.AuthConfigAdapter; +// 导入默认的授权配置适配器实现类,当没有自定义的授权配置适配器时,会使用这个默认的实现类来处理相关逻辑 +import com.yami.shop.security.common.adapter.DefaultAuthConfigAdapter; +// 导入自定义的授权过滤器类,用于对请求进行授权相关的过滤操作,比如验证用户权限等 +import com.yami.shop.security.common.filter.AuthFilter; +import jakarta.servlet.DispatcherType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; + +/** + * 授权配置类,主要用于在Spring应用中配置授权相关的组件和规则。 + * 这个类负责创建授权配置适配器以及注册授权过滤器等操作,以确保应用的授权功能能够正常工作。 + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@Configuration +// 启用方法级别的安全配置,允许在方法上使用诸如 @PreAuthorize、@PostAuthorize 等注解来进行权限控制 +@EnableMethodSecurity +public class AuthConfig { + // 通过自动注入获取授权过滤器实例,该过滤器将在后续的配置中被注册到Servlet容器中,用于对请求进行过滤处理 + @Autowired + private AuthFilter authFilter; + + /** + * 创建授权配置适配器的Bean。 + * 如果在Spring容器中没有找到自定义的AuthConfigAdapter类型的Bean, + * 则会创建并返回一个默认的授权配置适配器实例(DefaultAuthConfigAdapter), + * 这样可以方便地进行默认授权配置或者允许用户自定义授权配置来替换默认配置。 + * + * @return AuthConfigAdapter实例,用于适配授权配置相关逻辑 + */ + @Bean + @ConditionalOnMissingBean + public AuthConfigAdapter authConfigAdapter() { + return new DefaultAuthConfigAdapter(); + } + + + /** + * 创建并注册授权过滤器的FilterRegistrationBean。 + * 这个方法会创建一个FilterRegistrationBean对象,用于将授权过滤器(AuthFilter)注册到Servlet容器中, + * 并配置相关的属性,比如过滤路径、过滤器名称、优先级以及处理的请求类型等。 + * 这里使用了 @Lazy 注解,表示该Bean会延迟初始化,只有在第一次使用时才会真正创建实例,避免不必要的资源消耗。 + * + * @param authConfigAdapter 授权配置适配器实例,用于获取过滤路径等配置信息 + * @return FilterRegistrationBean对象,用于将授权过滤器注册到Servlet容器 + */ + @Bean + @Lazy + public FilterRegistrationBean filterRegistration(AuthConfigAdapter authConfigAdapter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + // 将之前注入的授权过滤器添加到FilterRegistrationBean中,这样Servlet容器就能识别并使用该过滤器来处理请求了 + registration.setFilter(authFilter); + // 设置过滤路径,通过授权配置适配器获取需要过滤的路径列表,并转换为字符串数组格式。 + // 这里使用ArrayUtil工具类将获取到的路径列表转换为String数组,以便设置给FilterRegistrationBean, + // 表示该过滤器将会对这些指定的路径下的请求进行过滤处理,此处的 /* 表示所有路径,具体路径配置通常由授权配置适配器决定 + registration.addUrlPatterns(ArrayUtil.toArray(authConfigAdapter.pathPatterns(), String.class)); + registration.setName("authFilter"); + // 设置过滤器的优先级,数值越小优先级越高,这里设置为0,表示较高的优先级,在多个过滤器存在的情况下会先执行该过滤器 + registration.setOrder(0); + registration.setDispatcherTypes(DispatcherType.REQUEST); + return registration; + } + +} \ No newline at end of file -- 2.34.1 From 9af387b5b05c525909c713a0d203a6b9e7765b9e Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Mon, 16 Dec 2024 14:08:37 +0800 Subject: [PATCH 027/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/adapter/AuthConfigAdapter.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/AuthConfigAdapter.java diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/AuthConfigAdapter.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/AuthConfigAdapter.java new file mode 100644 index 0000000..2570d41 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/AuthConfigAdapter.java @@ -0,0 +1,37 @@ +package com.yami.shop.security.common.adapter; + +import java.util.List; + +/** + * 该接口用于配置授权相关的路径信息,实现此接口后,能够方便地修改需要授权登录的路径以及不需要授权登录的路径。 + * 它为应用的权限控制中关于路径的授权配置提供了一种规范和扩展点,使得不同的应用场景可以根据自身需求灵活定义哪些路径需要授权,哪些不需要。 + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:31 + */ +public interface AuthConfigAdapter { + + /** + * 定义了一个字符串常量,表示可能需要登录才可用的URL的路径匹配模式。 + * 这里的 "/**/ma/**" 是一种通配符形式的路径表达式,意味着以任意多级目录开始,中间包含 "ma" 子目录的路径都可能需要登录才能访问。 + * 实际应用中可根据具体的业务逻辑和权限控制需求对该通配符表达式进行调整或替换。 + */ + String MAYBE_AUTH_URI = "/**/ma/**"; + + /** + * 获取需要授权登录的路径列表。 + * 实现该接口的类需要通过具体的逻辑来确定并返回哪些路径是要求用户必须进行授权登录后才能访问的。 + * 例如,可能会根据业务模块、功能权限等因素来收集相应的路径集合。 + * + * @return 需要授权登陆的路径列表,列表中的每个元素为一个具体的路径表达式(通常采用类似通配符等格式来表示一批路径) + */ + List pathPatterns(); + + /** + * 获取不需要授权登录的路径列表。 + * 同样,实现此接口的类要依据业务规则来明确哪些路径是无需用户授权登录就能直接访问的,比如一些公开的页面(如登录页面、注册页面、网站首页等常规对外公开的资源路径)。 + * + * @return 不需要授权登陆的路径列表,列表中的元素为具体的不需要授权的路径表达式 + */ + List excludePathPatterns(); +} \ No newline at end of file -- 2.34.1 From 317605f3766293aa0830e8d27e4e7574ad41e863 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Mon, 16 Dec 2024 14:10:21 +0800 Subject: [PATCH 028/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/dto/AuthenticationDTO.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/dto/AuthenticationDTO.java diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/dto/AuthenticationDTO.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/dto/AuthenticationDTO.java new file mode 100644 index 0000000..8ee3971 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/dto/AuthenticationDTO.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.security.common.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import jakarta.validation.constraints.NotBlank; + +/** + * 这个类是数据传输对象(DTO),主要用于在登录操作时传递账号和密码相关的信息。 + * 它在系统的安全模块中扮演着重要角色,作为客户端与服务端之间传递登录凭证的载体,方便对登录信息进行统一的校验、处理和传递。 + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@Data +public class AuthenticationDTO { + + /** + * 用于存储用户登录时输入的用户名信息。 + * 这里的用户名可以是实际的用户名,也可以是用户注册时关联的邮箱地址或者手机号等能够唯一标识用户身份的信息,具体取决于系统的业务逻辑和认证规则。 + * 并且通过 @NotBlank 注解进行了校验约束,要求该字段不能为空,若为空则会根据注解中配置的提示信息("userName不能为空")给出相应的错误提示。 + * 同时使用 @Schema 注解在 API 文档相关的生成(如 Swagger 等工具生成 API 文档时)中描述该字段,表明它代表用户名/邮箱/手机号,且是必填项。 + */ + @NotBlank(message = "userName不能为空") + @Schema(description = "用户名/邮箱/手机号", required = true) + protected String userName; + + /** + * 用于存储用户登录时输入的密码信息。 + * 此密码是用户在注册账号或者设置安全凭证时设定的,用于验证用户身份合法性的关键信息之一。 + * 同样受到 @NotBlank 注解的约束,不能为空,若为空会提示 "passWord不能为空" 的错误信息。 + * @Schema 注解用于在 API 文档生成时描述该字段,表示一般用作密码,且是必填项。 + */ + @NotBlank(message = "passWord不能为空") + @Schema(description = "一般用作密码", required = true) + protected String passWord; + +} \ No newline at end of file -- 2.34.1 From af1283fc0743e38d93a47d0e17c846f97d1fb9c5 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Mon, 16 Dec 2024 14:18:05 +0800 Subject: [PATCH 029/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/common/filter/AuthFilter.java | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/filter/AuthFilter.java diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/filter/AuthFilter.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/filter/AuthFilter.java new file mode 100644 index 0000000..2c7e885 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/filter/AuthFilter.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.filter; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.yami.shop.common.exception.YamiShopBindException; +import com.yami.shop.common.handler.HttpHandler; +import com.yami.shop.common.response.ResponseEnum; +import com.yami.shop.common.response.ServerResponseEntity; +import com.yami.shop.security.common.adapter.AuthConfigAdapter; +import com.yami.shop.security.common.bo.UserInfoInTokenBO; +import com.yami.shop.security.common.manager.TokenStore; +import com.yami.shop.security.common.util.AuthUserContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * 授权过滤类,主要负责对请求进行授权相关的校验过滤操作。 + * 只要实现AuthConfigAdapter接口,添加对应路径即可实现灵活的授权路径配置,以此来控制哪些请求需要授权,哪些不需要授权访问。 + * 它在整个系统的安全机制中处于关键位置,是保障系统资源按权限访问的重要防线。 + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@Component +public class AuthFilter implements Filter { + + // 用于记录日志,方便在运行过程中输出相关的调试、错误等信息,便于排查问题 + private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class); + + // 注入AuthConfigAdapter接口的实现类,通过它可以获取到配置的需要授权和不需要授权的路径信息 + @Autowired + private AuthConfigAdapter authConfigAdapter; + + // 注入HttpHandler,用于处理向Web端输出响应相关的操作,比如将服务器响应信息正确地返回给前端页面 + @Autowired + private HttpHandler httpHandler; + + // 注入TokenStore,主要用于管理和操作与令牌(Token)相关的存储及查询等功能,例如从存储中获取用户信息等 + @Autowired + private TokenStore tokenStore; + + // 通过配置文件注入令牌名称,用于后续从请求头中获取对应的令牌信息 + @Value("${sa-token.token-name}") + private String tokenName; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + Filter.super.init(filterConfig); + } + + /** + * 核心的过滤方法,对每个经过该过滤器的请求进行处理,判断是否需要授权等操作。 + * + * @param request 当前的Servlet请求对象,在这里被转换为HttpServletRequest以获取更多HTTP相关的请求信息 + * @param response 当前的Servlet响应对象,在这里被转换为HttpServletResponse用于后续向客户端返回响应 + * @param chain 过滤器链,用于将请求传递给下一个过滤器或者最终的目标资源(如Servlet、JSP页面等) + * @throws IOException 如果在处理请求或响应的输入输出流时出现错误则抛出此异常 + * @throws ServletException 如果在Servlet处理过程中出现其他异常则抛出此异常 + */ + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + // 将ServletRequest转换为HttpServletRequest,方便获取HTTP请求相关的属性和信息,比如请求路径、请求头信息等 + HttpServletRequest req = (HttpServletRequest) request; + // 将ServletResponse转换为HttpServletResponse,便于后续进行HTTP响应相关的操作,比如设置响应状态码、响应头、输出响应内容等 + HttpServletResponse resp = (HttpServletResponse) response; + + // 获取当前请求的URI(统一资源标识符),用于后续与配置的授权路径进行匹配判断 + String requestUri = req.getRequestURI(); + + // 通过AuthConfigAdapter获取不需要授权的路径列表,用于判断当前请求是否命中这些无需授权的路径 + List excludePathPatterns = authConfigAdapter.excludePathPatterns(); + + // 创建AntPathMatcher对象,用于进行路径的匹配操作,它支持通配符等方式来灵活匹配路径 + AntPathMatcher pathMatcher = new AntPathMatcher(); + // 如果不需要授权的路径列表不为空,就遍历这些路径,检查当前请求的URI是否匹配其中某个无需授权的路径模式 + if (CollectionUtil.isNotEmpty(excludePathPatterns)) { + for (String excludePathPattern : excludePathPatterns) { + // 使用AntPathMatcher进行路径匹配,如果匹配成功,说明当前请求是不需要授权的,直接将请求传递给下一个过滤器或目标资源 + if (pathMatcher.match(excludePathPattern, requestUri)) { + chain.doFilter(req, resp); + return; + } + } + } + + // 从请求头中获取名为tokenName的令牌信息,这个令牌通常用于验证用户的登录状态和权限等 + String accessToken = req.getHeader(tokenName); + // 判断当前请求的URI是否匹配可能需要登录但不登录也能用的路径模式(通过AuthConfigAdapter中定义的常量路径进行匹配) + boolean mayAuth = pathMatcher.match(AuthConfigAdapter.MAYBE_AUTH_URI, requestUri); + + // 用于存储从令牌中解析出来的用户信息,如果后续成功获取到用户信息则会赋值 + UserInfoInTokenBO userInfoInToken = null; + + try { + // 如果获取到的令牌信息不为空(即存在令牌),说明用户可能已经登录,需要进一步验证并获取用户信息 + if (StrUtil.isNotBlank(accessToken)) { + // 使用StpUtil工具类来校验用户是否已经登录,若登录验证失败会抛出异常,在这里捕获异常并进行相应处理 + try { + StpUtil.checkLogin(); + } catch (Exception e) { + // 如果登录校验失败,通过HttpHandler将表示未授权的服务器响应信息输出到Web端(通常是返回给前端页面显示相应的错误提示) + httpHandler.printServerResponseToWeb(ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED)); + return; + } + // 如果登录校验通过,从TokenStore中根据令牌获取对应的用户信息(从缓存等存储中查询并解析用户信息) + userInfoInToken = tokenStore.getUserInfoByAccessToken(accessToken, true); + } else if (!mayAuth) { + // 如果没有令牌且当前请求也不属于可能不需要登录就能访问的路径,那么说明该请求需要授权但未提供有效令牌,返回表示未授权的响应给前端 + httpHandler.printServerResponseToWeb(ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED)); + return; + } + // 将获取到的用户信息保存到AuthUserContext中,方便在后续的请求处理过程中(比如在其他地方需要获取当前登录用户信息时)可以直接获取 + AuthUserContext.set(userInfoInToken); + + // 如果前面的授权校验等操作都通过了,将请求传递给下一个过滤器或者最终的目标资源(如业务处理的Servlet等)继续处理 + chain.doFilter(req, resp); + + } catch (Exception e) { + // 手动捕获非controller层抛出的异常,进行统一处理 + if (e instanceof YamiShopBindException) { + // 如果是YamiShopBindException类型的异常,通过HttpHandler将该异常对应的响应信息输出到Web端 + httpHandler.printServerResponseToWeb((YamiShopBindException) e); + } else { + // 如果是其他类型的异常,直接抛出,让上层的异常处理机制(比如容器的异常处理)去进一步处理 + throw e; + } + } finally { + // 无论请求处理过程是否出现异常,最终都要清理AuthUserContext中的用户信息,避免数据残留影响下一次请求处理 + AuthUserContext.clean(); + } + } + + @Override + public void destroy() { + Filter.super.destroy(); + } +} \ No newline at end of file -- 2.34.1 From 61e04e3a89381d5e23b4c9a1a3c52df7686db582 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Mon, 16 Dec 2024 14:23:42 +0800 Subject: [PATCH 030/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/common/util/AuthUserContext.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/util/AuthUserContext.java diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/util/AuthUserContext.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/util/AuthUserContext.java new file mode 100644 index 0000000..cd9377d --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/util/AuthUserContext.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.util; + +import com.alibaba.ttl.TransmittableThreadLocal; +import com.yami.shop.security.common.bo.UserInfoInTokenBO; + +/** + * 这个类主要用于管理当前用户在整个线程上下文中的相关信息(具体是存储从令牌中解析出来的用户信息)。 + * 它利用了 `TransmittableThreadLocal` 来实现线程间的数据传递,方便在不同的代码位置都能获取到当前登录用户的相关信息, + * 在整个系统的权限验证及业务处理流程中起着传递用户上下文信息的重要作用。 + * + * @author FrozenWatermelon + * @date 2020/7/16 + */ +public class AuthUserContext { + + // 使用 `TransmittableThreadLocal` 来创建一个线程本地变量,用于存储 `UserInfoInTokenBO` 类型的用户信息对象。 + // `TransmittableThreadLocal` 相较于普通的 `ThreadLocal` 具有能在线程间传递数据的优势, + // 适合在多线程环境下(比如异步调用、线程池场景等)保证用户信息的正确传递和共享。 + private static final ThreadLocal USER_INFO_IN_TOKEN_HOLDER = new TransmittableThreadLocal<>(); + + /** + * 获取存储在当前线程上下文中的用户信息对象(从令牌中解析出的用户信息)。 + * 通过调用 `USER_INFO_IN_TOKEN_HOLDER` 的 `get` 方法来获取对应的 `UserInfoInTokenBO` 对象, + * 如果当前线程上下文中没有设置该信息,则会返回 `null`。 + * + * @return 当前线程上下文中存储的 `UserInfoInTokenBO` 对象,代表从令牌中解析出的用户信息,可能为 `null`。 + */ + public static UserInfoInTokenBO get() { + return USER_INFO_IN_TOKEN_HOLDER.get(); + } + + /** + * 设置当前线程上下文中的用户信息对象(将从令牌中解析出的用户信息存入线程本地变量中)。 + * 调用 `USER_INFO_IN_TOKEN_HOLDER` 的 `set` 方法来保存传入的 `UserInfoInTokenBO` 对象, + * 这样在当前线程后续的执行过程中,其他地方可以通过 `get` 方法获取到这个设置好的用户信息。 + * + * @param userInfoInTokenBo 要设置的 `UserInfoInTokenBO` 对象,即从令牌中解析出的用户信息。 + */ + public static void set(UserInfoInTokenBO userInfoInTokenBo) { + USER_INFO_IN_TOKEN_HOLDER.set(userInfoInTokenBo); + } + + /** + * 清理当前线程上下文中存储的用户信息对象(从线程本地变量中移除用户信息)。 + * 首先判断当前线程上下文中是否已经设置了用户信息(即 `USER_INFO_IN_TOKEN_HOLDER.get()` 是否不为 `null`), + * 如果不为 `null`,则调用 `USER_INFO_IN_TOKEN_HOLDER` 的 `remove` 方法将其移除, + * 确保线程本地变量不会残留上一次的用户信息,避免对后续请求处理产生干扰。 + */ + public static void clean() { + if (USER_INFO_IN_TOKEN_HOLDER.get()!= null) { + USER_INFO_IN_TOKEN_HOLDER.remove(); + } + } +} \ No newline at end of file -- 2.34.1 From 3b2bff654bc7fb13ccd22e580b547cd52d4e33b5 Mon Sep 17 00:00:00 2001 From: ph6vsnc9i <2218893456@qq.com> Date: Mon, 16 Dec 2024 19:26:18 +0800 Subject: [PATCH 031/130] Add FangJianJun_branch --- FangJianJun_branch | 1 + 1 file changed, 1 insertion(+) create mode 100644 FangJianJun_branch diff --git a/FangJianJun_branch b/FangJianJun_branch new file mode 100644 index 0000000..66dc905 --- /dev/null +++ b/FangJianJun_branch @@ -0,0 +1 @@ +undefined \ No newline at end of file -- 2.34.1 From 18d4c3fb8fcc09f21ced45d085343e6c97a31132 Mon Sep 17 00:00:00 2001 From: grey <2300486727@qq.com> Date: Mon, 16 Dec 2024 19:46:17 +0800 Subject: [PATCH 032/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/bean/app/param/AddrParam.java | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/app/param/AddrParam.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/app/param/AddrParam.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/param/AddrParam.java new file mode 100644 index 0000000..8b3bb5b --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/param/AddrParam.java @@ -0,0 +1,189 @@ +package com.yami.shop.bean.app.param; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +/** + * AddrParam类用于封装与地址相关的参数信息,在应用层(app)传递地址相关的数据时使用。 + * 此类中的各个属性对应地址的不同组成部分,并且通过相关注解进行了验证约束和描述说明,方便数据的校验与接口文档生成。 + * + * @author lanhai + */ +@Schema(description = "地址参数") +public class AddrParam { + + /** + * 地址的唯一标识符,用于在系统中区分不同的地址记录。 + * 通过 @Schema 注解描述该属性在接口文档中的相关信息,设置为必填项(required=true)。 + */ + @Schema(description = "地址ID", required = true) + private Long addrId; + + /** + * 收货人的姓名,是地址信息中重要的一部分,不能为空,通过 @NotNull 注解进行验证约束, + * 同时使用 @Schema 注解在接口文档中进行相应描述,且设置为必填项。 + */ + @NotNull(message = "收货人不能为空") + @Schema(description = "收货人", required = true) + private String receiver; + + /** + * 详细的地址内容,例如街道名称、门牌号等具体的收货地址信息,不能为空, + * 利用 @NotNull 约束验证,通过 @Schema 注解在接口文档中说明,设置为必填项。 + */ + @NotNull(message = "地址不能为空") + @Schema(description = "地址", required = true) + private String addr; + + /** + * 地址对应的邮编信息,该属性为可选填项(required=false),通过 @Schema 注解在接口文档中体现其属性描述。 + */ + @Schema(description = "邮编", required = false) + private String postCode; + + /** + * 收货人联系用的手机号码,是确保能联系到收货人的关键信息,不能为空, + * 使用 @NotNull 进行约束,借助 @Schema 注解在接口文档里标记为必填项并描述。 + */ + @NotNull(message = "手机不能为空") + @Schema(description = "手机", required = true) + private String mobile; + + /** + * 省份的唯一标识符,用于关联对应的省份信息,不能为空, + * 以 @NotNull 做验证,通过 @Schema 注解在接口文档中表明为必填项并描述其用途。 + */ + @NotNull(message = "省ID不能为空") + @Schema(description = "省ID", required = true) + private Long provinceId; + + /** + * 城市的唯一标识符,用于准确关联到具体的城市信息,不能为空, + * 借助 @NotNull 约束验证,利用 @Schema 注解在接口文档里设置为必填项并描述相关情况。 + */ + @NotNull(message = "城市ID不能为空") + @Schema(description = "城市ID", required = true) + private Long cityId; + + /** + * 区(县等)的唯一标识符,用于精确到具体的区(县等)级别的地域信息,不能为空, + * 通过 @NotNull 进行验证,使用 @Schema 注解在接口文档中标记为必填项并给出相应说明。 + */ + @NotNull(message = "区ID不能为空") + @Schema(description = "区ID", required = true) + private Long areaId; + + /** + * 省份的名称,以字符串形式表示具体的省份,不能为空, + * 通过 @NotNull 约束,配合 @Schema 注解在接口文档里设置为必填项并描述其含义。 + */ + @NotNull(message = "省不能为空") + @Schema(description = "省", required = true) + private String province; + + /** + * 城市的名称,用于明确具体的城市称呼,不能为空, + * 以 @NotNull 进行验证,通过 @Schema 注解在接口文档中设置为必填项并说明情况。 + */ + @NotNull(message = "城市不能为空") + @Schema(description = "城市", required = true) + private String city; + + /** + * 区(县等)的名称,具体指出所在的区(县等)的名称,不能为空, + * 通过 @NotNull 约束验证,利用 @Schema 注解在接口文档里标记为必填项并加以说明。 + */ + @NotNull(message = "区不能为空") + @Schema(description = "区", required = true) + private String area; + + // 以下是各个属性的Getter和Setter方法,用于获取和设置对应属性的值 + + public Long getAddrId() { + return addrId; + } + + public void setAddrId(Long addrId) { + this.addrId = addrId; + } + + public String getReceiver() { + return receiver; + } + + public void setReceiver(String receiver) { + this.receiver = receiver; + } + + public String getAddr() { + return addr; + } + + public void setAddr(String addr) { + this.addr = addr; + } + + public String getPostCode() { + return postCode; + } + + public void setPostCode(String postCode) { + this.postCode = postCode; + } + + public String getMobile() { + return mobile; + } + + public void setMobile(String mobile) { + this.mobile = mobile; + } + + public Long getProvinceId() { + return provinceId; + } + + public void setProvinceId(Long provinceId) { + this.provinceId = provinceId; + } + + public Long getCityId() { + return cityId; + } + + public void setCityId(Long cityId) { + this.cityId = cityId; + } + + public Long getAreaId() { + return areaId; + } + + public void setAreaId(Long areaId) { + this.areaId = areaId; + } + + public String getProvince() { + return province; + } + + public void setProvince(String province) { + this.province = province; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } +} \ No newline at end of file -- 2.34.1 From a0df4333b14501de98771f47bd07b3645d31977c Mon Sep 17 00:00:00 2001 From: grey <2300486727@qq.com> Date: Mon, 16 Dec 2024 19:48:35 +0800 Subject: [PATCH 033/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yami/shop/bean/model/Area.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/model/Area.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Area.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Area.java new file mode 100644 index 0000000..6a2a9bc --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Area.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * Area类用于表示地区相关的实体信息,对应数据库中的"tz_area"表结构, + * 通过使用MyBatis Plus的相关注解来映射数据库表字段,同时利用Lombok的 @Data 注解简化了实体类的Getter、Setter等方法的编写。 + * 此类主要用于在系统中传递、操作和存储地区相关的数据,比如地区的基本信息以及其下级地区列表等情况。 + * + * @author lanhai + */ +@Data +@TableName("tz_area") // 表明该实体类对应的数据库表名称为"tz_area",方便MyBatis Plus进行数据库操作时的表关联映射 +public class Area implements Serializable { + private static final long serialVersionUID = -6013320537436191451L; + + /** + * 地区的唯一标识符,通过 @TableId 注解标记其为数据库表中的主键字段, + * 利用 @Schema 注解描述该属性在接口文档中的相关信息,设置为必填项(required=true),用于在系统中唯一标识一个地区记录。 + */ + @TableId + @Schema(description = "地区id", required = true) + private Long areaId; + + /** + * 地区的名称,用于直观展示地区的称呼,通过 @Schema 注解在接口文档中进行相应描述,且设置为必填项, + * 比如可以是省份名称、城市名称或者区(县)名称等具体的地区称谓。 + */ + @Schema(description = "地区名称", required = true) + private String areaName; + + /** + * 地区的上级地区的唯一标识符,用于构建地区之间的层级关系,即指明当前地区隶属于哪个上级地区, + * 通过 @Schema 注解在接口文档中表明其为必填项,方便在查询地区层级结构等操作时进行关联查找。 + */ + @Schema(description = "地区上级id", required = true) + private Long parentId; + + /** + * 地区所处的层级,例如省级可以是1级,市级可以是2级,区(县)级可以是3级等, + * 通过 @Schema 注解在接口文档里设置为必填项并描述其用途,用于清晰地体现地区在整个地域层级体系中的位置。 + */ + @Schema(description = "地区层级", required = true) + private Integer level; + + /** + * 该字段用于存储当前地区的下级地区列表,通过 @TableField(exist=false) 注解表示此属性并非数据库表中的实际字段, + * 而是在业务逻辑处理过程中,用于临时存放其下级地区信息,比如在构建地区树形结构等操作时会使用到,方便进行层级关联展示等功能。 + */ + @TableField(exist = false) + private List areas; +} \ No newline at end of file -- 2.34.1 From dae45cc1bcc7cfe1701768a7c4e0f71481be613f Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 19:50:01 +0800 Subject: [PATCH 034/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/api/controller/AddrController.java | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/controller/AddrController.java diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/AddrController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/AddrController.java new file mode 100644 index 0000000..9e0acc5 --- /dev/null +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/AddrController.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.api.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.yami.shop.bean.app.dto.UserAddrDto; +import com.yami.shop.bean.app.param.AddrParam; +import com.yami.shop.bean.model.UserAddr; +import com.yami.shop.common.exception.YamiShopBindException; +import com.yami.shop.common.response.ServerResponseEntity; +import com.yami.shop.security.api.util.SecurityUtils; +import com.yami.shop.service.UserAddrService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import cn.hutool.core.bean.BeanUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.Valid; +import java.util.Date; +import java.util.List; + +/** + * @author lanhai + */ +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@RequestMapping("/p/address") // 定义请求路径的根地址为/p/address +@Tag(name = "地址接口") // 给API文档添加标签,描述这个控制器的功能 +@AllArgsConstructor // Lombok注解,用于生成全参构造函数 +public class AddrController { + + @Autowired + private UserAddrService userAddrService; // 自动注入用户地址服务类 + + /** + * 选择订单配送地址 + */ + @GetMapping("/list") + @Operation(summary = "用户地址列表" , description = "获取用户的所有地址信息") + public ServerResponseEntity> dvyList() { + // 获取当前用户ID + String userId = SecurityUtils.getUser().getUserId(); + // 查询用户的所有地址信息,并按是否常用和更新时间倒序排序 + List userAddrs = userAddrService.list( + new LambdaQueryWrapper() + .eq(UserAddr::getUserId, userId) + .orderByDesc(UserAddr::getCommonAddr) + .orderByDesc(UserAddr::getUpdateTime) + ); + // 将地址信息转换成DTO对象并返回 + return ServerResponseEntity.success(BeanUtil.copyToList(userAddrs, UserAddrDto.class)); + } + + @PostMapping("/addAddr") + @Operation(summary = "新增用户地址" , description = "新增用户地址") + public ServerResponseEntity addAddr(@Valid @RequestBody AddrParam addrParam) { + String userId = SecurityUtils.getUser().getUserId(); + + // 检查地址是否已存在 + if (addrParam.getAddrId() != null && addrParam.getAddrId() != 0) { + return ServerResponseEntity.showFailMsg("该地址已存在"); + } + + // 统计用户已有的地址数量 + long addrCount = userAddrService.count(new LambdaQueryWrapper().eq(UserAddr::getUserId, userId)); + UserAddr userAddr = BeanUtil.copyProperties(addrParam, UserAddr.class); + + // 如果没有地址,设为常用地址,否则设为非常用 + if (addrCount == 0) { + userAddr.setCommonAddr(1); + } else { + userAddr.setCommonAddr(0); + } + + userAddr.setUserId(userId); + userAddr.setStatus(1); // 设置状态为有效 + userAddr.setCreateTime(new Date()); // 设置创建时间 + userAddr.setUpdateTime(new Date()); // 设置更新时间 + + // 保存地址信息 + userAddrService.save(userAddr); + + // 如果是常用地址,清除默认地址缓存 + if (userAddr.getCommonAddr() == 1) { + userAddrService.removeUserAddrByUserId(0L, userId); + } + return ServerResponseEntity.success("添加地址成功"); + } + + /** + * 修改订单配送地址 + */ + @PutMapping("/updateAddr") + @Operation(summary = "修改订单用户地址" , description = "修改用户地址") + public ServerResponseEntity updateAddr(@Valid @RequestBody AddrParam addrParam) { + String userId = SecurityUtils.getUser().getUserId(); + + // 根据用户ID和地址ID获取地址信息 + UserAddr dbUserAddr = userAddrService.getUserAddrByUserId(addrParam.getAddrId(), userId); + if (dbUserAddr == null) { + return ServerResponseEntity.showFailMsg("该地址已被删除"); + } + + // 更新地址信息 + UserAddr userAddr = BeanUtil.copyProperties(addrParam, UserAddr.class); + userAddr.setUserId(userId); + userAddr.setUpdateTime(new Date()); + userAddrService.updateById(userAddr); + + // 清除缓存 + userAddrService.removeUserAddrByUserId(addrParam.getAddrId(), userId); + userAddrService.removeUserAddrByUserId(0L, userId); + + return ServerResponseEntity.success("修改地址成功"); + } + + /** + * 删除订单配送地址 + */ + @DeleteMapping("/deleteAddr/{addrId}") + @Operation(summary = "删除订单用户地址" , description = "根据地址id,删除用户地址") + @Parameter(name = "addrId", description = "地址ID" , required = true) + public ServerResponseEntity deleteDvy(@PathVariable("addrId") Long addrId) { + String userId = SecurityUtils.getUser().getUserId(); + + // 根据用户ID和地址ID获取地址信息 + UserAddr userAddr = userAddrService.getUserAddrByUserId(addrId, userId); + if (userAddr == null) { + return ServerResponseEntity.showFailMsg("该地址已被删除"); + } + + // 检查是否为默认地址,默认地址无法删除 + if (userAddr.getCommonAddr() == 1) { + return ServerResponseEntity.showFailMsg("默认地址无法删除"); + } + + // 删除地址信息 + userAddrService.removeById(addrId); + userAddrService.removeUserAddrByUserId(addrId, userId); + return ServerResponseEntity.success("删除地址成功"); + } + + /** + * 设置默认地址 + */ + @PutMapping("/defaultAddr/{addrId}") + @Operation(summary = "设置默认地址" , description = "根据地址id,设置默认地址") + public ServerResponseEntity defaultAddr(@PathVariable("addrId") Long addrId) { + String userId = SecurityUtils.getUser().getUserId(); + + // 更新默认地址 + userAddrService.updateDefaultUserAddr(addrId, userId); + + // 清除缓存 + userAddrService.removeUserAddrByUserId(0L, userId); + userAddrService.removeUserAddrByUserId(addrId, userId); + + return ServerResponseEntity.success("修改地址成功"); + } + + /** + * 获取地址信息订单配送地址 + */ + @GetMapping("/addrInfo/{addrId}") + @Operation(summary = "获取地址信息" , description = "根据地址id,获取地址信息") + @Parameter(name = "addrId", description = "地址ID" , required = true) + public ServerResponseEntity addrInfo(@PathVariable("addrId") Long addrId) { + String userId = SecurityUtils.getUser().getUserId(); + + // 根据用户ID和地址ID获取地址信息 + UserAddr userAddr = userAddrService.getUserAddrByUserId(addrId, userId); + if (userAddr == null) { + throw new YamiShopBindException("该地址已被删除"); + } + + // 转换为DTO对象并返回 + return ServerResponseEntity.success(BeanUtil.copyProperties(userAddr, UserAddrDto.class)); + } + +} -- 2.34.1 From 297d996a06eca51d173e50146b3c224c8433e01b Mon Sep 17 00:00:00 2001 From: grey <2300486727@qq.com> Date: Mon, 16 Dec 2024 19:51:11 +0800 Subject: [PATCH 035/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/bean/enums/AreaLevelEnum.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/enums/AreaLevelEnum.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/enums/AreaLevelEnum.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/enums/AreaLevelEnum.java new file mode 100644 index 0000000..971d261 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/enums/AreaLevelEnum.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.enums; + +/** + * AreaLevelEnum 是一个枚举类,用于定义地区层级相关的常量值,方便在系统中统一表示和区分不同层级的地区, + * 例如省级、市级、区(县)级等不同层级可以对应此枚举中的不同常量,使得代码在处理地区层级相关逻辑时更加清晰和规范。 + * + * @author cl + */ +public enum AreaLevelEnum { + + /** + * FIRST_LEVEL 代表地区层级中的第一层,通常可以用于表示最高层级的地区,比如省级地区, + * 其对应的整数值为 1,在数据库存储或者业务逻辑判断地区层级时可以依据此值进行相关操作。 + */ + FIRST_LEVEL(1), + + /** + * SECOND_LEVEL 表示地区层级中的第二层,一般可以对应市级地区, + * 其整数值为 2,方便在构建地区层级关系以及相关业务处理中识别和区分该层级的地区。 + */ + SECOND_LEVEL(2), + + /** + * THIRD_LEVEL 指代地区层级中的第三层,常见用于表示区(县)级等更细化的地区层级, + * 对应的数值是 3,有助于在涉及地区层级相关的查询、展示等功能中准确判断和处理相应层级的地区信息。 + */ + THIRD_LEVEL(3) + + ; + + /** + * 用于存储每个枚举常量对应的整数值,该值可以用于在业务逻辑中与数据库中的地区层级字段值进行对比等操作, + * 以此来确定地区所处的具体层级情况。 + */ + private Integer num; + + /** + * 获取当前枚举常量对应的整数值的方法,外部代码可以通过调用此方法获取具体的数值, + * 便于在各种业务场景下(如数据存储、层级判断等)使用该数值进行后续操作。 + * + * @return 当前枚举常量对应的整数值 + */ + public Integer value() { + return num; + } + + /** + * 枚举类的构造方法,用于初始化每个枚举常量对应的整数值,在定义枚举常量时传入相应的整数值进行赋值, + * 确保每个枚举常量都有与之对应的表示地区层级的数值。 + * + * @param num 用于表示地区层级的整数值 + */ + AreaLevelEnum(Integer num) { + this.num = num; + } + + /** + * 根据传入的整数值查找对应的 AreaLevelEnum 枚举常量的静态方法, + * 在业务逻辑中如果已知地区层级的数值表示,可通过调用此方法获取对应的枚举常量, + * 方便进行基于枚举的后续操作,比如根据层级进行不同的业务处理逻辑分支等。 + * 如果传入的数值没有对应的枚举常量匹配,则返回 null。 + * + * @param value 表示地区层级的整数值 + * @return 对应的 AreaLevelEnum 枚举常量,如果不存在匹配则返回 null + */ + public static AreaLevelEnum instance(Integer value) { + AreaLevelEnum[] enums = values(); + for (AreaLevelEnum statusEnum : enums) { + if (statusEnum.value().equals(value)) { + return statusEnum; + } + } + return null; + } +} \ No newline at end of file -- 2.34.1 From 02778a84e0b1181f8fd4a9f78ff44f024b9dda5d Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 20:09:40 +0800 Subject: [PATCH 036/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminLoginController.java | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AdminLoginController.java diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AdminLoginController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AdminLoginController.java new file mode 100644 index 0000000..1326f2c --- /dev/null +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AdminLoginController.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.admin.controller; // 定义类所在的包 + +import cn.hutool.core.util.StrUtil; // 引入Hutool工具类库中的StrUtil工具类,用于字符串操作 +import com.anji.captcha.model.common.ResponseModel; // 引入验证码响应模型 +import com.anji.captcha.model.vo.CaptchaVO; // 引入验证码模型 +import com.anji.captcha.service.CaptchaService; // 引入验证码服务 +import com.baomidou.mybatisplus.core.toolkit.Wrappers; // 引入MyBatis-Plus工具类Wrappers +import com.yami.shop.common.exception.YamiShopBindException; // 引入自定义异常类 +import com.yami.shop.common.response.ServerResponseEntity; // 引入服务器响应实体类 +import com.yami.shop.security.admin.dto.CaptchaAuthenticationDTO; // 引入验证码认证DTO +import com.yami.shop.security.common.bo.UserInfoInTokenBO; // 引入用户信息类 +import com.yami.shop.security.common.enums.SysTypeEnum; // 引入系统类型枚举 +import com.yami.shop.security.common.manager.PasswordCheckManager; // 引入密码校验管理类 +import com.yami.shop.security.common.manager.PasswordManager; // 引入密码管理类 +import com.yami.shop.security.common.manager.TokenStore; // 引入Token存储管理类 +import com.yami.shop.security.common.vo.TokenInfoVO; // 引入Token信息VO +import com.yami.shop.sys.constant.Constant; // 引入系统常量 +import com.yami.shop.sys.model.SysMenu; // 引入系统菜单模型 +import com.yami.shop.sys.model.SysUser; // 引入系统用户模型 +import com.yami.shop.sys.service.SysMenuService; // 引入系统菜单服务 +import com.yami.shop.sys.service.SysUserService; // 引入系统用户服务 +import io.swagger.v3.oas.annotations.Operation; // 引入Swagger的Operation注解 +import io.swagger.v3.oas.annotations.tags.Tag; // 引入Swagger的Tag注解 +import org.springframework.beans.factory.annotation.Autowired; // 引入Spring的@Autowired注解 +import org.springframework.web.bind.annotation.PostMapping; // 引入Spring的@PostMapping注解 +import org.springframework.web.bind.annotation.RequestBody; // 引入Spring的@RequestBody注解 +import org.springframework.web.bind.annotation.RestController; // 引入Spring的@RestController注解 + +import jakarta.validation.Valid; // 引入Jakarta Validation的Valid注解 +import java.util.Arrays; // 引入Java的Arrays工具类 +import java.util.List; // 引入Java的List接口 +import java.util.Objects; // 引入Java的Objects工具类 +import java.util.Set; // 引入Java的Set接口 +import java.util.stream.Collectors; // 引入Java的Collectors工具类 + +/** + * AdminLoginController类,用于处理管理员登录逻辑。 + * 该类包含一个登录方法和一个获取用户权限的方法。 + * 通过账号/手机号/用户名密码登录,进行验证码校验和密码校验,生成Token。 + * @作者 FrozenWatermelon + * @日期 2020/6/30 + */ +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@Tag(name = "登录") // 给API文档添加标签,描述这个控制器的功能 +public class AdminLoginController { + + @Autowired + private TokenStore tokenStore; // 自动注入Token存储管理类 + + @Autowired + private SysUserService sysUserService; // 自动注入系统用户服务 + + @Autowired + private SysMenuService sysMenuService; // 自动注入系统菜单服务 + + @Autowired + private PasswordCheckManager passwordCheckManager; // 自动注入密码校验管理类 + + @Autowired + private CaptchaService captchaService; // 自动注入验证码服务 + + @Autowired + private PasswordManager passwordManager; // 自动注入密码管理类 + + /** + * 账号密码 + 验证码登录(用于后台登录) + * 通过账号/手机号/用户名密码登录,并进行验证码校验和密码校验。 + * @param captchaAuthenticationDTO 包含账号、密码和验证码的DTO对象 + * @return 服务器响应实体,包含登录成功后的Token信息 + */ + @PostMapping("/adminLogin") + @Operation(summary = "账号密码 + 验证码登录(用于后台登录)" , description = "通过账号/手机号/用户名密码登录") + public ServerResponseEntity login( + @Valid @RequestBody CaptchaAuthenticationDTO captchaAuthenticationDTO) { + // 登陆后台登录需要再校验一遍验证码 + CaptchaVO captchaVO = new CaptchaVO(); + captchaVO.setCaptchaVerification(captchaAuthenticationDTO.getCaptchaVerification()); + ResponseModel response = captchaService.verification(captchaVO); + if (!response.isSuccess()) { + return ServerResponseEntity.showFailMsg("验证码有误或已过期"); + } + + SysUser sysUser = sysUserService.getByUserName(captchaAuthenticationDTO.getUserName()); + if (sysUser == null) { + throw new YamiShopBindException("账号或密码不正确"); + } + + // 半小时内密码输入错误十次,已限制登录30分钟 + String decryptPassword = passwordManager.decryptPassword(captchaAuthenticationDTO.getPassWord()); + passwordCheckManager.checkPassword(SysTypeEnum.ADMIN, captchaAuthenticationDTO.getUserName(), decryptPassword, sysUser.getPassword()); + + // 不是店铺超级管理员,并且是禁用状态,无法登录 + if (Objects.equals(sysUser.getStatus(), 0)) { + // 未找到此用户信息 + throw new YamiShopBindException("未找到此用户信息"); + } + + UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO(); + userInfoInToken.setUserId(String.valueOf(sysUser.getUserId())); + userInfoInToken.setSysType(SysTypeEnum.ADMIN.value()); + userInfoInToken.setEnabled(sysUser.getStatus() == 1); + userInfoInToken.setPerms(getUserPermissions(sysUser.getUserId())); + userInfoInToken.setNickName(sysUser.getUsername()); + userInfoInToken.setShopId(sysUser.getShopId()); + // 存储token返回vo + TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken); + return ServerResponseEntity.success(tokenInfoVO); + } + + /** + * 获取用户权限 + * 根据用户ID获取用户的所有权限,如果是超级管理员,拥有最高权限。 + * @param userId 用户ID + * @return 用户权限的Set集合 + */ + private Set getUserPermissions(Long userId) { + List permsList; + + //系统管理员,拥有最高权限 + if(userId == Constant.SUPER_ADMIN_ID){ + List menuList = sysMenuService.list(Wrappers.emptyWrapper()); + permsList = menuList.stream().map(SysMenu::getPerms).collect(Collectors.toList()); + }else{ + permsList = sysUserService.queryAllPerms(userId); + } + return permsList.stream().flatMap((perms) -> { + if (StrUtil.isBlank(perms)) { + return null; + } + return Arrays.stream(perms.trim().split(StrUtil.COMMA)); + } + ).collect(Collectors.toSet()); + } +} -- 2.34.1 From 4432efe70c5be488bf8eff11c9a43e8bf166e1b7 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 20:14:44 +0800 Subject: [PATCH 037/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/api/config/ApiBeanConfig.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/config/ApiBeanConfig.java diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/config/ApiBeanConfig.java b/yami-shop-api/src/main/java/com/yami/shop/api/config/ApiBeanConfig.java new file mode 100644 index 0000000..256a117 --- /dev/null +++ b/yami-shop-api/src/main/java/com/yami/shop/api/config/ApiBeanConfig.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.api.config; // 定义类所在的包 + +import cn.hutool.core.lang.Snowflake; // 引入Hutool工具包中的Snowflake类,用于生成唯一ID +import lombok.AllArgsConstructor; // 引入Lombok的@AllArgsConstructor注解,自动生成全参构造函数 +import org.springframework.context.annotation.Bean; // 引入Spring的@Bean注解 +import org.springframework.context.annotation.Configuration; // 引入Spring的@Configuration注解 + +/** + * ApiBeanConfig类,用于配置Snowflake实例,生成唯一ID。 + * @作者 lanhai + */ +@Configuration // 标注这是一个配置类 +@AllArgsConstructor // 使用Lombok注解生成全参构造函数 +public class ApiBeanConfig { + + private final ApiConfig apiConfig; // 自动注入ApiConfig配置类的实例 + + @Bean // 标注这是一个Spring Bean,会被Spring容器管理 + public Snowflake snowflake() { + // 创建并返回一个Snowflake实例,用于生成全局唯一ID + return new Snowflake(apiConfig.getWorkerId(), apiConfig.getDatacenterId()); + } +} -- 2.34.1 From 5cb982a58d3c5ba62b9628883b851be49aec70d6 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 20:24:14 +0800 Subject: [PATCH 038/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/admin/controller/AreaController.java | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AreaController.java diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AreaController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AreaController.java new file mode 100644 index 0000000..1368175 --- /dev/null +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/AreaController.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.admin.controller; // 定义类所在的包 + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; // 引入MyBatis-Plus的条件查询包装器 +import com.baomidou.mybatisplus.core.metadata.IPage; // 引入MyBatis-Plus的分页接口 +import com.yami.shop.bean.enums.AreaLevelEnum; // 引入区域级别枚举 +import com.yami.shop.bean.model.Area; // 引入区域模型 +import com.yami.shop.common.exception.YamiShopBindException; // 引入自定义异常类 +import com.yami.shop.common.util.PageParam; // 引入分页参数工具类 +import com.yami.shop.service.AreaService; // 引入区域服务类 +import org.springframework.beans.factory.annotation.Autowired; // 引入Spring的@Autowired注解 +import com.yami.shop.common.response.ServerResponseEntity; // 引入服务器响应实体类 +import org.springframework.security.access.prepost.PreAuthorize; // 引入Spring Security的PreAuthorize注解 +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.Valid; // 引入Jakarta Validation的Valid注解 +import java.util.List; // 引入Java的List接口 +import java.util.Objects; // 引入Java的Objects工具类 + +/** + * AreaController类,用于管理区域信息。 + * 该类包含了分页获取区域、获取省市、通过父级ID获取区域列表、获取区域信息、保存区域、修改区域和删除区域的方法。 + * @作者 lgh on 2018/10/26. + */ +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@RequestMapping("/admin/area") // 定义请求路径的根地址为/admin/area +public class AreaController { + + @Autowired + private AreaService areaService; // 自动注入区域服务类 + + /** + * 分页获取区域信息 + * @param area 查询条件 + * @param page 分页参数 + * @return 服务器响应实体,包含分页后的区域信息 + */ + @GetMapping("/page") + @PreAuthorize("@pms.hasPermission('admin:area:page')") // 权限检查 + public ServerResponseEntity> page(Area area, PageParam page) { + IPage areaPage = areaService.page(page, new LambdaQueryWrapper()); + return ServerResponseEntity.success(areaPage); + } + + /** + * 获取省市信息 + * @param area 查询条件 + * @return 服务器响应实体,包含查询到的省市信息 + */ + @GetMapping("/list") + @PreAuthorize("@pms.hasPermission('admin:area:list')") // 权限检查 + public ServerResponseEntity> list(Area area) { + List areas = areaService.list(new LambdaQueryWrapper() + .like(area.getAreaName() != null, Area::getAreaName, area.getAreaName())); + return ServerResponseEntity.success(areas); + } + + /** + * 通过父级ID获取区域列表 + * @param pid 父级ID + * @return 服务器响应实体,包含查询到的区域列表 + */ + @GetMapping("/listByPid") + public ServerResponseEntity> listByPid(Long pid) { + List list = areaService.listByPid(pid); + return ServerResponseEntity.success(list); + } + + /** + * 获取区域信息 + * @param id 区域ID + * @return 服务器响应实体,包含查询到的区域信息 + */ + @GetMapping("/info/{id}") + @PreAuthorize("@pms.hasPermission('admin:area:info')") // 权限检查 + public ServerResponseEntity info(@PathVariable("id") Long id) { + Area area = areaService.getById(id); + return ServerResponseEntity.success(area); + } + + /** + * 保存区域信息 + * @param area 区域信息 + * @return 服务器响应实体 + */ + @PostMapping + @PreAuthorize("@pms.hasPermission('admin:area:save')") // 权限检查 + public ServerResponseEntity save(@Valid @RequestBody Area area) { + if (area.getParentId() != null) { + Area parentArea = areaService.getById(area.getParentId()); + area.setLevel(parentArea.getLevel() + 1); + areaService.removeAreaCacheByParentId(area.getParentId()); + } + areaService.save(area); + return ServerResponseEntity.success(); + } + + /** + * 修改区域信息 + * @param area 区域信息 + * @return 服务器响应实体 + */ + @PutMapping + @PreAuthorize("@pms.hasPermission('admin:area:update')") // 权限检查 + public ServerResponseEntity update(@Valid @RequestBody Area area) { + Area areaDb = areaService.getById(area.getAreaId()); + // 判断当前省市区级别,如果是1级、2级则不能修改级别,不能修改成别人的下级 + if(Objects.equals(areaDb.getLevel(), AreaLevelEnum.FIRST_LEVEL.value()) && !Objects.equals(area.getLevel(), AreaLevelEnum.FIRST_LEVEL.value())){ + throw new YamiShopBindException("不能改变一级行政地区的级别"); + } + if(Objects.equals(areaDb.getLevel(), AreaLevelEnum.SECOND_LEVEL.value()) && !Objects.equals(area.getLevel(), AreaLevelEnum.SECOND_LEVEL.value())){ + throw new YamiShopBindException("不能改变二级行政地区的级别"); + } + hasSameName(area); + areaService.updateById(area); + areaService.removeAreaCacheByParentId(area.getParentId()); + return ServerResponseEntity.success(); + } + + /** + * 删除区域信息 + * @param id 区域ID + * @return 服务器响应实体 + */ + @DeleteMapping("/{id}") + @PreAuthorize("@pms.hasPermission('admin:area:delete')") // 权限检查 + public ServerResponseEntity delete(@PathVariable Long id) { + Area area = areaService.getById(id); + areaService.removeById(id); + areaService.removeAreaCacheByParentId(area.getParentId()); + return ServerResponseEntity.success(); + } + + /** + * 判断是否有相同名称的区域 + * @param area 区域信息 + */ + private void hasSameName(Area area) { + long count = areaService.count(new LambdaQueryWrapper() + .eq(Area::getParentId, area.getParentId()) + .eq(Area::getAreaName, area.getAreaName()) + .ne(Objects.nonNull(area.getAreaId()) && !Objects.equals(area.getAreaId(), 0L), Area::getAreaId, area.getAreaId()) + ); + if (count > 0) { + throw new YamiShopBindException("该地区已存在"); + } + } +} \ No newline at end of file -- 2.34.1 From 5b8e019a4761eb3a30a36abe8657cc47d40b10c5 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 20:31:26 +0800 Subject: [PATCH 039/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/api/controller/AreaController.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/controller/AreaController.java diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/AreaController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/AreaController.java new file mode 100644 index 0000000..b00d67e --- /dev/null +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/AreaController.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.api.controller; // 定义类所在的包 + +import java.util.List; // 引入Java的List接口 + +import com.yami.shop.service.AreaService; // 引入区域服务类 +import org.springframework.beans.factory.annotation.Autowired; // 引入Spring的@Autowired注解 +import com.yami.shop.common.response.ServerResponseEntity; // 引入服务器响应实体类 +import org.springframework.web.bind.annotation.GetMapping; // 引入Spring的@GetMapping注解 +import org.springframework.web.bind.annotation.RequestMapping; // 引入Spring的@RequestMapping注解 +import org.springframework.web.bind.annotation.RestController; // 引入Spring的@RestController注解 + +import com.yami.shop.bean.model.Area; // 引入区域模型 + +import io.swagger.v3.oas.annotations.tags.Tag; // 引入Swagger的Tag注解 +import io.swagger.v3.oas.annotations.Parameter; // 引入Swagger的Parameter注解 +import io.swagger.v3.oas.annotations.Operation; // 引入Swagger的Operation注解 + +/** + * AreaController类,负责处理省市区信息的获取请求。 + * 该类包含一个根据父级ID获取省市区信息的方法。 + * @作者 lgh on 2018/10/26. + */ +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@RequestMapping("/p/area") // 定义请求路径的根地址为/p/area +@Tag(name = "省市区接口") // 给API文档添加标签,描述这个控制器的功能 +public class AreaController { + + @Autowired + private AreaService areaService; // 自动注入区域服务类 + + /** + * 根据省市区的父级ID获取地址信息 + * @param pid 省市区的父级ID(pid为0获取所有省份) + * @return 服务器响应实体,包含查询到的省市区信息 + */ + @GetMapping("/listByPid") + @Operation(summary = "获取省市区信息" , description = "根据省市区的pid获取地址信息") + @Parameter(name = "pid", description = "省市区的pid(pid为0获取所有省份)" , required = true) + public ServerResponseEntity> listByPid(Long pid) { + List list = areaService.listByPid(pid); + return ServerResponseEntity.success(list); + } +} -- 2.34.1 From 751d5ffabcecd3eefe6ddf60f406914b30ce8c5b Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 20:49:27 +0800 Subject: [PATCH 040/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/listener/ConfirmOrderListener.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/listener/ConfirmOrderListener.java diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/listener/ConfirmOrderListener.java b/yami-shop-api/src/main/java/com/yami/shop/api/listener/ConfirmOrderListener.java new file mode 100644 index 0000000..f097361 --- /dev/null +++ b/yami-shop-api/src/main/java/com/yami/shop/api/listener/ConfirmOrderListener.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.api.listener; // 定义类所在的包 + +import com.yami.shop.bean.app.dto.ShopCartItemDto; // 引入购物车项目DTO +import com.yami.shop.bean.app.dto.ShopCartOrderDto; // 引入购物车订单DTO +import com.yami.shop.bean.app.param.OrderParam; // 引入订单参数 +import com.yami.shop.bean.event.ConfirmOrderEvent; // 引入确认订单事件 +import com.yami.shop.bean.model.Product; // 引入商品模型 +import com.yami.shop.bean.model.Sku; // 引入SKU模型 +import com.yami.shop.bean.model.UserAddr; // 引入用户地址模型 +import com.yami.shop.bean.order.ConfirmOrderOrder; // 引入确认订单顺序 +import com.yami.shop.common.exception.YamiShopBindException; // 引入自定义异常类 +import com.yami.shop.common.util.Arith; // 引入算术工具类 +import com.yami.shop.security.api.util.SecurityUtils; // 引入安全工具类 +import com.yami.shop.service.ProductService; // 引入商品服务类 +import com.yami.shop.service.SkuService; // 引入SKU服务类 +import com.yami.shop.service.TransportManagerService; // 引入运输管理服务类 +import com.yami.shop.service.UserAddrService; // 引入用户地址服务类 +import lombok.AllArgsConstructor; // 引入Lombok的@AllArgsConstructor注解 +import org.springframework.context.event.EventListener; // 引入Spring的事件监听注解 +import org.springframework.core.annotation.Order; // 引入Spring的Order注解 +import org.springframework.stereotype.Component; // 引入Spring的Component注解 + +/** + * 确认订单信息时的默认操作 + * ConfirmOrderListener类处理确认订单时的默认操作,包括计算订单金额、商品检查和运费计算等。 + * @作者 LGH + */ +@Component("defaultConfirmOrderListener") // 标注这是一个Spring组件,并且以defaultConfirmOrderListener为组件名称 +@AllArgsConstructor // 使用Lombok注解生成全参构造函数 +public class ConfirmOrderListener { + + private final UserAddrService userAddrService; // 自动注入用户地址服务类 + + private final TransportManagerService transportManagerService; // 自动注入运输管理服务类 + + private final ProductService productService; // 自动注入商品服务类 + + private final SkuService skuService; // 自动注入SKU服务类 + + /** + * 计算订单金额 + * @param event 确认订单事件 + */ + @EventListener(ConfirmOrderEvent.class) // 标注这是一个事件监听器,监听ConfirmOrderEvent事件 + @Order(ConfirmOrderOrder.DEFAULT) // 设置事件监听的顺序 + public void defaultConfirmOrderEvent(ConfirmOrderEvent event) { + + ShopCartOrderDto shopCartOrderDto = event.getShopCartOrderDto(); // 获取购物车订单DTO + + OrderParam orderParam = event.getOrderParam(); // 获取订单参数 + + String userId = SecurityUtils.getUser().getUserId(); // 获取当前用户ID + + // 订单的地址信息 + UserAddr userAddr = userAddrService.getUserAddrByUserId(orderParam.getAddrId(), userId); + + double total = 0.0; // 总金额 + + int totalCount = 0; // 总商品数量 + + double transfee = 0.0; // 总运费 + + for (ShopCartItemDto shopCartItem : event.getShopCartItems()) { + // 获取商品信息 + Product product = productService.getProductByProdId(shopCartItem.getProdId()); + // 获取sku信息 + Sku sku = skuService.getSkuBySkuId(shopCartItem.getSkuId()); + if (product == null || sku == null) { + throw new YamiShopBindException("购物车包含无法识别的商品"); // 商品或SKU为空,抛出异常 + } + if (product.getStatus() != 1 || sku.getStatus() != 1) { + throw new YamiShopBindException("商品[" + sku.getProdName() + "]已下架"); // 商品或SKU已下架,抛出异常 + } + + totalCount = shopCartItem.getProdCount() + totalCount; // 累加商品数量 + total = Arith.add(shopCartItem.getProductTotalAmount(), total); // 累加商品总金额 + // 用户地址如果为空,则表示该用户从未设置过任何地址相关信息 + if (userAddr != null) { + // 每个产品的运费相加 + transfee = Arith.add(transfee, transportManagerService.calculateTransfee(shopCartItem, userAddr)); + } + + shopCartItem.setActualTotal(shopCartItem.getProductTotalAmount()); // 设置实际总金额 + shopCartOrderDto.setActualTotal(Arith.add(total, transfee)); // 设置订单实际总金额 + shopCartOrderDto.setTotal(total); // 设置订单总金额 + shopCartOrderDto.setTotalCount(totalCount); // 设置订单总商品数量 + shopCartOrderDto.setTransfee(transfee); // 设置订单总运费 + } + } +} -- 2.34.1 From 022612cf96de0b8470a82b00303cce68155c1116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 22:45:21 +0800 Subject: [PATCH 041/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/bean/model/AttachFile.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/model/AttachFile.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/model/AttachFile.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/AttachFile.java new file mode 100644 index 0000000..f1852d0 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/AttachFile.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018 - 2999 广州市蓝海创新科技有限公司 All rights reserved. + * 版权声明部分,表明该代码文件的版权归属为广州市蓝海创新科技有限公司,版权有效期从2018年至2999年。 + * + * https://www.mall4j.com/ + * 给出了公司相关的网址,可能用于更多信息查询等用途。 + * + * 未经允许,不可做商业用途! + * 强调此代码未经公司许可,不能用于商业方面的使用场景,旨在保护版权所有者的权益。 + * + * 版权所有,侵权必究! + * 明确声明若存在侵犯该代码版权的行为,公司将依法追究责任。 + */ + +package com.yami.shop.bean.model; +// 声明该类所属的包名,用于在项目的代码组织结构中对类进行分类管理,方便代码的组织、查找以及不同模块间的引用。 + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +// 引入MyBatis Plus相关注解和Lombok的Data注解,用于简化代码编写以及实现实体类与数据库表的映射等功能。 + +import java.io.Serializable; +import java.util.Date; +// 引入Java标准库中的接口和类,Serializable接口用于标记该类对象可以被序列化,方便在网络传输、存储等场景下进行对象状态的保存和恢复;Date类用于处理日期相关的数据。 + +/** + * @author lanhai + * 类的作者信息,此处表明该类是由名为lanhai的开发者编写。 + */ +@Data +// Lombok的@Data注解,它会自动为类生成常用的方法,包括所有成员变量的getter和setter方法、toString方法、equals方法以及hashCode方法等,极大地减少了手动编写这些重复代码的工作量,使代码更加简洁。 +@TableName("tz_attach_file") +// MyBatis Plus的@TableName注解,用于将当前的实体类与数据库中的具体表进行映射关联,这里表明AttachFile类对应数据库中的"tz_attach_file"表,方便后续进行数据库操作时框架能够准确地知道操作的数据来源和目标表。 +public class AttachFile implements Serializable { + // 定义一个名为AttachFile的公共类,它实现了Serializable接口,意味着该类的实例对象能够进行序列化操作,比如可以保存到文件、在网络间传输等场景中使用。 + + @TableId + // MyBatis Plus的@TableId注解,用于标识该成员变量对应的是数据库表中的主键字段,在数据库操作(如查询、更新、删除等)中,主键是用于唯一标识每条记录的关键属性。 + private Long fileId; + // 定义一个名为fileId的私有成员变量,类型为Long,用于存储文件在系统中的唯一标识符,通常对应数据库表中主键列的值,通过它可以准确地定位和操作某一条具体的文件相关记录。 + + /** + * 文件路径 + * 对成员变量filePath的功能描述,说明该变量用于存储文件在存储系统中的具体路径信息,例如在文件系统中的绝对路径或者相对路径等,通过这个路径可以找到对应的物理文件进行读取、写入等操作。 + */ + private String filePath; + // 定义一个名为filePath的私有成员变量,类型为String,用于记录文件的存储路径。 + + /** + * 文件类型 + * 此注释说明该成员变量fileType的用途是保存文件的类型信息,常见的文件类型如"txt"(文本文件)、"jpg"(图片文件)、"pdf"(PDF文档)等,通过该变量可以方便地识别文件的格式,进而进行相应格式特定的处理操作。 + */ + private String fileType; + // 定义一个名为fileType的私有成员变量,类型为String,用于存储文件的类型标识。 + + /** + * 文件大小 + * 表明fileSize变量用于记录文件所占用的字节数大小,在文件管理场景中,比如判断文件是否超出存储限制、统计存储资源使用情况等方面,该变量起着重要作用。 + */ + private Integer fileSize; + // 定义一个名为fileSize的私有成员变量,类型为Integer,用于存储文件的大小数值,通常以字节为单位来衡量。 + + /** + * 上传时间 + * 解释uploadTime变量的作用是记录文件被上传到当前系统的具体时间,可用于文件操作历史记录、按照时间顺序对文件进行排序、统计不同时间段内的文件上传情况等功能实现。 + */ + private Date uploadTime; + // 定义一个名为uploadTime的私有成员变量,类型为Date,用于保存文件上传的时间信息。 + + /** + * 文件关联的表主键id + * 说明fileJoinId变量的功能是存储与该文件相关联的其他数据表中记录的主键标识符,通过这个关联主键,可以建立起文件与其他业务数据表之间的联系,实现数据的关联查询、整合等操作。 + */ + private Long fileJoinId; + // 定义一个名为fileJoinId的私有成员变量,类型为Long,用于记录与文件相关联的其他表的主键值。 + + /** + * 文件关联表类型:1 商品表 @see FileJoinType + * 对fileJoinType变量的详细说明,它用于表示文件所关联的表的类型,当前示例中值为1时表示关联的是商品表(更完整的关联类型可能通过FileJoinType枚举类等方式进一步定义规范,通过@see注解提示查看相关定义),通过该变量可以区分文件在不同业务场景下与不同数据表之间的关联关系。 + */ + private Integer fileJoinType; + // 定义一个名为fileJoinType的私有成员变量,类型为Integer,用于标识文件关联表的类型信息。 +} \ No newline at end of file -- 2.34.1 From 65203490817e6c1ab0f6553723218a01de080c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 22:51:53 +0800 Subject: [PATCH 042/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yami/shop/bean/model/Basket.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/model/Basket.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Basket.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Basket.java new file mode 100644 index 0000000..b44c42d --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Basket.java @@ -0,0 +1,75 @@ +package com.yami.shop.bean.model; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * Basket类用于表示购物车相关的实体信息。 + * 它对应数据库中的"tz_basket"表,通过MyBatis Plus的注解来映射表结构与实体类的关系。 + * 此类实现了Serializable接口,方便进行对象的序列化操作,例如在网络传输或持久化存储时使用。 + * + * @author lanhai + */ +@Data +@TableName("tz_basket") +public class Basket implements Serializable { + /** + * 主键,用于唯一标识购物车中的每一条记录。 + * 在数据库表"tz_basket"中对应的字段为主键字段,通常由数据库自动生成或者按照特定规则赋值。 + */ + @TableId + private Long basketId; + + /** + * 店铺ID,用于标识商品所属的店铺。 + * 通过该ID可以关联到对应的店铺信息,比如查询店铺的名称、地址等其他相关属性。 + */ + private Long shopId; + + /** + * 产品ID,用于确定购物车中具体是哪个产品。 + * 可以依据此ID去获取产品的详细信息,如产品名称、价格、描述等内容。 + */ + private Long prodId; + + /** + * SkuID,代表具体的库存保有单位(Stock Keeping Unit)的编号。 + * 不同的Sku可能对应产品的不同规格、颜色、尺寸等变体,通过该ID能准确区分同一产品下的不同库存单元。 + */ + private Long skuId; + + /** + * 用户ID,用于标识该购物车记录所属的用户。 + * 可以通过该ID关联到用户的其他信息,比如用户名、联系方式等,以确定是哪位用户添加的商品到购物车。 + * 这里是用字符串类型,可能是根据具体业务系统中用户ID的格式设定(比如可能包含字母等复杂格式)。 + */ + private String userId; + + /** + * 购物车产品个数,记录了当前购物车中对应商品的数量。 + * 用于在计算购物车总价、库存校验等业务场景中发挥作用。 + */ + private Integer basketCount; + + /** + * 购物时间,记录了用户将商品添加到购物车的具体时间。 + * 可以用于分析用户行为,例如统计不同时间段的购物车添加频率等业务需求。 + */ + private Date basketDate; + + /** + * 满减活动ID,用于关联对应的满减活动。 + * 如果商品参与了某个满减活动,通过该ID可以获取活动的详细规则,如满多少金额减多少等信息,以在结算等环节应用相应优惠。 + */ + private Long discountId; + + /** + * 分销推广人卡号,用于标识在分销业务场景下,将该商品推荐给用户的推广人员的卡号信息。 + * 通过这个卡号可以进行分销相关的业绩统计、佣金计算等操作。 + */ + private String distributionCardNo; +} \ No newline at end of file -- 2.34.1 From c13faef2a4c737a049f95cdf4a1b2b43d250e91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 22:55:19 +0800 Subject: [PATCH 043/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/bean/app/dto/BasketItemDto.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/BasketItemDto.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/BasketItemDto.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/BasketItemDto.java new file mode 100644 index 0000000..b44c42d --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/BasketItemDto.java @@ -0,0 +1,75 @@ +package com.yami.shop.bean.model; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * Basket类用于表示购物车相关的实体信息。 + * 它对应数据库中的"tz_basket"表,通过MyBatis Plus的注解来映射表结构与实体类的关系。 + * 此类实现了Serializable接口,方便进行对象的序列化操作,例如在网络传输或持久化存储时使用。 + * + * @author lanhai + */ +@Data +@TableName("tz_basket") +public class Basket implements Serializable { + /** + * 主键,用于唯一标识购物车中的每一条记录。 + * 在数据库表"tz_basket"中对应的字段为主键字段,通常由数据库自动生成或者按照特定规则赋值。 + */ + @TableId + private Long basketId; + + /** + * 店铺ID,用于标识商品所属的店铺。 + * 通过该ID可以关联到对应的店铺信息,比如查询店铺的名称、地址等其他相关属性。 + */ + private Long shopId; + + /** + * 产品ID,用于确定购物车中具体是哪个产品。 + * 可以依据此ID去获取产品的详细信息,如产品名称、价格、描述等内容。 + */ + private Long prodId; + + /** + * SkuID,代表具体的库存保有单位(Stock Keeping Unit)的编号。 + * 不同的Sku可能对应产品的不同规格、颜色、尺寸等变体,通过该ID能准确区分同一产品下的不同库存单元。 + */ + private Long skuId; + + /** + * 用户ID,用于标识该购物车记录所属的用户。 + * 可以通过该ID关联到用户的其他信息,比如用户名、联系方式等,以确定是哪位用户添加的商品到购物车。 + * 这里是用字符串类型,可能是根据具体业务系统中用户ID的格式设定(比如可能包含字母等复杂格式)。 + */ + private String userId; + + /** + * 购物车产品个数,记录了当前购物车中对应商品的数量。 + * 用于在计算购物车总价、库存校验等业务场景中发挥作用。 + */ + private Integer basketCount; + + /** + * 购物时间,记录了用户将商品添加到购物车的具体时间。 + * 可以用于分析用户行为,例如统计不同时间段的购物车添加频率等业务需求。 + */ + private Date basketDate; + + /** + * 满减活动ID,用于关联对应的满减活动。 + * 如果商品参与了某个满减活动,通过该ID可以获取活动的详细规则,如满多少金额减多少等信息,以在结算等环节应用相应优惠。 + */ + private Long discountId; + + /** + * 分销推广人卡号,用于标识在分销业务场景下,将该商品推荐给用户的推广人员的卡号信息。 + * 通过这个卡号可以进行分销相关的业绩统计、佣金计算等操作。 + */ + private String distributionCardNo; +} \ No newline at end of file -- 2.34.1 From dde746d4d6177618136c4013c69e43b8817c700b Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 22:59:28 +0800 Subject: [PATCH 044/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/DeliveryController.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/controller/DeliveryController.java diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/DeliveryController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/DeliveryController.java new file mode 100644 index 0000000..86c6469 --- /dev/null +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/DeliveryController.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.admin.controller; // 定义类所在的包 + +import java.util.List; // 引入Java的List接口 + +import org.springframework.beans.factory.annotation.Autowired; // 引入Spring的@Autowired注解 +import com.yami.shop.common.response.ServerResponseEntity; // 引入服务器响应实体类 +import org.springframework.web.bind.annotation.GetMapping; // 引入Spring的@GetMapping注解 +import org.springframework.web.bind.annotation.RequestMapping; // 引入Spring的@RequestMapping注解 +import org.springframework.web.bind.annotation.RestController; // 引入Spring的@RestController注解 + +import com.yami.shop.bean.model.Delivery; // 引入配送模型 +import com.yami.shop.service.DeliveryService; // 引入配送服务类 + +/** + * DeliveryController类,用于管理配送信息。 + * 该类包含一个分页获取配送信息的方法。 + * @作者 lgh on 2018/11/26. + */ +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@RequestMapping("/admin/delivery") // 定义请求路径的根地址为/admin/delivery +public class DeliveryController { + + @Autowired + private DeliveryService deliveryService; // 自动注入配送服务类 + + /** + * 分页获取配送信息 + * @return 服务器响应实体,包含配送信息列表 + */ + @GetMapping("/list") + public ServerResponseEntity> page() { + List list = deliveryService.list(); // 获取所有配送信息 + return ServerResponseEntity.success(list); // 返回配送信息列表 + } +} -- 2.34.1 From 682dcc3d15fd4f9efd68c1f745495d79f33c0a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 23:00:47 +0800 Subject: [PATCH 045/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yami/shop/bean/model/Brand.java | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/model/Brand.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Brand.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Brand.java new file mode 100644 index 0000000..8e26872 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Brand.java @@ -0,0 +1,96 @@ +package com.yami.shop.bean.model; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * Brand类用于表示品牌相关的实体信息,对应数据库中的"tz_brand"表, + * 通过MyBatis Plus的注解(如@TableName)来建立实体类与数据库表之间的映射关系, + * 同时实现了Serializable接口,便于对象在诸如网络传输、持久化存储等场景下进行序列化操作。 + * + * @author lanhai + */ +@Data +@TableName("tz_brand") +public class Brand implements Serializable { + /** + * 主键,用于在数据库中唯一标识每一个品牌记录。 + * 在数据库表"tz_brand"中对应的字段是主键字段,是区分不同品牌实体的关键标识, + * 通常在数据插入等操作时由数据库按照一定规则(如自增)来生成或者进行合理赋值。 + */ + @TableId + private Long brandId; + + /** + * 品牌名称,用于展示给用户或者在业务逻辑中区分不同品牌的关键属性。 + * 例如在前端界面的品牌列表展示、商品筛选等场景中,都会依据这个名称来呈现品牌相关信息。 + */ + private String brandName; + + /** + * 图片路径,指向存储品牌相关图片的具体位置。 + * 可以是本地文件系统的路径,也可能是网络存储的URL等形式,通过这个路径能够获取品牌对应的展示图片, + * 比如在品牌详情页面展示品牌logo或者宣传图片等场景会用到该图片。 + */ + private String brandPic; + + /** + * 用户ID,用于标识与该品牌相关操作的用户主体。 + * 例如可能是创建这个品牌的用户、最后更新品牌信息的用户等,通过该ID可以关联到用户的详细信息, + * 像用户名、所属部门等,方便进行权限管理、操作记录追溯等业务处理。 + */ + private String userId; + + /** + * 备注,用于记录一些关于品牌的额外说明信息。 + * 比如品牌的特殊背景、历史沿革、适用场景等补充性的描述内容,便于内部人员更好地了解品牌相关情况。 + */ + private String memo; + + /** + * 顺序,用于定义品牌在某些特定展示或排序场景下的先后次序。 + * 例如在品牌列表页面按照一定规则排序展示时,依据这个顺序字段来排列品牌的先后顺序,方便用户查看。 + */ + private Integer seq; + + /** + * 默认是1,表示正常状态,0为下线状态,用于标记品牌当前所处的业务状态。 + * 在业务流程中,根据品牌是否还在正常运营、是否需要对外展示等情况来设置该状态值, + * 比如对已停止合作的品牌可以将其状态设为0使其下线,不再出现在常规的品牌展示列表中。 + */ + private Integer status; + + /** + * 简要描述,提供品牌的一个简短概括性的介绍内容。 + * 相比于备注信息可能更精炼,常用于在一些列表页面等有限空间内快速展示品牌的关键特点或定位等信息。 + */ + private String brief; + + /** + * 记录时间,记录品牌相关信息首次被创建或者录入系统的时间点。 + * 可用于数据统计分析,比如统计不同时间段新品牌的创建数量等业务需求,也有助于了解品牌数据的历史情况。 + */ + private Date recTime; + + /** + * 更新时间,标记品牌信息最后一次被修改的时间。 + * 有助于判断数据的时效性,以及在一些需要基于更新时间来进行同步、版本控制等业务场景中发挥作用。 + */ + private Date updateTime; + + /** + * 品牌首字母,通常用于一些快速索引、分类筛选的场景。 + * 比如在按照首字母排序的品牌导航栏中,依据这个首字母来对品牌进行归类展示,方便用户查找特定品牌。 + */ + private String firstChar; + + /** + * 内容,可用于存储更详细、更丰富的关于品牌的文本描述内容。 + * 可以包含品牌故事、品牌文化、产品特色等综合性的信息,具体的使用场景和格式可根据业务实际需求确定。 + */ + private String content; +} \ No newline at end of file -- 2.34.1 From e08bba041433ee6f005b772ad352a363431b4cf9 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 23:05:43 +0800 Subject: [PATCH 046/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/DeliveryController.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/controller/DeliveryController.java diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/DeliveryController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/DeliveryController.java new file mode 100644 index 0000000..b7ef9ba --- /dev/null +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/DeliveryController.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.api.controller; // 定义类所在的包 + +import com.yami.shop.service.OrderService; // 引入订单服务类 +import org.springframework.beans.factory.annotation.Autowired; // 引入Spring的@Autowired注解 +import com.yami.shop.common.response.ServerResponseEntity; // 引入服务器响应实体类 +import org.springframework.web.bind.annotation.GetMapping; // 引入Spring的@GetMapping注解 +import org.springframework.web.bind.annotation.RequestMapping; // 引入Spring的@RequestMapping注解 +import org.springframework.web.bind.annotation.RestController; // 引入Spring的@RestController注解 + +import com.yami.shop.bean.app.dto.DeliveryDto; // 引入物流DTO +import com.yami.shop.bean.model.Delivery; // 引入配送模型 +import com.yami.shop.bean.model.Order; // 引入订单模型 +import com.yami.shop.common.util.Json; // 引入JSON工具类 +import com.yami.shop.service.DeliveryService; // 引入配送服务类 + +import cn.hutool.http.HttpUtil; // 引入Hutool工具类库中的HttpUtil工具类 +import io.swagger.v3.oas.annotations.tags.Tag; // 引入Swagger的Tag注解 +import io.swagger.v3.oas.annotations.Parameter; // 引入Swagger的Parameter注解 +import io.swagger.v3.oas.annotations.Operation; // 引入Swagger的Operation注解 + +/** + * DeliveryController类,负责查看物流信息。 + * 该类包含一个查看物流信息的方法,根据订单号查看物流详情。 + * @作者 lanhai + */ +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@RequestMapping("/delivery") // 定义请求路径的根地址为/delivery +@Tag(name = "查看物流接口") // 给API文档添加标签,描述这个控制器的功能 +public class DeliveryController { + + @Autowired + private DeliveryService deliveryService; // 自动注入配送服务类 + @Autowired + private OrderService orderService; // 自动注入订单服务类 + + /** + * 查看物流接口 + * @param orderNumber 订单号 + * @return 服务器响应实体,包含物流信息 + */ + @GetMapping("/check") + @Operation(summary = "查看物流" , description = "根据订单号查看物流") + @Parameter(name = "orderNumber", description = "订单号" , required = true) + public ServerResponseEntity checkDelivery(String orderNumber) { + + Order order = orderService.getOrderByOrderNumber(orderNumber); // 根据订单号获取订单信息 + Delivery delivery = deliveryService.getById(order.getDvyId()); // 根据配送ID获取配送信息 + String url = delivery.getQueryUrl().replace("{dvyFlowId}", order.getDvyFlowId()); // 构建查询物流信息的URL + String deliveryJson = HttpUtil.get(url); // 发送HTTP GET请求获取物流信息 + + DeliveryDto deliveryDto = Json.parseObject(deliveryJson, DeliveryDto.class); // 将JSON格式的物流信息转换为DTO对象 + deliveryDto.setDvyFlowId(order.getDvyFlowId()); // 设置物流流水号 + deliveryDto.setCompanyHomeUrl(delivery.getCompanyHomeUrl()); // 设置物流公司主页URL + deliveryDto.setCompanyName(delivery.getDvyName()); // 设置物流公司名称 + return ServerResponseEntity.success(deliveryDto); // 返回物流信息 + } +} -- 2.34.1 From 2fc1c0d21223e472b6601a6fe57d3000fabfc6be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 23:08:30 +0800 Subject: [PATCH 047/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/bean/model/HotSearch.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/model/HotSearch.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/model/HotSearch.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/HotSearch.java new file mode 100644 index 0000000..f937227 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/HotSearch.java @@ -0,0 +1,68 @@ +package com.yami.shop.bean.model; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.util.Date; + +/** + * HotSearch类用于表示热门搜索相关的实体信息,对应数据库中的"tz_hot_search"表。 + * 通过MyBatis Plus的相关注解(如@TableName)来建立实体类与数据库表之间的映射关系, + * 同时实现了Serializable接口,便于在需要的场景(如网络传输、持久化存储等)下对该对象进行序列化操作。 + * + * @author lanhai + */ +@Data +@TableName("tz_hot_search") +public class HotSearch implements Serializable { + /** + * 主键,用于在数据库中唯一标识每一条热门搜索记录。 + * 在"tz_hot_search"表中,此主键字段是区分不同热门搜索项的关键标识, + * 其值的生成方式可能由数据库按照既定规则(比如自增等)来确定,以保证每条记录都有唯一的标识。 + */ + @TableId + private Long hotSearchId; + + /** + * 店铺id,用于关联对应的店铺信息。 + * 通过该店铺id可以获取店铺的详细情况,例如店铺名称、主营类目等,明确该热门搜索内容是属于哪个具体店铺的, + * 有助于在多店铺的应用场景下进行针对性的搜索展示和管理。 + */ + private Long shopId; + + /** + * 标题,作为热门搜索项呈现给用户的主要文字描述内容。 + * 它通常简洁明了地概括了用户可能感兴趣的搜索主题,比如商品类别名称、热门促销活动主题等,方便用户快速识别和点击搜索。 + */ + private String title; + + /** + * 内容,用于进一步详细阐述热门搜索项相关的信息。 + * 相较于标题更为详细,可能包含具体的商品推荐、活动详情、搜索关键词的具体解释等内容,辅助用户更好地理解搜索项所涉及的内容。 + */ + private String content; + + /** + * 录入时间,记录该热门搜索项被添加到系统中的具体时间点。 + * 可以用于数据统计分析,比如统计不同时间段新增的热门搜索项数量,也有助于按照时间顺序对搜索项进行展示或管理等操作。 + * 此处通过@DateTimeFormat注解指定了日期时间的格式化模式为"yyyy-MM-dd HH:mm:ss",方便在数据绑定等操作时进行正确的格式转换。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date recDate; + + /** + * 顺序,用于确定热门搜索项在展示列表中的先后排列次序。 + * 在前端页面展示热门搜索列表时,可依据这个顺序字段来合理安排各项的展示顺序,使得重要或热门程度高的搜索项优先展示给用户,提升用户体验。 + */ + private Integer seq; + + /** + * 状态,默认是1,表示正常状态,0为下线状态,用于标记热门搜索项当前所处的业务状态。 + * 在实际业务中,根据搜索项是否还符合当前业务需求、是否需要继续展示给用户等情况来设置该状态值, + * 例如某些季节性的搜索主题结束后,可将其状态设为0使其下线,不在热门搜索列表中呈现给用户了。 + */ + private Integer status; +} \ No newline at end of file -- 2.34.1 From 963b6045c8187f54dd93ae8861e37aff35e0155c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 23:22:15 +0800 Subject: [PATCH 048/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/bean/event/CancelOrderEvent.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/event/CancelOrderEvent.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/event/CancelOrderEvent.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/event/CancelOrderEvent.java new file mode 100644 index 0000000..f404be6 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/event/CancelOrderEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 包声明,表明该类所在的包名,这里定义在com.yami.shop.bean.event包下, +// 按照Java的包结构规范,通常用于对相关类进行组织和分类管理,方便代码的模块化开发和维护。 +package com.yami.shop.bean.event; + +// 导入Order类,因为在本类中会使用到Order类型的变量,通过导入对应的类, +// 才能在代码中正确地引用和操作该类型,此处表示从com.yami.shop.bean.model包中引入Order类。 +import com.yami.shop.bean.model.Order; +// Lombok注解,用于自动生成包含所有参数的构造函数, +// 这样在创建CancelOrderEvent类的实例时,可以方便地通过传入相应参数来初始化对象。 +import lombok.AllArgsConstructor; +// Lombok注解,用于自动生成类的getter、setter方法以及其他一些常用的方法(如toString等), +// 减少了手动编写这些重复代码的工作量,提高代码的简洁性和开发效率。 +import lombok.Data; + +// 类的文档注释,简要描述了该类的作用,即表示取消订单的事件。 +// 这种注释有助于其他开发人员快速理解该类在整个业务逻辑中的用途。 +/** + * 取消订单的事件 + * @author + */ +// 使用@Data注解,让Lombok自动为该类生成相关的方法,如getter、setter等。 +// 使用@AllArgsConstructor注解,让Lombok自动生成包含所有参数的构造函数。 +@Data +@AllArgsConstructor +// 定义CancelOrderEvent类,用于表示取消订单这一业务事件相关的信息, +// 通常在基于事件驱动的架构中,此类可以作为事件对象在不同组件之间传递相关的业务数据。 +public class CancelOrderEvent { + + // 定义一个私有成员变量order,类型为Order,用于存储被取消的订单相关的详细信息, + // 比如订单编号、下单用户、商品明细、订单金额等内容,方便在事件传递过程中获取和处理订单相关的数据。 + private Order order; +} \ No newline at end of file -- 2.34.1 From e3f3a626747c35f2ef4dc46e57424491ad1d5742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 23:25:20 +0800 Subject: [PATCH 049/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/bean/app/dto/CategoryDto.java | 82 +++++++++ .../com/yami/shop/bean/model/Category.java | 170 ++++++++++++++++++ .../yami/shop/bean/model/CategoryBrand.java | 59 ++++++ 3 files changed, 311 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/CategoryDto.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/model/Category.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/model/CategoryBrand.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/CategoryDto.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/CategoryDto.java new file mode 100644 index 0000000..45e4fa9 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/CategoryDto.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.app.dto; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.yami.shop.common.serializer.json.ImgJsonSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * `CategoryDto`类是一个数据传输对象(DTO),用于在应用层不同组件或模块之间传递商品分类相关的信息。 + * 它的主要作用是将与商品分类有关的数据进行整合封装,使得在接口调用、页面展示等业务场景下, + * 数据的传递更加清晰、规范且符合业务需求,避免了直接传递复杂的底层实体对象或者分散的单个数据, + * 提高了代码的可读性以及不同模块间交互的便利性。 + * + * @author lanhai + */ +@Data +public class CategoryDto { + + /** + * 此属性代表商品分类的唯一标识符,用于在整个系统中精确地指代某一个具体的商品分类。 + * 无论是在数据库查询操作中,还是在不同业务逻辑模块间传递分类相关信息时, + * 都通过这个 `categoryId` 来准确地定位对应的分类实体。例如,当需要获取某个分类下的商品列表, + * 或者查找该分类的详细属性(如分类描述、关联的其他数据等)时,都会依赖这个唯一标识进行操作。 + * + * 在接口交互方面,通过 `@Schema` 注解明确标记该属性在 API 文档生成(例如使用 Swagger 等工具时) + * 中的描述信息为“分类id”,并且表明它是接口调用时必须提供的参数(`required = true`), + * 以此告知接口使用者该参数的重要性及必要性。 + */ + @Schema(description = "分类id", required = true) + private Long categoryId; + + /** + * `parentId` 属性用于记录当前商品分类的父级分类的唯一标识符,它构建了商品分类之间的层级关系。 + * 在电商系统或者其他存在分类层级结构的业务场景中,通过这个属性可以追溯到该分类所属的上级分类, + * 进而形成一个类似树状的分类体系。比如,在电商的商品分类里,“智能手机”分类可能隶属于“数码产品”分类, + * 那么“数码产品”分类的 `id` 就会存储在此处作为 `parentId`。 + * + * 同样,借助 `@Schema` 注解,在 API 文档中明确该属性的描述为“分类父id”,且为必填项, + * 方便接口调用者理解其含义以及在传递数据时按要求提供相应的值,有助于系统准确构建和展示分类层级结构。 + */ + @Schema(description = "分类父id", required = true) + private Long parentId; + + /** + * `categoryName` 是用于直观展示给用户或者在系统内部区分不同商品分类的名称属性。 + * 它以字符串的形式呈现了商品分类的具体称谓,例如“服装”“电子产品”“食品”等常见的分类名称, + * 是用户在前端界面看到的用于识别不同分类的文字标识,也是在业务逻辑处理中进行分类筛选、 + * 统计等操作时便于理解和操作的重要信息。 + * + * 通过 `@Schema` 注解,为该属性在 API 文档里添加“分类名称”的描述,并标记为必填项, + * 确保接口返回的数据包含清晰准确的分类名称信息,同时要求调用方在传递分类相关数据时必须提供有效的名称。 + */ + @Schema(description = "分类名称", required = true) + private String categoryName; + + /** + * `pic` 属性用于存放商品分类对应的图片资源相关信息,通常可能是图片在系统中的存储路径、 + * 唯一标识名称或者完整的 URL 地址等形式,具体取决于系统的图片存储和管理方式。 + * 其目的是在前端界面展示分类时能够配上相应的图片,增强视觉效果,帮助用户更直观地识别不同分类, + * 比如在展示“服装”分类时,可以显示一张服装展示图片,让用户更快速地定位到感兴趣的分类。 + * + * 这里使用了 `@JsonSerialize(using = ImgJsonSerializer.class)` 注解组合, + * 意味着在将该 `CategoryDto` 对象转换为 JSON 格式数据(例如在接口返回数据给前端时), + * 会使用自定义的 `ImgJsonSerializer` 序列化器对这个 `pic` 属性进行特殊处理, + * 可能是对图片路径进行格式调整、添加域名等操作,使其符合前端展示或者与其他系统交互时的要求, + * 保证图片资源信息能够被正确解析和使用。同时,通过 `@Schema` 注解标记该属性描述为“分类图片”且为必填项, + * 确保分类图片信息的完整性和准确性,以便在前端展示等业务场景中正常使用。 + */ + @Schema(description = "分类图片", required = true) + @JsonSerialize(using = ImgJsonSerializer.class) + private String pic; +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Category.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Category.java new file mode 100644 index 0000000..01bc664 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Category.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 声明该类所在的包名,按照Java的包结构组织方式,将相关的类放在同一个包下方便管理和区分不同功能模块的代码, +// 这里表示该类属于com.yami.shop.bean.model包。 +package com.yami.shop.bean.model; + +// 导入MyBatis Plus的注解TableField,用于定义实体类中字段与数据库表字段之间的一些额外映射关系, +// 比如指定字段是否在数据库表中有对应列等情况。 +import com.baomidou.mybatisplus.annotation.TableField; +// 导入MyBatis Plus的注解TableId,用于标记实体类中的主键字段,使得MyBatis Plus能正确识别与数据库表主键的对应关系。 +import com.baomidou.mybatisplus.annotation.TableId; +// 导入MyBatis Plus的注解TableName,用于将实体类与数据库中的具体表进行映射,表明该实体类对应哪个数据库表。 +import com.baomidou.myatisplus.annotation.TableName; +// 导入Jackson的注解JsonSerialize,用于指定在将对象转换为JSON格式时,对特定字段使用自定义的序列化方式。 +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +// 导入自定义的ImgJsonSerializer类,它应该是用于处理图片相关字段序列化的类, +// 在这里配合@JsonSerialize注解来对特定的图片字段进行定制化的JSON序列化操作。 +import com.yami.shop.common.serializer.json.ImgJsonSerializer; +// 导入Lombok的Data注解,使用该注解后,Lombok会自动为类生成常用的方法,比如getter、setter、toString等方法, +// 减少了手动编写这些重复代码的工作量,提高代码的简洁性和开发效率。 +import lombok.Data; + +// 导入Java的相关接口和类,用于实现对象的序列化功能(Serializable接口)以及处理日期相关操作(Date类), +// 还有用于操作列表数据结构(List接口),使得该类能更好地满足常见的业务需求,比如数据存储、传输等场景。 +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 该类的文档注释,简单说明了类的作者信息,这里作者是lanhai, + * 一般可以在此处更详细地描述类的功能和用途,方便其他开发人员快速理解该类在整个项目中的角色。 + * @author lanhai + */ +// 使用@Data注解,让Lombok自动生成类的getter、setter等常用方法,简化代码编写。 +// 使用@TableName注解,将该Category类与数据库中的"tz_category"表进行映射关联,意味着该类的实例可以与数据库表中的记录相互转换操作。 +@Data +@TableName("tz_category") +public class Category implements Serializable { + + /** + * 类目ID的成员变量声明及注释,用于唯一标识每个类目, + * 在数据库表"tz_category"中对应的是主键字段,是区分不同类目记录的关键标识, + * 其值的生成方式可能由数据库按照既定规则(如自增等)来确定,以保证每条类目记录都有唯一的ID。 + */ + @TableId + private Long categoryId; + + /** + * 店铺id的成员变量声明及注释,用于关联对应的店铺信息, + * 通过该店铺id可以获取店铺的详细情况,例如店铺名称、主营类目等,明确该类目是属于哪个具体店铺的, + * 有助于在多店铺的应用场景下进行类目数据的分类管理和相关业务操作。 + */ + private Long shopId; + + /** + * 父节点的成员变量声明及注释,用于表示当前类目的上级类目ID, + * 这里默认初始化为0L,表示如果没有明确的父类目(比如顶级类目)时的默认值, + * 通过这个字段可以构建类目之间的层级关系,方便进行类目树的展示、查询等操作。 + */ + private Long parentId = 0L; + + /** + * 产品类目名称的成员变量声明及注释,用于展示给用户或者在业务逻辑中区分不同类目的关键属性, + * 例如在前端界面的类目列表展示、商品筛选等场景中,都会依据这个名称来呈现类目相关信息,帮助用户快速定位和选择感兴趣的商品类目。 + */ + private String categoryName; + + /** + * 类目图标的成员变量声明及注释,用于指向存储类目相关图标文件的具体位置, + * 可以是本地文件系统的路径,也可能是网络存储的URL等形式,通过这个路径能够获取类目对应的图标, + * 比如在前端界面展示类目时,使用该图标来直观地表示类目,提升用户体验。 + */ + private String icon; + + /** + * 类目的显示图片的成员变量声明及注释,通过@JsonSerialize注解指定使用ImgJsonSerializer自定义类来进行序列化, + * 意味着在将该类的对象转换为JSON格式(比如用于接口返回数据等场景)时,对这个图片字段会按照自定义的序列化逻辑进行处理, + * 可能涉及到图片路径格式的调整、图片加载相关逻辑等,方便前端正确获取和展示类目对应的显示图片。 + */ + @JsonSerialize(using = ImgJsonSerializer.class) + private String pic; + + /** + * 排序的成员变量声明及注释,用于定义类目在某些特定展示或排序场景下的先后次序, + * 例如在类目列表页面按照一定规则排序展示时,依据这个顺序字段来排列类目的先后顺序,方便用户查看, + * 也有助于在后台管理类目时按照期望的顺序进行管理操作。 + */ + private Integer seq; + + /** + * 默认是1,表示正常状态,0为下线状态的成员变量声明及注释,用于标记类目当前所处的业务状态, + * 在业务流程中,根据类目是否还在正常使用、是否需要对外展示等情况来设置该状态值, + * 比如对已停止运营或不再需要展示的类目可以将其状态设为0使其下线,不再出现在常规的类目展示列表中。 + */ + private Integer status; + + /** + * 记录时间的成员变量声明及注释,用于记录类目相关信息首次被创建或者录入系统的时间点, + * 可用于数据统计分析,比如统计不同时间段新类目的创建数量等业务需求,也有助于了解类目数据的历史情况。 + */ + private Date recTime; + + /** + * 分类层级的成员变量声明及注释,用于表示当前类目所处的层级深度, + * 通过该字段可以更清晰地构建和了解类目树的结构,例如一级类目层级值可能为1,二级类目层级值为2等,方便进行层级相关的业务操作和查询。 + */ + private Integer grade; + + /** + * 更新时间的成员变量声明及注释,用于标记类目信息最后一次被修改的时间, + * 有助于判断数据的时效性,以及在一些需要基于更新时间来进行同步、版本控制等业务场景中发挥作用, + * 比如在缓存类目数据时,根据更新时间来判断是否需要重新获取最新的类目数据。 + */ + private Date updateTime; + + /** + * 品牌id的成员变量声明及注释,使用@TableField(exist=false)注解表示该字段在数据库表"tz_category"中不存在对应列, + * 它用于存储与该类目关联的品牌的ID列表,方便在业务逻辑中查询和处理与该类目相关的品牌信息,比如获取该类目下有哪些品牌的商品等。 + */ + @TableField(exist=false) + private List brandIds; + + /** + * 参数id的成员变量声明及注释,同样使用@TableField(exist=false)注解表明在数据库表中无对应列, + * 用于存储与该类目相关的参数的ID列表,在业务中可以依据这些参数来进一步描述类目下商品的特性、属性等信息, + * 例如商品的尺寸、颜色等参数与类目进行关联管理。 + */ + @TableField(exist=false) + private List attributeIds; + + /** + * 品牌列表的成员变量声明及注释,通过@TableField(exist=false)注解说明在数据库表中没有对应物理列, + * 用于存储与该类目相关的具体品牌对象列表,相比于只存储品牌ID,这里可以更方便地获取和操作品牌的详细信息, + * 比如品牌名称、品牌图片等,在涉及类目与品牌关联展示、查询等业务场景中使用。 + */ + @TableField(exist=false) + private List brands; + + /** + * 参数列表的成员变量声明及注释,借助@TableField(exist=false)注解指出在数据库表中不存在相应列, + * 用于存放与该类目相关的具体参数对象列表,方便在业务逻辑中详细了解类目下商品所涉及的各种属性参数情况, + * 比如对商品进行筛选、展示详细规格等操作时会用到这些参数对象。 + */ + @TableField(exist=false) + private List prodProps; + + /** + * 商品列表的成员变量声明及注释,使用@TableField(exist=false)注解表明其不在数据库表中有对应列, + * 用于存储属于该类目的商品对象列表,便于在业务操作中查询和获取该类目下包含哪些具体的商品, + * 比如在类目详情页面展示类目下的商品列表、进行类目商品统计等场景会用到该列表。 + */ + @TableField(exist=false) + private List products; + + /** + * 类目列表的成员变量声明及注释,通过@TableField(exist=false)注解说明在数据库表中没有对应的列, + * 用于存储当前类目下的子类目对象列表,通过这个字段可以构建和遍历类目树结构,方便进行类目层级相关的展示、查询等业务操作, + * 例如展示类目树的展开和收起效果时需要用到子类目列表信息。 + */ + @TableField(exist=false) + private List categories; +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/model/CategoryBrand.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/CategoryBrand.java new file mode 100644 index 0000000..5814b4b --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/CategoryBrand.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.app.dto; + +import lombok.Data; +import java.io.Serializable; + +/** + * 该类用于封装购物车中被选中的满减活动项的相关信息,作为数据传输对象(DTO)在应用层各组件或模块间传递相应数据。 + * 在电商系统等涉及购物车与促销活动交互的业务场景中,当用户在购物车页面选择参与某些满减活动时, + * 每个被选中的满减活动项具体信息就会被整理封装到此类的实例对象中,以便后续进行诸如计算优惠金额、 + * 展示活动详情、更新购物车总价等业务操作时使用,同时保证了数据传递的规范性和完整性, + * 避免了直接传递零散、复杂的数据结构,方便不同业务层之间进行交互协作。 + * + * @author lanhai + */ +@Data +public class ChooseDiscountItemDto implements Serializable { + + // 此处可根据实际业务需求添加相应的属性及对应的注释,以下为示例,可按需调整替换 + + /** + * 满减活动项的唯一标识符,用于在系统中精准定位该满减活动项的详细信息。 + * 例如在数据库中可以通过该ID查询到活动项对应的满减规则(如满多少金额减多少金额等具体规则)、 + * 活动的有效期限、适用的商品范围等相关内容,方便后续业务逻辑围绕该活动项进行准确处理。 + */ + private Long discountItemId; + + /** + * 关联的满减活动的ID,指向整个满减活动的总体定义信息。 + * 一个满减活动可能包含多个不同的活动项,通过这个ID可以获取到该满减活动的通用属性, + * 像活动名称、活动发起方(是平台发起还是店铺发起等)等基础信息,便于从整体上把握该满减活动的全貌。 + */ + private Long discountActivityId; + + /** + * 该满减活动项在本次购物车选中场景下可减免的金额数值。 + * 用于明确当前所选的这个满减活动项具体能为用户节省多少钱,是计算购物车最终总价、 + * 展示优惠明细等操作中重要的数据依据,在向用户展示优惠情况或者进行金额相关业务逻辑处理时会频繁用到。 + */ + private Double discountAmount; + + /** + * 表示该满减活动项是否已经生效的标识,通常可以用布尔类型(true或false)来表示。 + * 例如当满足了活动项设定的前置条件(如购物车商品总价达到规定金额等)时,该标识会被设置为true, + * 意味着此活动项开始生效,相应的优惠金额可以进行扣除等操作;反之则为false, + * 方便在业务逻辑中判断活动项的执行状态,确保优惠计算的准确性。 + */ + private boolean isActive; + +} \ No newline at end of file -- 2.34.1 From d6e9456248aec86fa2228b44fd34c00417eb277b Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:26:47 +0800 Subject: [PATCH 050/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/service/impl/BasketServiceImpl.java | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/impl/BasketServiceImpl.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/BasketServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/BasketServiceImpl.java new file mode 100644 index 0000000..122d982 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/BasketServiceImpl.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service.impl; + +// 导入所需的类和包 + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import com.yami.shop.bean.app.dto.*; +import com.yami.shop.bean.app.param.ChangeShopCartParam; +import com.yami.shop.bean.app.param.OrderItemParam; +import com.yami.shop.bean.app.param.ShopCartParam; +import com.yami.shop.bean.event.ShopCartEvent; +import com.yami.shop.bean.model.Basket; +import com.yami.shop.bean.model.Product; +import com.yami.shop.bean.model.ShopDetail; +import com.yami.shop.bean.model.Sku; +import com.yami.shop.common.util.Arith; +import com.yami.shop.common.util.CacheManagerUtil; +import com.yami.shop.dao.BasketMapper; +import com.yami.shop.service.BasketService; +import com.yami.shop.service.ProductService; +import com.yami.shop.service.ShopDetailService; +import com.yami.shop.service.SkuService; +import lombok.AllArgsConstructor; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 购物车服务实现类 + * + * @author lgh on 2018/10/18. + */ +@Service +@AllArgsConstructor +public class BasketServiceImpl extends ServiceImpl implements BasketService { + + // 注入的组件 + private final BasketMapper basketMapper; + private final CacheManagerUtil cacheManagerUtil; + private final ApplicationContext applicationContext; + private final SkuService skuService; + private final ShopDetailService shopDetailService; + private final ProductService productService; + + /** + * 根据购物车ID删除购物车商品 + * @param userId 用户ID + * @param basketIds 购物车ID列表 + */ + @Override + @CacheEvict(cacheNames = "ShopCartItems", key = "#userId") + public void deleteShopCartItemsByBasketIds(String userId, List basketIds) { + basketMapper.deleteShopCartItemsByBasketIds(userId, basketIds); + } + + /** + * 添加购物车商品 + * @param param 购物车参数 + * @param userId 用户ID + */ + @Override + @CacheEvict(cacheNames = "ShopCartItems", key = "#userId") + public void addShopCartItem(ChangeShopCartParam param, String userId) { + Basket basket = new Basket(); + basket.setBasketCount(param.getCount()); + basket.setBasketDate(new Date()); + basket.setProdId(param.getProdId()); + basket.setShopId(param.getShopId()); + basket.setUserId(userId); + basket.setSkuId(param.getSkuId()); + basket.setDistributionCardNo(param.getDistributionCardNo()); + basketMapper.insert(basket); + } + + /** + * 更新购物车商品 + * @param basket 购物车对象 + */ + @Override + @CacheEvict(cacheNames = "ShopCartItems", key = "#basket.userId") + public void updateShopCartItem(Basket basket) { + basketMapper.updateById(basket); + } + + /** + * 删除用户所有购物车商品 + * @param userId 用户ID + */ + @Override + @CacheEvict(cacheNames = "ShopCartItems", key = "#userId") + public void deleteAllShopCartItems(String userId) { + basketMapper.deleteAllShopCartItems(userId); + } + + /** + * 获取用户购物车商品列表 + * @param userId 用户ID + * @return 购物车商品列表 + */ + @Override + public List getShopCartItems(String userId) { + // 从缓存中获取购物车商品列表,如果缓存中没有则从数据库中获取 + List shopCartItemDtoList = cacheManagerUtil.getCache("ShopCartItems", userId); + if (shopCartItemDtoList != null) { + return shopCartItemDtoList; + } + shopCartItemDtoList = basketMapper.getShopCartItems(userId); + for (ShopCartItemDto shopCartItemDto : shopCartItemDtoList) { + shopCartItemDto.setProductTotalAmount(Arith.mul(shopCartItemDto.getProdCount(), shopCartItemDto.getPrice())); + } + cacheManagerUtil.putCache("ShopCartItems", userId, shopCartItemDtoList); + return shopCartItemDtoList; + } + + /** + * 获取过期的购物车商品列表 + * @param userId 用户ID + * @return 过期的购物车商品列表 + */ + @Override + public List getShopCartExpiryItems(String userId) { + return basketMapper.getShopCartExpiryItems(userId); + } + + /** + * 清除过期商品列表 + * @param userId 用户ID + */ + @Override + @CacheEvict(cacheNames = "ShopCartItems", key = "#userId") + public void cleanExpiryProdList(String userId) { + basketMapper.cleanExpiryProdList(userId); + } + + /** + * 根据购物车参数更新购物车 + * @param userId 用户ID + * @param basketIdShopCartParamMap 购物车ID和参数的映射 + */ + @Override + @CacheEvict(cacheNames = "ShopCartItems", key = "#userId") + public void updateBasketByShopCartParam(String userId, Map basketIdShopCartParamMap) { + basketMapper.updateDiscountItemId(userId, basketIdShopCartParamMap); + } + + /** + * 根据用户ID移除购物车缓存 + * @param userId 用户ID + */ + @Override + @CacheEvict(cacheNames = "ShopCartItems", key = "#userId") + public void removeShopCartItemsCacheByUserId(String userId) { + // 该方法体为空,可能是待实现的方法 + } + + /** + * 根据商品ID列出所有用户的ID + * @param prodId 商品ID + * @return 用户ID列表 + */ + @Override + public List listUserIdByProdId(Long prodId) { + return basketMapper.listUserIdByProdId(prodId); + } + + /** + * 根据购物车商品项获取购物车信息 + * @param shopCartItems 购物车商品项列表 + * @return 购物车信息列表 + */ + @Override + public List getShopCarts(List shopCartItems) { + // 根据店铺ID分组购物车商品项 + Map> shopCartMap = shopCartItems.stream().collect(Collectors.groupingBy(ShopCartItemDto::getShopId)); + + // 构建购物车信息列表 + List shopCartDtos = Lists.newArrayList(); + for (Long shopId : shopCartMap.keySet()) { + // 获取店铺的所有商品项 + List shopCartItemDtoList = shopCartMap.get(shopId); + + // 构建每个店铺的购物车信息 + ShopCartDto shopCart = new ShopCartDto(); + shopCart.setShopId(shopId); + shopCart.setShopName(shopCartItemDtoList.get(0).getShopName()); + + // 发布购物车事件 + applicationContext.publishEvent(new ShopCartEvent(shopCart, shopCartItemDtoList)); + + shopCartDtos.add(shopCart); + } + + return shopCartDtos; + } + + /** + * 根据订单项和用户ID获取购物车商品项 + * @param basketId 购物车ID列表 + * @param orderItem 订单项参数 + * @param userId 用户ID + * @return 购物车商品项列表 + */ + @Override + public List getShopCartItemsByOrderItems(List basketId, OrderItemParam orderItem, String userId) { + if (orderItem == null && CollectionUtil.isEmpty(basketId)) { + return Collections.emptyList(); + } + + // 当立即购买时,没有提交的订单是没有购物车信息的 + if (CollectionUtil.isEmpty(basketId) && orderItem != null) { + Sku sku = skuService.getSkuBySkuId(orderItem.getSkuId()); + if (sku == null) { + throw new RuntimeException("订单包含无法识别的商品"); + } + Product prod = productService.getProductByProdId(orderItem.getProdId()); + if (prod == null) { + throw new RuntimeException("订单包含无法识别的商品"); + } + + // 构建购物车商品项 + ShopCartItemDto shopCartItemDto = new ShopCartItemDto(); + shopCartItemDto.setBasketId(-1L); + shopCartItemDto.setSkuId(orderItem.getSkuId()); + shopCartItemDto.setProdCount(orderItem.getProdCount()); + shopCartItemDto.setProdId(orderItem.getProdId()); + shopCartItemDto.setSkuName(sku.getSkuName()); + shopCartItemDto.setPic(StrUtil.isBlank(sku.getPic()) ? prod.getPic() : sku.getPic()); + shopCartItemDto.setProdName(sku.getProdName()); + shopCartItemDto.set +} -- 2.34.1 From 6a2f36da3be81b5a290ebfb480b15c8cee811369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 23:27:32 +0800 Subject: [PATCH 051/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bean/app/dto/ChooseDiscountItemDto.java | 75 ++++++++++++++ .../bean/app/param/ChangeShopCartParam.java | 97 +++++++++++++++++++ .../yami/shop/bean/model/CategoryProp.java | 53 ++++++++++ 3 files changed, 225 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/ChooseDiscountItemDto.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/app/param/ChangeShopCartParam.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/model/CategoryProp.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/ChooseDiscountItemDto.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/ChooseDiscountItemDto.java new file mode 100644 index 0000000..ba6d586 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/ChooseDiscountItemDto.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.app.dto; + +import lombok.Data; +import java.io.Serializable; + +/** + * `ChooseDiscountItemDto`类是一个数据传输对象(DTO),用于封装购物车中被选中的满减活动项相关信息, + * 在电商系统的业务逻辑处理中,起着在不同组件或模块之间传递相关数据的重要作用。 + * 当用户在购物车页面选择参与某些满减活动时,每个被选中的满减活动项具体细节都会被整理并封装到此类的实例对象中, + * 以便后续进行如计算优惠金额、展示活动详情给用户、更新购物车总价等一系列业务操作时使用, + * 确保了数据传递的规范性和完整性,避免了在各业务层之间直接传递零散且复杂的数据结构。 + + * @author lanhai + */ +@Data +public class ChooseDiscountItemDto implements Serializable { + + /** + * 满减活动项的唯一标识符,用于在整个系统中精确地定位该满减活动项对应的详细信息。 + * 例如,在数据库中可以通过这个 `discountItemId` 去查询该项活动的具体规则(如满多少金额减多少金额、 + * 满减的条件限制等)、活动的有效期限、适用的商品范围以及与其他业务模块相关联的信息等, + * 为后续围绕该满减活动项进行准确的业务逻辑处理(如判断是否满足满减条件、计算优惠额度等)提供关键依据。 + */ + private Long discountItemId; + + /** + * 关联的满减活动的整体唯一标识符,即 `discountActivityId`,它指向整个满减活动的总体定义信息。 + * 通常一个满减活动可能会包含多个不同的活动项,通过这个ID能够获取到该满减活动的通用属性, + * 比如活动的名称、活动发起方(是平台发起还是店铺发起等)、活动覆盖的商品分类范围等基础信息, + * 有助于从整体上把握该满减活动的全貌,并且在涉及多个活动项协同处理或者统计分析整个满减活动相关数据时发挥作用。 + */ + private Long discountActivityId; + + /** + * 该满减活动项在当前购物车选中场景下能够为用户减免的具体金额数值,即 `discountAmount`。 + * 它明确了选择这个满减活动项后,用户可以实际节省多少钱,是计算购物车最终总价、 + * 向用户清晰展示优惠明细等操作中不可或缺的数据要素,在涉及金额计算和展示优惠情况的业务逻辑中会频繁被使用。 + */ + private Double discountAmount; + + /** + * 用于标识该满减活动项当前是否已经生效的布尔类型属性,例如可以用 `isActive` 来表示。 + * 其取值通常为 `true` 或 `false`,当满足了该活动项预先设定的生效条件(比如购物车商品总价达到规定金额、 + * 所选商品符合活动要求的品类等)时,这个标识会被设置为 `true`,意味着此活动项开始生效,相应的优惠金额可以进行扣除等操作; + * 反之,若不满足条件则为 `false`,通过这个属性方便在业务逻辑中准确判断活动项的执行状态, + * 确保优惠计算的准确性以及整个购物流程中满减活动应用的正确性。 + */ + private boolean isActive; + + /** + * 满减活动项适用的商品集合相关信息,可以用合适的数据结构来存储(例如 `List` 类型的列表, + * 里面存放符合该活动项的商品ID等,具体根据业务实际情况确定),用 `applicableProdIds` 表示。 + * 它明确了该满减活动项具体适用于哪些商品,在判断购物车中的商品是否能参与此满减活动、 + * 以及在计算优惠金额时分摊到各个商品上的情况等业务逻辑中会起到关键作用,有助于更精细地处理满减优惠与商品的关联关系。 + */ + private Object applicableProdIds; + + /** + * 满减活动项剩余的可使用次数,用 `remainingUseTimes` 表示,通常可以是整数类型。 + * 在一些有限次使用的满减活动场景中,这个属性用于记录该活动项还能被使用的次数, + * 例如,某个满减活动项规定用户总共可以使用3次,每使用一次就会相应地减少该次数, + * 通过这个属性可以在业务逻辑中控制活动项的使用频率,避免超次数使用,确保活动规则的正确执行。 + */ + private Integer remainingUseTimes; +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/app/param/ChangeShopCartParam.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/param/ChangeShopCartParam.java new file mode 100644 index 0000000..7a08e7d --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/param/ChangeShopCartParam.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.app.param; + +import jakarta.validation.constraints.NotNull; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * `ChangeShopCartParam`类是一个用于接收修改购物车相关操作参数的数据对象,在电商应用的业务逻辑处理中起着关键作用。 + * 它主要用于在前端与后端进行交互时,承载用户发起的修改购物车操作所需的各项数据信息,以便后端能够依据这些参数准确地执行相应的业务逻辑, + * 比如更新购物车中商品的数量、更换商品的规格、处理与分销相关的业务逻辑等操作。 + + * @author LGH + */ +@Data +public class ChangeShopCartParam { + + /** + * `basketId`属性代表购物车中某条商品记录的唯一标识符,也就是购物车ID。 + * 在购物车的业务场景中,购物车可以包含多条不同的商品记录,每条记录都有其独立的 `basketId`, + * 通过这个ID,后端服务能够精准地定位到用户想要修改的具体是哪一项商品记录, + * 无论是对商品数量进行调整、删除该商品,还是进行其他与该商品在购物车中相关的操作,都依赖这个唯一标识来确定操作对象。 + * 同时,通过 `@Schema` 注解,在生成API文档(例如使用Swagger等工具时)时,为该属性添加“购物车ID”的描述信息, + * 并明确标记其为必填项(`required = true`),告知接口调用者在发起修改购物车操作时必须提供这个参数值, + * 以此保证后端接收到的数据完整性,便于准确执行后续业务逻辑。 + */ + @Schema(description = "购物车ID", required = true) + private Long basketId; + + /** + * `prodId`属性是商品的唯一标识符,即商品ID。 + * 在整个电商系统中,每个商品都被赋予了一个唯一的 `prodId`,用于精确地指代该商品, + * 通过这个ID,后端可以关联到商品的详细信息,如商品名称、价格、规格详情、库存信息等, + * 进而在修改购物车操作中,准确知晓当前操作涉及的是哪个具体商品。 + * 这里使用了 `@NotNull` 注解,并设置了相应的提示消息“商品ID不能为空”, + * 用于在数据校验阶段确保前端传递过来的参数中该商品ID不能为空值,若为空则会触发验证错误提示, + * 同时通过 `@Schema` 注解在API文档中表明其描述为“商品ID”且为必填项,确保接口调用时数据的规范性和完整性。 + */ + @NotNull(message = "商品ID不能为空") + @Schema(description = "商品ID", required = true) + private Long prodId; + + /** + * `skuId`属性表示商品库存保有单位(Stock Keeping Unit,简称SKU)的ID,即具体商品规格的唯一标识符。 + * 在电商业务中,同一款商品往往会有多种不同的规格组合,例如一款手机可能有不同的颜色、内存容量等配置, + * 每个不同的规格组合就对应一个独特的SKU,通过这个 `skuId`,能够精准地定位到具体是哪一种规格的商品, + * 以便在修改购物车操作中准确处理与该商品规格相关的业务逻辑,比如更新对应规格商品的数量等。 + * 同样,借助 `@NotNull` 注解保证该参数不能为空,若为空会给出“skuId不能为空”的错误提示, + * 且通过 `@Schema` 注解在API文档里明确其描述为“skuId”并标记为必填项,便于接口调用者正确传递参数。 + */ + @NotNull(message = "skuId不能为空") + @Schema(description = "skuId", required = true) + private Long skuId; + + /** + * `shopId`属性为店铺的唯一标识符,也就是店铺ID。 + * 在多店铺的电商平台模式下,不同店铺可能会售卖相同的商品,但商品的库存数量、价格、优惠活动等信息可能各不相同, + * 所以需要通过这个 `shopId` 来明确当前修改购物车操作所涉及商品所属的具体店铺, + * 确保后端能够依据准确的店铺信息来处理诸如库存扣减、价格计算、店铺相关优惠应用等业务逻辑。 + * 利用 `@NotNull` 注解强制要求该参数必须提供有效的店铺ID值,若缺失会触发验证错误, + * 并且通过 `@Schema` 注解在API文档中展示其描述为“店铺ID”以及必填属性,保证接口调用的准确性。 + */ + @NotNull(message = "店铺ID不能为空") + @Schema(description = "店铺ID", required = true) + private Long shopId; + + /** + * `count`属性代表商品的数量,即用户期望在购物车中设置的该商品的个数。 + * 在修改购物车操作里,用户常常会调整商品的购买数量,比如从原来购买1个增加到购买2个等情况, + * 这个属性就用于记录用户想要修改后的商品数量值,后端依据该值来更新购物车中对应商品的数量信息, + * 并进一步基于新的数量进行诸如总价重新计算、库存更新等相关业务逻辑处理。 + * 通过 `@NotNull` 注解确保该参数不能为空,必须传递有效的数量数值, + * 同时借助 `@Schema` 注解在API文档中明确其描述为“商品个数”且为必填项,方便接口调用者知晓参数要求并正确传递数据。 + */ + @NotNull(message = "商品个数不能为空") + @Schema(description = "商品个数", required = true) + private Integer count; + + /** + * `distributionCardNo`属性用于存放分销推广人卡号相关信息。 + * 在电商平台的分销业务模式下,当用户通过某个分销推广人的链接进入购物并产生购买行为时, + * 这个卡号可以用于追踪该商品销售与推广人的关联关系,以便后续进行相应的分销收益计算、记录等业务操作。 + * 该属性并非必填项,其描述信息通过 `@Schema` 注解在API文档中体现为“分销推广人卡号”, + * 告知接口调用者该参数的含义以及在有分销相关业务场景时可以选择性地提供相应卡号信息。 + */ + @Schema(description = "分销推广人卡号") + private String distributionCardNo; +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/model/CategoryProp.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/CategoryProp.java new file mode 100644 index 0000000..e3b9fc6 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/CategoryProp.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.model; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; + +/** + * `CategoryProp`类是一个实体类,用于表示商品分类与商品属性之间的关联关系,在电商系统等业务场景中有着重要作用。 + * 它主要负责在数据库层面记录每个商品分类下具体关联了哪些商品属性,方便后续基于分类进行商品属性相关的操作, + * 例如在展示商品详情、进行商品筛选以及查询符合特定属性要求的商品时,依据这种关联关系获取相应的数据。 + * + * @author lanhai + */ +@Data +@TableName("tz_category_prop") +public class CategoryProp implements Serializable { + + /** + * `id`属性作为该类的主键,是每条分类与属性关联记录在数据库中的唯一标识符。 + * 它具有唯一性,通过这个 `id`,系统能够在数据库中精准地定位、操作某一条具体的分类属性关联记录, + * 比如在更新、删除特定的关联关系或者根据关联记录查询相关信息时,都要依赖这个唯一标识来确保操作的准确性和针对性, + * 是整个关联关系数据管理中的关键标识字段。 + */ + @TableId + private Long id; + + /** + * `categoryId`属性用于标识关联的商品分类的ID,它与系统中定义的商品分类体系相连接。 + * 通过这个ID可以明确当前这条关联记录所属的具体商品分类,例如在电商系统中,它可能对应着诸如“服装”分类、 + * “数码产品”分类等具体分类的唯一标识,借助该ID还能进一步获取该分类的其他详细信息, + * 像分类名称、分类的层级结构以及与之关联的其他相关数据等,为基于分类的各种业务逻辑处理提供基础依据。 + */ + private Long categoryId; + + /** + * `propId`属性代表商品属性的ID,它对应着系统中另外一个用于定义商品属性的相关数据表(如 `tz_prod_prop` 表)里的 `prop_id` 字段。 + * 此属性的作用是确定具体是哪个商品属性与当前分类进行了关联,例如在“数码产品”分类下, + * 这个 `propId` 可能指向“屏幕尺寸”这个具体属性的标识,通过该ID可以获取到该属性更详细的内容, + * 比如属性的名称、可选的值范围等信息,从而在商品展示、筛选等业务操作中准确地呈现相应的属性详情, + * 方便用户根据属性来挑选商品。 + */ + private Long propId; +} \ No newline at end of file -- 2.34.1 From e30358b7b562475df57376345c417609506851fc Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:28:11 +0800 Subject: [PATCH 052/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/mapper/BrandMapper.xml | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 yami-shop-service/src/main/resources/mapper/BrandMapper.xml diff --git a/yami-shop-service/src/main/resources/mapper/BrandMapper.xml b/yami-shop-service/src/main/resources/mapper/BrandMapper.xml new file mode 100644 index 0000000..93d0d53 --- /dev/null +++ b/yami-shop-service/src/main/resources/mapper/BrandMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- 2.34.1 From a753700ddf35cc72aa6b68e48b9f95c422b0a7ec Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:29:19 +0800 Subject: [PATCH 053/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/service/impl/BrandServiceImpl.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/impl/BrandServiceImpl.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/BrandServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/BrandServiceImpl.java new file mode 100644 index 0000000..da5f45e --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/BrandServiceImpl.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yami.shop.bean.model.Brand; +import com.yami.shop.dao.BrandMapper; +import com.yami.shop.dao.CategoryBrandMapper; +import com.yami.shop.service.BrandService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 品牌服务实现类,提供品牌相关的业务逻辑 + * + * @author lanhai + */ +@Service +public class BrandServiceImpl extends ServiceImpl implements BrandService { + + // 自动注入BrandMapper,用于操作品牌数据 + @Autowired + private BrandMapper brandMapper; + + // 自动注入CategoryBrandMapper,用于操作品牌与分类关系的数据 + @Autowired + private CategoryBrandMapper categoryBrandMapper; + + /** + * 根据品牌名称查询品牌信息 + * @param brandName 品牌名称 + * @return 品牌对象 + */ + @Override + public Brand getByBrandName(String brandName) { + return brandMapper.getByBrandName(brandName); + } + + /** + * 根据品牌ID删除品牌以及其与分类的关系 + * @param brandId 品牌ID + */ + @Override + public void deleteByBrand(Long brandId) { + brandMapper.deleteById(brandId); + categoryBrandMapper.deleteByBrandId(brandId); + } + + /** + * 根据分类ID查询品牌列表 + * @param categoryId 分类ID + * @return 品牌列表 + */ + @Override + public List listByCategoryId(Long categoryId) { + return brandMapper.listByCategoryId(categoryId); + } + +} -- 2.34.1 From d7713c7e883d8f47df2a914bda836f84c1530bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 23:29:53 +0800 Subject: [PATCH 054/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/bean/app/dto/CouponOrderDto.java | 66 +++++++++++++++++++ .../shop/bean/event/ConfirmOrderEvent.java | 62 +++++++++++++++++ .../shop/bean/order/ConfirmOrderOrder.java | 54 +++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/CouponOrderDto.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/event/ConfirmOrderEvent.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/order/ConfirmOrderOrder.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/CouponOrderDto.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/CouponOrderDto.java new file mode 100644 index 0000000..4ecee59 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/CouponOrderDto.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.app.dto; + +import lombok.Data; +import java.io.Serializable; + +/** + * `CouponOrderDto`类是一个数据传输对象(DTO),用于在电商系统应用层的不同组件或模块之间传递与优惠券和订单相关的信息。 + * 其主要目的是将涉及优惠券在订单场景下的相关数据进行整合封装,方便在诸如订单创建、订单金额计算(包含优惠券优惠计算)、 + * 订单详情展示等业务操作中进行数据传递,使得数据交互更加清晰、规范,避免直接传递复杂或分散的底层数据结构, + * 提高不同业务层之间协作的效率以及代码的整体可读性。 + + * @author lanhai + */ +@Data +public class CouponOrderDto implements Serializable { + + /** + * 优惠券的唯一标识符,用于在系统中精准定位某一张具体的优惠券。 + * 通过这个 `couponId`,可以关联到优惠券的详细信息,例如优惠券的名称、类型(满减券、折扣券等)、 + * 优惠金额或折扣比例、使用条件(如满多少金额可用、适用的商品范围等)以及有效期等关键属性, + * 在订单处理过程中,判断该优惠券是否可用于当前订单以及计算具体优惠金额时,都需要依赖这个唯一标识来获取相应信息。 + */ + private Long couponId; + + /** + * 关联的订单的唯一标识符,即 `orderId`,用于明确该优惠券是应用于哪一个具体的订单上。 + * 在电商系统中,一个优惠券可能可以在多个订单中使用(前提是满足使用条件),同样,一个订单也可能会使用多张优惠券, + * 通过这个 `orderId` 能够清晰地建立起优惠券与订单之间的对应关系,便于后续业务逻辑围绕该订单进行优惠券相关的处理, + * 比如统计该订单使用了哪些优惠券、计算因优惠券使用而减免的总金额等操作。 + */ + private Long orderId; + + /** + * 该优惠券在当前订单中实际减免的金额数值,用 `discountAmount` 表示。 + * 在订单处理流程中,当确定某张优惠券可应用于当前订单后,需要根据优惠券的规则(如满减券的满减金额设定等) + * 来计算出它在这个订单里具体能减免多少钱,这个属性就用于记录该实际减免的金额, + * 是计算订单最终应付金额、展示订单优惠明细等操作中重要的数据依据,能直观体现优惠券给订单带来的优惠效果。 + */ + private Double discountAmount; + + /** + * 表示优惠券在当前订单中的使用状态的标识,例如可以用布尔类型的 `isUsed` 属性来表示(`true` 表示已使用,`false` 表示未使用)。 + * 在整个订单生命周期内,优惠券的使用状态可能会发生变化,例如用户最初选择使用某张优惠券,但后续又取消了选择, + * 或者由于某些条件不满足导致优惠券无法使用等情况,通过这个属性可以清晰地反映出优惠券当前是否已在该订单中被实际应用, + * 方便在业务逻辑中进行相应的判断和处理,确保订单与优惠券之间的关联状态准确无误。 + */ + private boolean isUsed; + + /** + * 优惠券的剩余可用次数(如果优惠券有使用次数限制的话),用 `remainingUseTimes` 表示,通常为整数类型。 + * 对于一些有限次使用的优惠券,每次在订单中成功使用后,剩余可用次数会相应减少, + * 这个属性用于记录当前优惠券针对后续订单还能被使用的次数,在判断优惠券是否还能继续在其他订单中使用等业务逻辑中起着关键作用, + * 有助于合理控制优惠券的使用频率,遵循其预先设定的使用规则。 + */ + private Integer remainingUseTimes; +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/event/ConfirmOrderEvent.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/event/ConfirmOrderEvent.java new file mode 100644 index 0000000..6d17771 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/event/ConfirmOrderEvent.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.event; + +import com.yami.shop.bean.app.dto.ShopCartDto; +import com.yami.shop.bean.app.dto.ShopCartItemDto; +import com.yami.shop.bean.app.dto.ShopCartOrderDto; +import com.yami.shop.bean.app.dto.ShopCartOrderMergerDto; +import com.yami.shop.bean.app.param.OrderParam; +import lombok.AllArgsConstructor; +import lombok.Data; +import java.util.List; + +/** + * `ConfirmOrderEvent`类是一个用于封装确认订单相关业务信息的事件类,它遵循领域驱动设计(DDD)中领域事件的概念, + * 在电商系统等涉及订单处理的业务场景中发挥着重要作用。当用户进行确认订单操作时,会触发该事件, + * 该类的实例对象会承载确认订单这一时刻所涉及的关键数据信息,并在不同的业务组件或模块之间传递, + * 使得其他关注确认订单事件的部分能够基于这些数据进行相应的业务逻辑处理,比如订单金额计算、库存扣减、 + * 订单记录保存等后续操作,确保整个订单确认流程的准确与完整。 + + * @author LGH + */ +@Data +@AllArgsConstructor +public class ConfirmOrderEvent { + + /** + * `shopCartOrderDto`属性用于存放购物车已经组装好的店铺订单信息。 + * 在电商购物流程中,用户将商品加入购物车后,当准备确认订单时,系统会根据购物车中的商品、店铺等信息, + * 按照一定规则(例如按照不同店铺进行订单拆分、合并等)组装出每个店铺对应的订单信息, + * 这个 `ShopCartOrderDto` 对象就包含了诸如店铺的基本信息(店铺名称、店铺ID等)、 + * 该店铺下所有商品的汇总信息(商品总价、商品总数量等)以及其他与店铺订单相关的必要数据, + * 方便后续在确认订单阶段基于店铺维度进行进一步的业务处理,比如计算店铺优惠、生成店铺订单详情等操作。 + */ + private ShopCartOrderDto shopCartOrderDto; + + /** + * `orderParam`属性承载了下单请求的各项参数信息。 + * 这些参数通常是由用户在前端界面填写或者系统根据默认规则生成的,例如用户的收货地址、联系人姓名、 + * 联系电话、选择的支付方式等关键信息,它们对于生成完整准确的订单记录至关重要, + * 后续的订单创建、保存以及与第三方支付平台交互(如果涉及)等业务逻辑都会依赖这些参数进行相应操作, + * 确保订单能够按照用户期望的方式被处理和执行。 + */ + private OrderParam orderParam; + + /** + * `shopCartItems`属性是一个列表,用于存储店铺中的所有商品项信息,每个元素是 `ShopCartItemDto` 类型。 + * 在确认订单阶段,需要详细了解购物车中每个具体商品的详细情况,包括商品的具体规格(通过对应的SKU信息体现)、 + * 商品单价、购买数量以及商品在购物车中的唯一标识等信息,这个列表就完整地记录了这些商品项的详细数据, + * 方便在后续业务逻辑中,例如计算每个商品的实际金额、更新商品库存、生成订单商品明细等操作时使用, + * 确保订单中商品相关信息的准确性和完整性。 + */ + private List shopCartItems; +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/order/ConfirmOrderOrder.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/order/ConfirmOrderOrder.java new file mode 100644 index 0000000..cbae2f9 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/order/ConfirmOrderOrder.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.order; + +/** + * `ConfirmOrderOrder`接口用于定义提交订单事件先后顺序相关的常量,在电商系统的订单处理业务流程中, + * 特别是涉及多种优惠活动、业务规则应用于订单的场景下,起到明确各环节执行顺序的重要作用。 + * 通过定义这些顺序常量,系统可以清晰地知晓在提交订单时,不同优惠、业务逻辑处理的先后顺序, + * 确保订单金额计算、优惠叠加等操作按照合理且符合业务要求的顺序进行,避免出现逻辑混乱或计算错误的情况。 + + * @author LGH + */ +public interface ConfirmOrderOrder { + + /** + * `DEFAULT`常量表示在没有任何活动参与时的默认顺序,其值为0。 + * 当订单不存在如满减、优惠券、分销等特殊优惠活动或业务逻辑时, + * 系统按照这个默认顺序来处理订单相关的基础操作,例如计算商品原价总和、 + * 确定基本的运费(如果有)等,它作为一个基准顺序,其他涉及活动的顺序都基于此进行相对排序。 + */ + int DEFAULT = 0; + + /** + * `DISCOUNT`常量代表满减活动在整个订单处理顺序中的位置,其值为100,排在 `DEFAULT` 后面。 + * 在电商系统中,满减活动是常见的优惠方式之一,当订单满足满减活动的条件(如购物车商品总价达到一定金额可减免部分金额)时, + * 系统会按照这个顺序来处理满减优惠的计算和应用,确保在完成基础的订单金额计算(即 `DEFAULT` 顺序对应的操作)之后, + * 再进行满减金额的扣除等相关操作,保证优惠计算的准确性和顺序性。 + */ + int DISCOUNT = 100; + + /** + * `COUPON`常量用于表示优惠券相关业务在订单处理顺序中的位置,其值为200,排在 `DISCOUNT` 后面。 + * 当用户在下单过程中使用了优惠券来进一步降低订单金额时,系统会依据这个顺序, + * 在完成满减优惠计算(按照 `DISCOUNT` 顺序操作)之后,再进行优惠券优惠金额的扣除等处理, + * 以此来合理叠加不同的优惠方式,使得最终订单金额的计算符合业务设定的先后逻辑顺序。 + */ + int COUPON = 200; + + /** + * `DISTRIBUTION`常量定义了分销相关业务在订单处理顺序中的位置,其值为300,排在 `COUPON` 后面。 + * 在电商的分销模式下,涉及到根据订单情况计算分销收益、记录分销相关信息等业务操作, + * 系统按照这个顺序,在完成优惠券优惠应用(按照 `COUPON` 顺序)之后,再处理分销相关的逻辑, + * 确保整个订单处理流程中各业务环节有序进行,避免优惠计算和业务逻辑执行出现冲突或错误。 + */ + int DISTRIBUTION = 300; +} \ No newline at end of file -- 2.34.1 From 305d551bf7203b35ef5ef306bb8399d197f9d426 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:30:11 +0800 Subject: [PATCH 055/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/dao/CategoryBrandMapper.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/dao/CategoryBrandMapper.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/dao/CategoryBrandMapper.java b/yami-shop-service/src/main/java/com/yami/shop/dao/CategoryBrandMapper.java new file mode 100644 index 0000000..87c9a0d --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/dao/CategoryBrandMapper.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yami.shop.bean.model.CategoryBrand; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 分类品牌Mapper接口,定义了与分类品牌相关的数据库操作 + * + * @author lanhai + */ +public interface CategoryBrandMapper extends BaseMapper { + + /** + * 批量插入分类品牌关系 + * @param categoryId 分类ID + * @param brandIds 品牌ID列表 + */ + void insertCategoryBrand(@Param("categoryId") Long categoryId, @Param("brandIds") List brandIds); + + /** + * 根据分类ID删除分类品牌关系 + * @param categoryId 分类ID + */ + void deleteByCategoryId(Long categoryId); + + /** + * 根据品牌ID删除分类品牌关系 + * @param brandId 品牌ID + */ + void deleteByBrandId(Long brandId); +} \ No newline at end of file -- 2.34.1 From 7668da9e2b38c8b10d5b0879ed9d7c58bcb6a000 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:31:04 +0800 Subject: [PATCH 056/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/mapper/CategoryBrandMapper.xml | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 yami-shop-service/src/main/resources/mapper/CategoryBrandMapper.xml diff --git a/yami-shop-service/src/main/resources/mapper/CategoryBrandMapper.xml b/yami-shop-service/src/main/resources/mapper/CategoryBrandMapper.xml new file mode 100644 index 0000000..674df73 --- /dev/null +++ b/yami-shop-service/src/main/resources/mapper/CategoryBrandMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + insert into tz_category_brand (category_id,brand_id) values + + + (#{categoryId},#{brandId}) + + + + + delete from tz_category_brand where category_id = #{categoryId} + + + + + delete from tz_category_brand where brand_id = #{brandId} + + \ No newline at end of file -- 2.34.1 From 69bbcdedb053607c4fce4e6bf1c901c23ff2793d Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:31:46 +0800 Subject: [PATCH 057/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/service/CategoryBrandService.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/CategoryBrandService.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/CategoryBrandService.java b/yami-shop-service/src/main/java/com/yami/shop/service/CategoryBrandService.java new file mode 100644 index 0000000..c68a5ff --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/CategoryBrandService.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yami.shop.bean.model.CategoryBrand; + +/** + * 分类品牌服务接口,继承了MyBatis Plus的IService接口,提供分类品牌相关的业务操作 + * + * @author lanhai + */ +public interface CategoryBrandService extends IService { + + // 该接口目前为空,继承了IService接口的所有方法,可以添加自定义的方法 +} \ No newline at end of file -- 2.34.1 From 1414e91be9a107fd4cf6e324c2e58b9ff11de36f Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:33:07 +0800 Subject: [PATCH 058/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/CategoryBrandServiceImpl.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/impl/CategoryBrandServiceImpl.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/CategoryBrandServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/CategoryBrandServiceImpl.java new file mode 100644 index 0000000..ddbe74a --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/CategoryBrandServiceImpl.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.yami.shop.bean.model.CategoryBrand; +import com.yami.shop.dao.CategoryBrandMapper; +import com.yami.shop.service.CategoryBrandService; + +/** + * 分类品牌服务实现类,提供分类品牌相关的业务逻辑实现 + * + * @author lanhai + */ +@Service +public class CategoryBrandServiceImpl extends ServiceImpl implements CategoryBrandService { + + /** + * 自动注入CategoryBrandMapper,用于操作分类品牌数据 + */ + @Autowired + private CategoryBrandMapper categoryBrandMapper; + + // 该类目前为空,继承了ServiceImpl的所有方法,可以添加自定义的方法实现 +} \ No newline at end of file -- 2.34.1 From 1f4af3300db037dbad7088e3f3d073e1929aa0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 23:33:42 +0800 Subject: [PATCH 059/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/bean/app/dto/DeliveryDto.java | 110 ++++++++++++++++++ .../shop/bean/app/dto/DeliveryInfoDto.java | 68 +++++++++++ .../com/yami/shop/bean/model/Delivery.java | 73 ++++++++++++ 3 files changed, 251 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/DeliveryDto.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/DeliveryInfoDto.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/model/Delivery.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/DeliveryDto.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/DeliveryDto.java new file mode 100644 index 0000000..160bbf2 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/DeliveryDto.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.app.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * @author lanhai + *//* + * * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * * + * * https://www.mall4j.com/ + * * + * * 未经允许,不可做商业用途! + * * + * * 版权所有,侵权必究! + * */ + * + *package com.yami.shop.bean.app.dto; + * + *import io.swagger.v3.oas.annotations.media.Schema; + *import lombok.Data; + *import java.util.List; + * + * /** + * * `DeliveryDto`类是一个数据传输对象(DTO),主要用于在电商系统应用层不同组件或模块之间传递与物流配送相关的综合信息。 + * * 它将涉及物流公司、物流订单以及对应的详细物流信息等多方面的数据进行整合封装,以便在诸如订单物流查询结果展示、 + * * 向用户反馈物流状态等业务场景下进行规范、清晰的数据传递,使得各业务环节能基于这些完整的物流数据进行相应操作, + * * 提升用户体验以及保障物流相关业务流程的顺畅进行。 + * + * * @author lanhai + * */ + *@Data + * + +public class DeliveryDto { + * + * /** + * * `companyName`属性用于存储物流公司的名称,它明确指出了负责当前物流配送的公司具体称谓, + * * 例如“顺丰速运”“申通快递”“京东物流”等常见的物流公司名称。在向用户展示物流信息或者在系统内部区分不同物流服务提供商时, + * * 这个属性起着关键的标识作用,并且通过 `@Schema` 注解标记其描述为“物流公司名称”且为必填项(`required = true`), + * * 确保在数据传递过程中该重要信息不会缺失,保证物流信息展示的完整性和准确性。 + * */ + * + @Schema(description = "物流公司名称", required = true) + * + private String companyName; + * + * /** + * * `companyHomeUrl`属性存放的是物流公司的官方网站网址,即用户可以通过访问这个网址获取该物流公司更详细全面的信息, + * * 比如公司的服务介绍、业务范围、客服联系方式以及快递查询入口等内容。在电商系统中,有时候可能会为用户提供链接跳转功能, + * * 方便用户直接从订单物流界面跳转到物流公司官网进一步了解相关情况,通过 `@Schema` 注解将其描述为“物流公司官网”且设为必填项, + * * 保证了在传递物流相关数据时该网址信息的完整性,有助于完善物流信息服务。 + * */ + * + @Schema(description = "物流公司官网", required = true) + * + private String companyHomeUrl; + * + * /** + * * `dvyFlowId`属性代表物流订单号,它是每个物流包裹在相应物流公司系统中的唯一标识符, + * * 通过这个订单号,物流公司可以准确查询到该包裹的具体运输情况、轨迹信息等,在电商系统中, + * * 用户查询自己订单的物流状态时,就是依靠这个订单号向物流公司的查询接口发起请求来获取物流详情的, + * * 借助 `@Schema` 注解标记其为“物流订单号”并设为必填项,确保在物流数据传递及后续查询操作中有准确的订单号依据。 + * */ + * + @Schema(description = "物流订单号", required = true) + * + private String dvyFlowId; + * + * /** + * * `data`属性是一个列表类型,其中的元素为 `DeliveryInfoDto` 类型,用于存放查询出的详细物流信息。 + * * 它包含了诸如快递包裹的当前状态(已揽收、运输中、派送中、已签收等)、所在区域、更新时间等具体的物流动态数据, + * * 通过将这些详细信息整合在一个列表中,可以完整地展示物流的整个过程以及当前状态,方便用户全面了解自己包裹的运输情况。 + * * 利用 `@Schema` 注解将其描述为“查询出的物流信息”并设为必填项,保证了在向用户或其他业务模块传递物流数据时, + * * 有完整且准确的详细物流内容可供使用,提升物流信息展示的质量和实用性。 + * */ + * + @Schema(description = "查询出的物流信息", required = true) + * + private List data; + * +} +@Data +public class DeliveryDto { + + @Schema(description = "物流公司名称" ,required=true) + private String companyName; + + @Schema(description = "物流公司官网" ,required=true) + private String companyHomeUrl; + + @Schema(description = "物流订单号" ,required=true) + private String dvyFlowId; + + @Schema(description = "查询出的物流信息" ,required=true) + private List data; + +} diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/DeliveryInfoDto.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/DeliveryInfoDto.java new file mode 100644 index 0000000..8991d36 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/DeliveryInfoDto.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.app.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * `DeliveryInfoDto`类是一个数据传输对象(DTO),在电商系统的物流相关业务场景中发挥着重要作用, + * 主要用于在不同的业务组件或模块之间传递快递物流过程中的具体信息,以便实现诸如向用户展示物流详情、 + * 系统内部记录物流轨迹等功能,确保物流信息的传递清晰、规范且完整。 + + * @author lanhai + */ +@Data +public class DeliveryInfoDto { + + /** + * `context`属性用于承载快递物流的详细描述信息,这是一个非常关键的属性,涵盖了包裹运输过程中的诸多重要情况。 + * 例如,它可能包含包裹当前所处的具体状态,像“已揽收”表示快递员已经接收了包裹开始运输流程,“运输中”说明包裹正在运往目的地的途中, + * “派送中”意味着快递即将送到收件人手中,“已签收”则代表收件人已成功接收包裹等;同时还可能包含一些额外的详细说明, + * 比如“因恶劣天气原因,派送可能延迟”或者“包裹在中转站点进行分拣”之类的特殊情况备注。无论是在前端页面展示给用户查看, + * 还是在系统后台用于记录和分析物流状态,这个属性都提供了最为核心的文字描述内容。 + * 通过 `@Schema` 注解明确其描述为“详细信息”且标记为必填项(`required = true`),以此确保在物流信息传递过程中, + * 该关键的详细情况能够完整无误地传达,使得各方都能准确知晓快递的具体进展和相关特殊情况。 + */ + @Schema(description = "详细信息", required = true) + private String context; + + /** + * `ftime`属性用于存放快递预计送达时间相关的信息,其具体的表现形式和含义通常会依据系统的业务设定有所不同。 + * 它有可能是一个遵循特定日期时间格式(比如常见的 `yyyy-MM-dd HH:mm:ss`)的精确时间,表示预计包裹将会在这个时间点送达收件人处; + * 也可能是一种相对模糊但通俗易懂的时间范围描述,例如“明天上午”“后天下午”等,目的是为用户提供一个大致的心理预期, + * 方便用户提前安排好接收包裹的相关事宜。不过,与其他必填属性不同,该属性并非强制要求每次都必须提供具体值, + * 因为在某些情况下,物流信息可能暂时无法准确预估送达时间,所以可根据实际掌握的物流数据完整性来决定是否填充该属性内容。 + */ + private String ftime; + + /** + * `location`属性主要记录快递包裹当前所在的区域信息,这对于用户追踪包裹位置以及了解其运输路线具有重要意义。 + * 具体而言,它可以精确到城市、区县等地理范围,比如“北京市朝阳区”“上海市浦东新区”;也有可能进一步细化到具体的快递站点名称, + * 像“XX快递XX街道营业点”,或者是更宽泛一点的配送区域划分,例如“XX小区配送范围”等。通过明确快递所在的区域, + * 用户能够对包裹的大致地理位置有较为直观清晰的了解,从而更好地预估包裹到达自己手中的时间等情况。 + * 借助 `@Schema` 注解将其描述为“快递所在区域”并设定为必填项,确保在物流数据传递过程中, + * 始终能准确传达包裹的所在位置信息,方便展示给用户知晓其运输轨迹。 + */ + @Schema(description = "快递所在区域", required = true) + private String location; + + /** + * `time`属性用于记录物流信息的更新时间,通常采用标准的日期时间格式(如 `yyyy-MM-dd HH:mm:ss`)来准确表示。 + * 这个时间点明确了当前这条物流信息是在何时被记录或者更新的,其重要性体现在多个方面。对于用户来说, + * 可以依据更新时间判断所看到的物流信息是否是最新的,从而了解包裹运输状态的时效性;而在系统内部, + * 开发人员可以基于更新时间对物流数据进行排序、筛选等操作,以便更有序地管理和展示物流信息,或者进行数据分析等工作。 + * 通过 `@Schema` 注解标记为“物流更新时间”且设为必填项,保证了物流数据的及时性和准确性能够在各个业务环节得以准确体现, + * 维持整个物流信息管理的有序性和可靠性。 + */ + @Schema(description = "物流更新时间", required = true) + private String time; +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Delivery.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Delivery.java new file mode 100644 index 0000000..63b2d32 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Delivery.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.model; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.io.Serializable; +import java.util.Date; + +/** + * `Delivery`类是一个实体类,用于表示物流公司相关的信息,在电商系统等业务场景中有着重要的作用, + * 主要负责在数据库层面记录物流公司的各项关键数据,方便后续进行与物流相关的业务操作, + * 例如查询物流信息、展示可用的物流公司列表、根据物流公司配置进行快递单号查询等操作。 + + * @author lanhai + */ +@Data +@TableName("tz_delivery") +public class Delivery implements Serializable { + + /** + * `dvyId`属性作为该类的主键,是每条物流公司记录在数据库中的唯一标识符。 + * 它具有唯一性,通过这个 `dvyId`,系统能够在数据库中精准地定位、操作某一条具体的物流公司相关记录, + * 比如在更新、删除特定物流公司信息或者根据该ID查询其详细属性时,都要依赖这个唯一标识来确保操作的准确性和针对性, + * 是整个物流公司数据管理中的关键标识字段。 + */ + @TableId + private Long dvyId; + + /** + * `dvyName`属性用于存储物流公司的名称,例如“顺丰速运”“中通快递”“圆通速递”等常见的物流公司称谓。 + * 在电商系统中,用户在选择快递配送方式或者查看订单物流信息时,会看到这个名称展示出来, + * 便于直观地识别是哪家物流公司负责包裹的运输,同时也是关联到具体物流服务的重要标识信息。 + */ + private String dvyName; + + /** + * `companyHomeUrl`属性存放物流公司的公司主页网址,即用户可以通过访问这个网址了解该物流公司的更多详细信息, + * 比如公司的服务范围、业务介绍、客服联系方式等内容,在电商系统中,可能会为用户提供链接跳转功能, + * 使其能够方便地访问物流公司官网获取相关资讯,不过该属性也可能存在部分物流公司未提供网址而为空值的情况。 + */ + private String companyHomeUrl; + + /** + * `recTime`属性代表物流公司相关信息的建立时间,通常记录的是这条物流公司记录首次被创建并录入系统的日期和时间, + * 以 `Date` 类型存储,格式一般遵循系统默认的时间格式(例如 `yyyy-MM-dd HH:mm:ss`), + * 它可以用于数据的溯源、统计分析等操作,比如查看不同时间段内新增了哪些物流公司等业务场景中会用到该时间信息。 + */ + private Date recTime; + + /** + * `modifyTime`属性用于记录物流公司信息的最后修改时间,同样是以 `Date` 类型呈现具体的时间点, + * 每次对该物流公司的相关属性(如名称、网址、物流查询接口等)进行更新操作后,这个时间会随之更新, + * 方便在数据管理中知晓该条记录最近一次的变动情况,也有助于进行数据版本控制、审计等相关业务处理。 + */ + private Date modifyTime; + + /** + * `queryUrl`属性存放的是物流公司的物流查询接口网址,在电商系统需要获取某个订单的物流实时状态时, + * 可以通过调用该网址对应的接口(按照接口规定的请求参数、方式等)来获取相应的物流信息, + * 例如快递包裹的当前位置、运输状态等关键数据,是实现物流信息查询功能的重要配置信息。 + */ + private String queryUrl; +} \ No newline at end of file -- 2.34.1 From e260078095aa8ce06be09a83d6918c1ec58e4af7 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:33:50 +0800 Subject: [PATCH 060/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/dao/CategoryMapper.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/dao/CategoryMapper.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/dao/CategoryMapper.java b/yami-shop-service/src/main/java/com/yami/shop/dao/CategoryMapper.java new file mode 100644 index 0000000..d7b53d8 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/dao/CategoryMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yami.shop.bean.model.Category; +import java.util.List; + +/** + * 分类Mapper接口,定义了与分类相关的数据库操作 + * + * @author lanhai + */ +public interface CategoryMapper extends BaseMapper { + + /** + * 根据父级ID查询分类列表 + * @param parentId 父级ID + * @return 分类列表 + */ + List listByParentId(Long parentId); + + /** + * 根据店铺ID查询分类列表 + * @param shopId 店铺ID + * @return 分类列表 + */ + List tableCategory(Long shopId); +} \ No newline at end of file -- 2.34.1 From 84e699930daa64a7ed098fc01d7b5afa589ddf89 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:34:57 +0800 Subject: [PATCH 061/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/mapper/CategoryMapper.xml | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 yami-shop-service/src/main/resources/mapper/CategoryMapper.xml diff --git a/yami-shop-service/src/main/resources/mapper/CategoryMapper.xml b/yami-shop-service/src/main/resources/mapper/CategoryMapper.xml new file mode 100644 index 0000000..464914e --- /dev/null +++ b/yami-shop-service/src/main/resources/mapper/CategoryMapper.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- 2.34.1 From 2eef085e09978f028b0446db387aee95de1e93af Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:35:55 +0800 Subject: [PATCH 062/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/dao/CategoryPropMapper.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/dao/CategoryPropMapper.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/dao/CategoryPropMapper.java b/yami-shop-service/src/main/java/com/yami/shop/dao/CategoryPropMapper.java new file mode 100644 index 0000000..97fa783 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/dao/CategoryPropMapper.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yami.shop.bean.model.CategoryProp; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 分类属性Mapper接口,定义了与分类属性相关的数据库操作 + * + * @author lanhai + */ +public interface CategoryPropMapper extends BaseMapper { + + /** + * 批量插入分类属性关系 + * @param categoryId 分类ID + * @param propIds 属性ID列表 + */ + void insertCategoryProp(@Param("categoryId") Long categoryId, @Param("propIds") List propIds); + + /** + * 根据分类ID删除分类属性关系 + * @param categoryId 分类ID + */ + void deleteByCategoryId(Long categoryId); + + /** + * 根据属性ID删除分类属性关系 + * @param propId 属性ID + */ + void deleteByPropId(Long propId); +} \ No newline at end of file -- 2.34.1 From 1f675f8fe7cf4ad5d04aee7a4875b991efead917 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:36:52 +0800 Subject: [PATCH 063/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/mapper/CategoryPropMapper.xml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 yami-shop-service/src/main/resources/mapper/CategoryPropMapper.xml diff --git a/yami-shop-service/src/main/resources/mapper/CategoryPropMapper.xml b/yami-shop-service/src/main/resources/mapper/CategoryPropMapper.xml new file mode 100644 index 0000000..987e341 --- /dev/null +++ b/yami-shop-service/src/main/resources/mapper/CategoryPropMapper.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + insert into tz_category_prop (category_id,prop_id) values + + + (#{categoryId},#{propId}) + + + + + + delete from tz_category_prop where category_id = #{categoryId} + + + + + delete from tz_category_prop where prop_id = #{propId} + + \ No newline at end of file -- 2.34.1 From fe3554e6f36d9038ca730635326a139142bbe208 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:37:31 +0800 Subject: [PATCH 064/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/service/CategoryPropService.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/CategoryPropService.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/CategoryPropService.java b/yami-shop-service/src/main/java/com/yami/shop/service/CategoryPropService.java new file mode 100644 index 0000000..5585ad7 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/CategoryPropService.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yami.shop.bean.model.CategoryProp; + +/** + * 分类属性服务接口,继承了MyBatis Plus的IService接口,提供分类属性相关的业务操作 + * + * @author lanhai + */ +public interface CategoryPropService extends IService { + + // 该接口目前为空,继承了IService接口的所有方法,可以添加自定义的方法 +} -- 2.34.1 From 2476fe3158400130b203018df27e5a0dd51b9cf5 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:38:44 +0800 Subject: [PATCH 065/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/CategoryPropServiceImpl.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/impl/CategoryPropServiceImpl.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/CategoryPropServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/CategoryPropServiceImpl.java new file mode 100644 index 0000000..e43a8ef --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/CategoryPropServiceImpl.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.yami.shop.bean.model.CategoryProp; +import com.yami.shop.dao.CategoryPropMapper; +import com.yami.shop.service.CategoryPropService; + +/** + * 分类属性服务实现类,提供分类属性相关的业务逻辑实现 + * + * @author lanhai + */ +@Service +public class CategoryPropServiceImpl extends ServiceImpl implements CategoryPropService { + + /** + * 自动注入CategoryPropMapper,用于操作分类属性数据 + */ + @Autowired + private CategoryPropMapper categoryPropMapper; + + // 该类目前为空,继承了ServiceImpl的所有方法,可以添加自定义的方法实现 +} -- 2.34.1 From 3cc3778ef8a76dedfa25d3ff2e3a64b494792327 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:40:12 +0800 Subject: [PATCH 066/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/service/CategoryService.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/CategoryService.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/CategoryService.java b/yami-shop-service/src/main/java/com/yami/shop/service/CategoryService.java new file mode 100644 index 0000000..cc21de3 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/CategoryService.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yami.shop.bean.model.Category; +import java.util.List; + +/** + * 商品分类服务接口,继承了MyBatis Plus的IService接口,并扩展了分类相关的业务操作 + * + * @author lanhai + */ +public interface CategoryService extends IService { + + /** + * 根据父级ID查询分类列表,用于获取一级分类时传入0 + * @param parentId 父级ID,0表示查询一级分类 + * @return 分类列表 + */ + List listByParentId(Long parentId); + + /** + * 根据店铺ID查询分类列表,并根据seq排序,用于页面表单展示 + * @param shopId 店铺ID + * @return 分类列表 + */ + List tableCategory(Long shopId); + + /** + * 保存分类信息,包括品牌和参数 + * @param category 分类对象 + */ + void saveCategory(Category category); + + /** + * 更新分类信息,包括品牌和参数 + * @param category 分类对象 + */ + void updateCategory(Category category); + + /** + * 删除分类信息,包括品牌、参数以及分类对应的图片 + * @param categoryId 分类ID + */ + void deleteCategory(Long categoryId); + + /** + * 根据店铺ID和层级获取商品分类树,用于构建分类树结构 + * @param shopId 店铺ID + * @param grade 分类层级 + * @return 分类树列表 + */ + List treeSelect(Long shopId, int grade); + +} \ No newline at end of file -- 2.34.1 From 63636e38bfc4931132c5060a6056f7e2000f6696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 23:41:37 +0800 Subject: [PATCH 067/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/bean/app/dto/DiscountDto.java | 67 +++++++++ .../com/yami/shop/bean/dto/HotSearchDto.java | 57 ++++++++ .../shop/bean/param/DeliveryOrderParam.java | 129 ++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/DiscountDto.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/dto/HotSearchDto.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/param/DeliveryOrderParam.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/DiscountDto.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/DiscountDto.java new file mode 100644 index 0000000..a2f4b00 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/DiscountDto.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.app.dto; + +import lombok.Data; +import java.io.Serializable; + +/** + * `DiscountDto`类是一个数据传输对象(DTO),用于在电商系统应用层的不同组件或模块之间传递与折扣(优惠)相关的信息。 + * 其主要目的是将涉及商品折扣、满减等优惠活动的各项数据进行整合封装,方便在诸如订单创建、订单金额计算(包含优惠金额计算)、 + * 优惠活动展示等业务操作中进行数据传递,使得数据交互更加清晰、规范,避免直接传递复杂或分散的底层数据结构, + * 提高不同业务层之间协作的效率以及代码的整体可读性。 + + * @author lanhai + */ +@Data +public class DiscountDto implements Serializable { + + /** + * 优惠活动的唯一标识符,用于在系统中精准定位某一个具体的优惠活动。 + * 通过这个 `discountId`,可以关联到优惠活动的详细信息,例如优惠活动的名称(如“满200减50”“8折优惠”等)、 + * 优惠活动的类型(是满减活动、折扣活动还是其他类型)、适用的商品范围(哪些商品可以参与该优惠)、 + * 活动的有效期限(开始时间和结束时间)以及其他相关的规则设定等关键属性, + * 在订单处理过程中,判断该优惠活动是否可用于当前订单以及计算具体优惠金额时,都需要依赖这个唯一标识来获取相应信息。 + */ + private Long discountId; + + /** + * 关联的订单的唯一标识符,即 `orderId`,用于明确该优惠活动是应用于哪一个具体的订单上。 + * 在电商系统中,一个优惠活动可能可以在多个订单中使用(前提是满足活动使用条件),同样,一个订单也可能会使用多个优惠活动, + * 通过这个 `orderId` 能够清晰地建立起优惠活动与订单之间的对应关系,便于后续业务逻辑围绕该订单进行优惠相关的处理, + * 比如统计该订单使用了哪些优惠活动、计算因优惠活动使用而减免的总金额等操作。 + */ + private Long orderId; + + /** + * 该优惠活动在当前订单中实际减免的金额数值,用 `discountAmount` 表示。 + * 在订单处理流程中,当确定某优惠活动可应用于当前订单后,需要根据活动的规则(如满减活动的满减金额设定、折扣活动的折扣比例等) + * 来计算出它在这个订单里具体能减免多少钱,这个属性就用于记录该实际减免的金额, + * 是计算订单最终应付金额、展示订单优惠明细等操作中重要的数据依据,能直观体现优惠活动给订单带来的优惠效果。 + */ + private Double discountAmount; + + /** + * 表示优惠活动在当前订单中的使用状态的标识,例如可以用布尔类型的 `isUsed` 属性来表示(`true` 表示已使用,`false` 表示未使用)。 + * 在整个订单生命周期内,优惠活动的使用状态可能会发生变化,例如用户最初选择参与某优惠活动,但后续又取消了选择, + * 或者由于某些条件不满足导致优惠活动无法使用等情况,通过这个属性可以清晰地反映出优惠活动当前是否已在该订单中被实际应用, + * 方便在业务逻辑中进行相应的判断和处理,确保订单与优惠活动之间的关联状态准确无误。 + */ + private boolean isUsed; + + /** + * 优惠活动的剩余可用次数(如果优惠活动有使用次数限制的话),用 `remainingUseTimes` 表示,通常为整数类型。 + * 对于一些有限次使用的优惠活动,每次在订单中成功使用后,剩余可用次数会相应减少, + * 这个属性用于记录当前优惠活动针对后续订单还能被使用的次数,在判断优惠活动是否还能继续在其他订单中使用等业务逻辑中起着关键作用, + * 有助于合理控制优惠活动的使用频率,遵循其预先设定的使用规则。 + */ + private Integer remainingUseTimes; +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/dto/HotSearchDto.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/dto/HotSearchDto.java new file mode 100644 index 0000000..8f5d2d1 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/dto/HotSearchDto.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import java.io.Serializable; + +/** + * `HotSearchDto`类是一个数据传输对象(DTO),主要用于在电商系统应用层不同组件或模块之间传递热搜相关的数据信息。 + * 其作用是将与热搜有关的关键数据进行整合封装,方便在诸如搜索功能模块、页面展示等业务场景下进行数据传递, + * 使得数据的交互更加清晰、规范且符合业务需求,便于不同业务部分基于这些信息进行相应的操作,例如展示热门搜索词条、 + * 根据热搜进行相关商品推荐等。 + + * @author lanhai + */ +@Schema(description = "热搜数据") +@Data +public class HotSearchDto implements Serializable { + + /** + * `hotSearchId`属性用于存放热搜的唯一标识符,也就是热搜在系统中的ID值,通常为一个Long类型的数字。 + * 这个ID能够在整个系统中精准地定位到某一条具体的热搜记录,方便进行数据的管理操作,比如对热搜词条进行更新、删除、 + * 查询其详细属性等操作时,都依赖这个唯一标识来确保操作的准确性和针对性。通过 `@Schema` 注解明确其描述为“热搜id”, + * 虽然它在当前设定下并非必填项,但在系统数据管理以及后续可能的功能扩展中,这个ID有着重要的标识作用, + * 有助于区分不同的热搜内容,保证数据的唯一性和可追溯性。 + */ + @Schema(description = "热搜id") + private Long hotSearchId; + + /** + * `title`属性代表热搜的标题,通常是一个简短且能够直观体现用户搜索意图或者热门搜索主题的字符串。 + * 例如在电商系统中,可能是“夏季连衣裙”“智能手机推荐”等类似的文字表述,它是用户在搜索界面看到的热搜词条的主要展示内容, + * 用于吸引用户点击并进行相应的搜索操作,进而查看与该标题相关的商品、内容等信息。通过 `@Schema` 注解将其描述为“标题”, + * 方便在API文档(如使用Swagger等工具生成时)或者代码注释中清晰地表明该属性的含义,便于开发人员理解和使用。 + */ + @Schema(description = "标题") + private String title; + + /** + * `content`属性用于存储与热搜相关的具体内容信息,其具体形式和含义可以根据业务场景有所不同。 + * 在一些情况下,它可能是对热搜标题更详细的补充说明,比如对于“夏季连衣裙”这个热搜标题,内容里可能会包含一些流行款式、 + * 热门颜色等相关介绍;也有可能是关联到该热搜词条下的一些关键商品信息、推荐理由等内容,方便在用户点击热搜词条后, + * 展示更丰富、有价值的信息供用户参考。通过 `@Schema` 注解标记其为“内容”,明确该属性在整个 `HotSearchDto` 类中所承载的数据含义, + * 便于在不同业务环节准确运用该属性所存储的信息。 + */ + @Schema(description = "内容") + private String content; +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/param/DeliveryOrderParam.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/param/DeliveryOrderParam.java new file mode 100644 index 0000000..f1d5f92 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/param/DeliveryOrderParam.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.param; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; + +/** + * `DeliveryOrderParam`类是作为一个参数对象,用于接收在电商系统中与物流订单操作相关的输入参数。 + * 其主要目的是在前端与后端进行交互时,承载那些对于处理物流订单必不可少的数据,以便后端能够依据这些准确的数据来执行对应的业务逻辑, + * 例如在物流单号绑定订单、查询物流状态等具体业务操作场景下,为后端服务提供必要的信息支撑。 + + * @author lanhai + */ +public class DeliveryOrderParam { + + /** + * `orderNumber`属性用于存放具体的订单号信息,它在整个电商系统里是唯一标识每个订单的关键字符串。 + * 每一个订单从生成之时起,就会被赋予这样一个独一无二的订单号,通过这个订单号,后端系统能够精确地关联到该订单对应的所有详细信息, + * 比如下单用户的信息、所购买商品的详情、订单的金额以及其他各种与该订单相关的附属数据等。 + * 在涉及物流相关操作时,这个订单号尤为重要,它能让系统知晓当前的物流业务是针对哪一个具体订单展开的,例如将获取到的物流信息准确匹配到对应的订单上。 + * + * 此处使用了 `@NotBlank` 注解,同时搭配了提示消息“订单号不能为空”,这样在进行数据校验时(例如前端向后端传递参数时), + * 如果该属性值为空字符串,就会触发相应的验证错误提示,以此来保证传递过来的订单号是有效的、非空的。 + * 并且,通过 `@Schema` 注解,在生成 API 文档(像使用 Swagger 等工具展示接口信息时),会为该属性添加明确的“订单号”描述, + * 同时标记其为必填项(`required = true`),清晰地告知接口调用者在发起涉及物流订单的相关操作时,必须提供这个关键的订单号参数, + * 从而确保后端接收到完整且有效的数据,以保障后续业务逻辑能够顺利执行。 + */ + @NotBlank(message = "订单号不能为空") + @Schema(description = "订单号", required = true) + private String orderNumber; + + /** + * `dvyId`属性代表的是快递公司在系统中的唯一标识符,也就是快递公司的 ID,其数据类型通常为 `Long`。 + * 在电商平台运营过程中,会事先录入众多合作的快递公司信息到系统中,并为每家快递公司分配一个独一无二的 ID, + * 通过这个 ID,系统可以进一步获取该快递公司的各种详细信息,例如公司的名称、官方网站地址、对应的物流查询接口等关键数据。 + * 在物流订单相关的业务操作里,明确是由哪家快递公司负责运输当前订单的商品,就完全依赖这个 `dvyId` 参数来确定。 + * + * 同样地,借助 `@NotBlank` 注解来确保该参数不能为空,若为空的话,就会出现“快递公司 id 不能为空”的错误提示, + * 以此来强制要求接口调用者必须提供有效的快递公司 ID 值。并且,通过 `@Schema` 注解在 API 文档中为其添加“快递公司”的描述, + * 同时将其设定为必填项,方便接口调用者清楚知晓该参数的含义以及在调用接口时需要正确传递相应的值, + * 进而保证物流相关业务能够准确关联到对应的快递公司,保障整个物流流程的准确执行。 + */ + @NotBlank(message = "快递公司id不能为空") + @Schema(description = "快递公司", required = true) + private Long dvyId; + + /** + * `dvyFlowId`属性用于存储具体的物流单号,它是快递公司为每一个快递包裹分配的唯一识别编号,一般是一串特定格式的字符串。 + * 在物流业务流程中,无论是用户查询包裹的实时物流状态,还是系统内部跟踪包裹的运输轨迹等操作,都需要依靠这个物流单号来向对应的快递公司查询接口发起请求, + * 进而获取到包裹的详细物流信息,比如包裹当前处于运输的哪个阶段、在哪个城市的哪个站点等情况,所以它是物流查询操作中至关重要的一个参数。 + * + * 通过使用 `@NotBlank` 注解,规定该参数不能为空,若前端传递过来的这个属性值为空字符串,就会触发“物流单号不能为空”的错误提示, + * 以此来保证传递的物流单号是有效的、真实存在的。同时,借助 `@Schema` 注解在 API 文档里为其添加“物流单号”的描述, + * 并将其标记为必填项,告知接口调用者在进行物流相关操作时必须提供准确的物流单号,确保物流查询等业务能够顺利开展。 + */ + @NotBlank(message = "物流单号不能为空") + @Schema(description = "物流单号", required = true) + private String dvyFlowId; + + /** + * `getDvyId`方法是遵循 JavaBean 规范的访问器方法(Getter 方法),其作用是对外提供获取 `dvyId` 属性值的途径。 + * 在其他类需要获取当前 `DeliveryOrderParam` 对象中存储的快递公司 ID 信息时,就可以通过调用这个方法来得到相应的值, + * 这符合面向对象编程中对数据封装和访问控制的原则,通过这种规范的方法调用,能够保证数据获取过程的安全性和规范性, + * 避免外部类直接访问属性可能带来的一些不合理操作,同时也使得代码结构更加清晰、易于维护。 + */ + public Long getDvyId() { + return dvyId; + } + + /** + * `setDvyId`方法是与 `getDvyId` 方法相对应的修改器方法(Setter 方法),它用于为 `dvyId` 属性设置新的值。 + * 当在业务逻辑中需要更新当前对象所存储的快递公司 ID 信息时(虽然在实际情况中这种更新可能相对较少出现), + * 外部代码可以通过调用这个方法,并传入一个新的 `Long` 类型的值,来实现对 `dvyId` 属性值的修改操作。 + * 在这个方法内部,还可以根据具体的业务需求添加一些额外的数据验证逻辑,例如验证传入的 ID 是否符合系统中已有的快递公司 ID 规则等, + * 以此来进一步保障数据的合法性和准确性,确保设置的快递公司 ID 是有效的、符合业务要求的。 + */ + public void setDvyId(Long dvyId) { + this.dvyId = dvyId; + } + + /** + * `getDvyFlowId`方法同样是一个 Getter 方法,它的功能是对外提供获取 `dvyFlowId` 属性值的渠道。 + * 在涉及物流业务操作的其他代码部分,如果需要知晓当前对象中存储的物流单号具体是什么,就可以调用这个方法来获取相应的字符串值, + * 遵循了 JavaBean 的规范,使得对属性的访问更加有序、可控,方便不同模块之间基于统一的方式来获取和使用这个重要的物流单号数据。 + */ + public String getDvyFlowId() { + return dvyFlowId; + } + + /** + * `setDvyFlowId`方法作为 `getDvyFlowId` 的对应 Setter 方法,用于对 `dvyFlowId` 属性进行赋值操作。 + * 当需要更新物流单号信息时(例如可能由于物流单号录入错误需要重新修改等情况),外部代码可以调用这个方法, + * 传入一个新的符合物流单号格式要求的字符串值,来改变 `DeliveryOrderParam` 对象中存储的物流单号内容。 + * 并且,在方法内部也可以根据实际业务情况添加必要的数据校验逻辑,例如检查新传入的物流单号是否符合对应快递公司的单号格式规范等, + * 从而保证设置的物流单号是正确且有效的,避免因错误的物流单号导致后续物流查询等业务出现问题。 + */ + public void setDvyFlowId(String dvyFlowId) { + this.dvyFlowId = dvyFlowId; + } + + /** + * `getOrderNumber`方法是用于获取 `orderNumber` 属性值的 Getter 方法,其作用与前面介绍的其他 Getter 方法类似。 + * 它为外部代码提供了获取当前对象中存储的订单号信息的途径,在后续的业务逻辑中,例如根据订单号去查询订单详情、 + * 将物流信息与对应的订单进行关联匹配等操作时,都需要通过调用这个方法来获取准确的订单号数据, + * 以保证整个业务流程能够围绕正确的订单来展开,同时也遵循了 JavaBean 规范,使得代码在数据访问方面更加清晰、规范。 + */ + public String getOrderNumber() { + return orderNumber; + } + + /** + * `setOrderNumber`方法是对应 `getOrderNumber` 的 Setter 方法,用于对 `orderNumber` 属性进行赋值操作。 + * 尽管在正常的电商业务流程中,订单号一旦生成通常是不允许随意更改的,但在某些特殊情况下(如数据修正等极个别场景), + * 如果确实需要更新订单号信息,外部代码可以调用这个方法,并传入一个新的符合订单号生成规则的字符串值,来改变该属性的存储内容。 + * 同样,在方法内部也可以添加适当的数据验证逻辑,例如检查新的订单号是否满足唯一性等要求,以保障订单号的合法性和整个系统的数据一致性。 + */ + public void setOrderNumber(String orderNumber) { + this.orderNumber = orderNumber; + } +} \ No newline at end of file -- 2.34.1 From 2d7142740347b058b89957f3450bab3e28f8db70 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Mon, 16 Dec 2024 23:42:24 +0800 Subject: [PATCH 068/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/CategoryServiceImpl.java | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/impl/CategoryServiceImpl.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/CategoryServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/CategoryServiceImpl.java new file mode 100644 index 0000000..7359aec --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/CategoryServiceImpl.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service.impl; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import com.yami.shop.bean.model.Category; +import com.yami.shop.dao.CategoryBrandMapper; +import com.yami.shop.dao.CategoryMapper; +import com.yami.shop.dao.CategoryPropMapper; +import com.yami.shop.service.AttachFileService; +import com.yami.shop.service.CategoryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 商品分类服务实现类,提供商品分类相关的业务逻辑实现 + * + * @author lanhai + */ +@Service +public class CategoryServiceImpl extends ServiceImpl implements CategoryService { + + @Autowired + private CategoryMapper categoryMapper; + + @Autowired + private CategoryBrandMapper categoryBrandMapper; + + @Autowired + private CategoryPropMapper categoryPropMapper; + + @Autowired + private AttachFileService attachFileService; + + /** + * 根据父级ID查询分类列表 + * @param parentId 父级ID + * @return 分类列表 + */ + @Override + public List listByParentId(Long parentId) { + return categoryMapper.listByParentId(parentId); + } + + /** + * 根据店铺ID查询分类列表,并根据seq排序,用于页面表单展示 + * @param shopId 店铺ID + * @return 分类列表 + */ + @Override + public List tableCategory(Long shopId) { + return categoryMapper.tableCategory(shopId); + } + + /** + * 保存分类信息,包括品牌和参数 + * @param category 分类对象 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void saveCategory(Category category) { + category.setRecTime(new Date()); + // 保存分类信息 + categoryMapper.insert(category); + insertBrandsAndAttributes(category); + } + + /** + * 更新分类信息,包括品牌和参数 + * @param category 分类对象 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void updateCategory(Category category) { + Category dbCategory = categoryMapper.selectById(category.getCategoryId()); + category.setUpdateTime(new Date()); + // 保存分类信息 + categoryMapper.updateById(category); + // 先删除后增加 + deleteBrandsAndAttributes(category.getCategoryId()); + insertBrandsAndAttributes(category); + // 如果以前有图片,并且图片与现在不同,则删除以前的图片 + // if (StrUtil.isNotBlank(dbCategory.getPic()) && !dbCategory.getPic().equals(category.getPic())) { + // attachFileService.deleteFile(dbCategory.getPic()); + // } + } + + /** + * 删除分类信息,包括品牌、参数以及分类对应的图片 + * @param categoryId 分类ID + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteCategory(Long categoryId) { + Category category = categoryMapper.selectById(categoryId); + categoryMapper.deleteById(categoryId); + deleteBrandsAndAttributes(categoryId); + // if (StrUtil.isNotBlank(category.getPic())) { + // attachFileService.deleteFile(category.getPic()); + // } + } + + /** + * 删除所有分类所关联的品牌和参数 + * @param categoryId 分类ID + */ + private void deleteBrandsAndAttributes(Long categoryId) { + // 删除所有分类所关联的品牌 + categoryBrandMapper.deleteByCategoryId(categoryId); + // 删除所有分类所关联的参数 + categoryPropMapper.deleteByCategoryId(categoryId); + } + + /** + * 保存分类与品牌信息,保存分类与参数信息 + * @param category 分类对象 + */ + private void insertBrandsAndAttributes(Category category) { + // 保存分类与品牌信息 + if(CollUtil.isNotEmpty(category.getBrandIds())){ + categoryBrandMapper.insertCategoryBrand(category.getCategoryId(), category.getBrandIds()); + } + // 保存分类与参数信息 + if(CollUtil.isNotEmpty(category.getAttributeIds())){ + categoryPropMapper.insertCategoryProp(category.getCategoryId(), category.getAttributeIds()); + } + } + + /** + * 根据店铺ID和层级,获取商品分类树 + * @param shopId 店铺ID + * @param grade 分类层级 + * @return 分类树列表 + */ + @Override + public List treeSelect(Long shopId, int grade) { + List categories = categoryMapper.selectList(new LambdaQueryWrapper().le(Category::getGrade, grade).eq(Category::getShopId, shopId)); + return categoryListToTree(categories); + } + + /** + * 将分类列表转换为树形结构 + * @param categorys 分类列表 + * @return 树形结构的分类列表 + */ + public List categoryListToTree(List categorys){ + if (CollectionUtils.isEmpty(categorys)) { + return Lists.newArrayList(); + } + Map> categoryMap = categorys.stream().collect(Collectors.groupingBy(Category::getParentId)); + List rootList = categoryMap.get(0L); + transformCategoryTree(rootList, categoryMap); + return rootList; + } + + /** + * 递归构建分类树 + * @param categorys 当前层级的分类列表 + * @param categoryMap 所有分类的映射 + */ + public void transformCategoryTree(List categorys, Map> categoryMap) { + for (Category category : categorys) { + List nextList = categoryMap.get(category.getCategoryId()); + if (CollectionUtils.isEmpty(nextList)) { + continue; + } + // 将排序完成的list设为下一层级 + category.setCategories(nextList); + // 处理下一层级 + transformCategoryTree(nextList, categoryMap); + } + } + +} \ No newline at end of file -- 2.34.1 From 5a93c224e65fe5f4fa678f5e87f5913dbe73e048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 23:48:28 +0800 Subject: [PATCH 069/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/bean/app/dto/IndexImgDto.java | 81 +++++++++++ .../yami/shop/bean/app/param/LoginParam.java | 104 ++++++++++++++ .../com/yami/shop/bean/model/IndexImg.java | 129 ++++++++++++++++++ 3 files changed, 314 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/IndexImgDto.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/app/param/LoginParam.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/model/IndexImg.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/IndexImgDto.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/IndexImgDto.java new file mode 100644 index 0000000..106280b --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/IndexImgDto.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.app.dto; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.yami.shop.common.serializer.json.ImgJsonSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import java.util.Date; + +/** + * `IndexImgDto`类是一个数据传输对象(DTO),主要用于在电商系统应用层不同组件或模块之间传递首页图片相关的信息。 + * 其作用是将首页图片的关键数据进行整合封装,使得在诸如首页图片展示配置、图片数据获取与传递等业务场景下, + * 数据的交互更加清晰、规范且符合业务需求,便于不同业务部分基于这些信息进行相应的操作,例如按照指定顺序展示图片、 + * 根据类型筛选图片、依据关联ID查找相关联的其他业务对象等。 + + * @author lanhai + */ +@Schema(description = "首页图片对象") +@Data +public class IndexImgDto { + + /** + * `imgUrl`属性用于存放首页图片的网络地址或者在系统中的存储路径等信息,具体取决于系统的图片存储和访问方式。 + * 例如在使用云存储服务时,可能是该图片在云存储中的完整URL地址;若是本地存储,则可能是相对系统根目录的文件路径。 + * 这个属性是展示首页图片的关键数据,前端页面通过获取该 `imgUrl` 来准确显示对应的图片内容。 + * 同时,借助 `@JsonSerialize(using = ImgJsonSerializer.class)` 注解,在将该类对象转换为JSON格式数据(如接口返回数据给前端时), + * 会使用自定义的序列化器对这个属性进行特殊处理,可能是对图片路径进行格式调整、添加域名等操作,使其符合前端展示或者与其他系统交互的要求。 + * 通过 `@Schema` 注解明确其描述为“图片Url”且标记为必填项(`required = true`),确保在数据传递过程中该图片地址信息的完整性, + * 以便顺利实现图片展示功能。 + */ + @JsonSerialize(using = ImgJsonSerializer.class) + @Schema(description = "图片Url", required = true) + private String imgUrl; + + /** + * `seq`属性代表首页图片的展示顺序,通常为整数类型,用于确定在首页展示图片时各个图片的先后排列次序。 + * 可以按照业务需求设定相应的排序规则,比如较小的数值在前、较大的数值在后等,通过调整不同图片的 `seq` 值, + * 能够灵活地改变首页图片的展示顺序,实现对首页展示效果的个性化定制和优化。 + * 通过 `@Schema` 注解将其描述为“图片顺序”并设定为必填项,保证在传递首页图片相关数据时,展示顺序信息不会缺失, + * 便于前端或其他业务模块按照正确的顺序展示图片。 + */ + @Schema(description = "图片顺序", required = true) + private Integer seq; + + /** + * `uploadTime`属性记录的是首页图片的上传时间,以 `Date` 类型存储,格式一般遵循系统默认的时间格式(例如 `yyyy-MM-dd HH:mm:ss`)。 + * 它在业务中有多方面的作用,比如可以用于数据的溯源,方便查看不同时间段内首页图片的更新情况;也能用于统计分析, + * 了解图片的上传频率等信息。通过 `@Schema` 注解标记其描述为“上传时间”且设为必填项,确保在图片数据传递过程中, + * 上传时间信息能够准确传达,有助于进行相关的数据管理和业务操作。 + */ + @Schema(description = "上传时间", required = true) + private Date uploadTime; + + /** + * `type`属性用于区分首页图片的类型,一般通过不同的整数值来对应不同的图片分类。 + * 例如,可以设定 `1` 表示商品推荐图片,`2` 表示活动宣传图片,`3` 表示店铺形象展示图片等。 + * 通过这种分类方式,在业务逻辑中能够针对不同类型的图片进行差异化的处理和展示,比如不同类型图片展示在首页的不同位置、 + * 采用不同的展示样式等,也便于进行分类管理和统计分析等操作。 + * 通过 `@Schema` 注解明确其描述为“类型”且设为必填项,保证传递图片数据时类型信息的完整性,便于后续根据类型进行相应的业务处理。 + */ + @Schema(description = "类型", required = true) + private int type; + + /** + * `relation`属性用于存放与首页图片相关联的其他数据的唯一标识符,比如可能关联到某个商品的 `id`、某个活动的 `id` 等, + * 其具体含义取决于业务场景中图片与其他实体的关联关系设定。通过这个属性可以建立起图片与其他业务对象之间的联系, + * 在进行相关业务操作时,例如根据图片查找关联商品、获取关联活动详情等,就可以依据这个 `relation` 值来获取对应的关联数据。 + * 通过 `@Schema` 注解将其描述为“关联id”并设为必填项,确保在传递图片信息时,关联ID信息准确无误,方便后续基于关联关系开展业务操作。 + */ + @Schema(description = "关联id", required = true) + private Long relation; +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/app/param/LoginParam.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/param/LoginParam.java new file mode 100644 index 0000000..14f2dda --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/param/LoginParam.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.app.param; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * `LoginParam`类是一个用于接收用户登录相关操作参数的数据对象,在电商系统用户登录的业务逻辑处理中起着关键作用。 + * 它主要用于在前端与后端进行交互时,承载用户发起登录操作时所需的各项必要数据信息,以便后端能够依据这些参数准确地执行相应的业务逻辑, + * 比如验证登录信息的合法性、根据不同的登录方式(小程序登录或账号密码登录)进行对应的身份验证等操作。 + + * @author lanhai + */ +@Schema(description = "登陆参数") +public class LoginParam { + + /** + * `code`属性用于存储小程序登录时返回的 `code` 值。在小程序登录的流程中,用户授权登录后,小程序会向微信等平台获取一个临时的 `code`, + * 这个 `code` 会被传递到后端服务器,后端再凭借该 `code` 向对应的平台(如微信开放平台)发起请求,换取用户的相关信息(如唯一标识等), + * 进而完成登录验证操作。通过 `@Schema` 注解明确其描述为“小程序登陆时返回的 `code`(使用 `code` 登陆必填)”, + * 并标记为必填项(`required = true`),以此确保在用户选择小程序登录方式时,前端必须传递这个关键的 `code` 参数给后端, + * 保证登录流程能够顺利进行,避免因缺少必要参数而导致登录失败。 + */ + @Schema(description = "小程序登陆时返回的code(使用code登陆必填)", required = true) + private String code; + + /** + * `mobile`属性用于存放用户登录时的用户名信息,在采用账号密码登录方式时,这里通常指的是用户的手机号码。 + * 手机号码作为常见的账号形式,方便用户记忆且具有唯一性,后端系统会依据这个手机号码去数据库等存储介质中查找对应的用户记录, + * 并与后续传入的密码进行匹配验证,以确认用户身份的合法性。通过 `@Schema` 注解将其描述为“登陆时的用户名(账号密码登陆必填)”, + * 同时设为必填项,告知接口调用者在使用账号密码登录时必须提供有效的手机号码作为用户名,保障登录验证过程有准确的账号依据。 + */ + @Schema(description = "登陆时的用户名(账号密码登陆必填)", required = true) + private String mobile; + + /** + * `password`属性用于存储用户登录时输入的密码信息,在账号密码登录模式下,它与前面的 `mobile` 属性一同作为验证用户身份的关键要素。 + * 用户输入的密码会与后端存储的已加密的密码(出于安全考虑,通常会对密码进行加密存储)进行比对,只有两者匹配时, + * 才会认定用户登录成功。借助 `@Schema` 注解明确其描述为“登陆时的密码(账号密码登陆必填)”,并标记为必填项, + * 强制要求接口调用者在使用账号密码登录方式时必须提供正确的密码,确保登录操作的安全性和准确性。 + */ + @Schema(description = "登陆时的密码(账号密码登陆必填)", required = true) + private String password; + + /** + * `getCode`方法是一个标准的JavaBean风格的访问器方法(Getter方法),用于获取 `code` 属性的值。 + * 在其他类需要访问本类中存储的小程序登录 `code` 参数信息时,可以通过调用这个方法来获取相应的值,遵循了面向对象编程中封装的原则, + * 对属性的访问进行了一定的控制,保证数据获取的规范性和安全性。 + */ + public String getCode() { + return code; + } + + /** + * `setCode`方法是与 `getCode` 对应的修改器方法(Setter方法),用于设置 `code` 属性的值。 + * 当外部需要更新本类中存储的小程序登录 `code` 信息时,可以通过调用这个方法并传入新的字符串值来实现属性值的修改, + * 同样也是遵循JavaBean规范,方便对属性进行赋值操作,同时在方法内部可以添加一些额外的逻辑(如数据验证等)来保障数据的合法性。 + */ + public void setCode(String code) { + this.code = code; + } + + /** + * `getMobile`方法是用于获取 `mobile` 属性值的Getter方法,其作用与 `getCode` 类似, + * 为外部类提供了获取用户名(手机号码)信息的途径,使得其他部分的代码可以按照规范的方式获取该属性存储的内容,便于后续基于用户名进行相关操作。 + */ + public String getMobile() { + return mobile; + } + + /** + * `setMobile`方法是对应 `getMobile` 的Setter方法,用于设置 `mobile` 属性的值, + * 当需要更新用户名(手机号码)信息时,外部代码可以调用这个方法传入新的字符串值来改变属性的存储内容, + * 并且可以在方法内部根据业务需求添加必要的数据校验等逻辑,确保设置的手机号码符合要求。 + */ + public void setMobile(String mobile) { + this.mobile = mobile; + } + + /** + * `getPassword`方法是获取 `password` 属性值的Getter方法,通过调用它, + * 其他类能够获取到本类中存储的密码信息,方便在后续的业务逻辑中,例如密码验证等操作中使用该密码作为关键的比对依据, + * 确保业务操作围绕准确的登录密码展开。 + */ + public String getPassword() { + return password; + } + + /** + * `setPassword`方法是用于设置 `password` 属性值的Setter方法,当需要更新密码信息时(例如用户修改密码等情况), + * 外部代码可以调用这个方法传入新的字符串值来改变其存储内容,同样也可以在方法内部添加适当的数据验证逻辑,保障密码的合法性和安全性。 + */ + public void setPassword(String password) { + this.password = password; + } +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/model/IndexImg.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/IndexImg.java new file mode 100644 index 0000000..151b71b --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/IndexImg.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.yami.shop.common.serializer.json.ImgJsonSerializer; +import lombok.Data; +import java.io.Serializable; +import java.util.Date; + +/** + * `IndexImg`类是一个实体类,用于表示电商系统首页图片相关的信息,在首页展示、图片管理等业务场景中有着重要作用, + * 它主要负责在数据库层面记录首页图片的各项关键数据,方便后续进行诸如图片展示顺序调整、图片关联商品设置、 + * 图片状态管理等操作。 + + * @author lanhai + */ +@Data +@TableName("tz_index_img") +public class IndexImg implements Serializable { + private static final long serialVersionUID = -3468251351681518798L; + + /** + * `imgId`属性作为该类的主键,是每条首页图片记录在数据库中的唯一标识符。 + * 它具有唯一性,通过这个 `imgId`,系统能够在数据库中精准地定位、操作某一条具体的首页图片相关记录, + * 比如在更新、删除特定首页图片信息或者根据该ID查询其详细属性时,都要依赖这个唯一标识来确保操作的准确性和针对性, + * 是整个首页图片数据管理中的关键标识字段。 + */ + @TableId + private Long imgId; + + /** + * `shopId`属性用于标识该图片所属的店铺ID,在多店铺的电商平台模式下,不同店铺可能有各自独立的首页图片展示需求, + * 通过这个属性可以明确图片是与哪个店铺相关联,进而在展示首页图片时能够按照店铺进行区分和筛选, + * 同时在涉及店铺相关的图片管理操作(如店铺自行上传、修改、删除自己的首页图片等)时,也依靠这个ID来确定操作对象。 + */ + private Long shopId; + + /** + * `imgUrl`属性存放的是图片的网络地址或者在系统中的存储路径等信息,具体取决于系统的图片存储和访问方式。 + * 例如在使用云存储服务时,可能是该图片在云存储中的完整URL地址;若是本地存储,则可能是相对系统根目录的文件路径, + * 这个属性用于在前端页面展示首页图片时,准确获取并显示对应的图片内容,是实现图片展示功能的关键数据之一。 + */ + private String imgUrl; + + /** + * `des`属性用于存储图片的说明文字或者描述信息,它可以对图片展示的内容、目的等进行简要介绍, + * 比如对于一张展示某款商品的图片,这里的描述可能是“新款夏季连衣裙,时尚优雅”之类的文字,帮助用户更好地理解图片所传达的信息, + * 同时在一些图片管理操作或者页面排版时,也可以参考这些描述来进行合理的布局和安排。 + */ + private String des; + + /** + * `title`属性代表图片的标题,通常是一个简洁且能概括图片核心内容的字符串, + * 例如“热门商品推荐”“限时优惠活动”等,在前端展示图片时,标题可能会以一定的样式显示在图片上方或附近位置, + * 起到突出主题、吸引用户关注的作用,也是图片相关信息展示的重要组成部分。 + */ + private String title; + + /** + * `link`属性存放的是图片所关联的链接地址,当用户点击首页图片时,会跳转到这个链接指向的页面, + * 链接的目标页面可以是商品详情页、活动专题页、店铺主页等不同类型的页面,具体根据业务需求和图片的用途来设定, + * 以此实现通过图片引导用户进行相应的浏览和操作,提升用户在平台内的交互体验。 + */ + private String link; + + /** + * `status`属性用于表示图片的状态,一般用整数类型来表示不同的状态值,例如可以设定 `0` 表示图片未启用(暂不展示), + * `1` 表示图片已启用(正常展示)等状态定义,通过这个属性可以方便地对首页图片进行展示与否的控制, + * 比如在后台管理系统中,管理员可以根据业务情况随时更改图片的状态,从而调整首页图片的展示效果。 + */ + private Integer status; + + /** + * `seq`属性代表图片的展示顺序,同样是整数类型,用于确定在首页展示图片时各个图片的先后排列次序, + * 可以按照业务需求设定较小的数值在前、较大的数值在后等排序规则,通过调整不同图片的 `seq` 值, + * 能够灵活地改变首页图片的展示顺序,实现对首页展示效果的个性化定制和优化。 + */ + private Integer seq; + + /** + * `uploadTime`属性记录的是图片的上传时间,以 `Date` 类型存储,格式一般遵循系统默认的时间格式(例如 `yyyy-MM-dd HH:mm:ss`), + * 它可以用于数据的溯源、统计分析等操作,比如查看不同时间段内店铺上传了哪些首页图片、了解图片的更新频率等业务场景中会用到该时间信息。 + */ + private Date uploadTime; + + /** + * `type`属性用于区分图片的类型,不同的整数值可以对应不同的图片分类,例如 `1` 表示商品推荐图片,`2` 表示活动宣传图片, + * `3` 表示店铺形象展示图片等,通过这种分类方式,可以在业务逻辑中针对不同类型的图片进行差异化的处理和展示, + * 也便于进行分类管理和统计分析等操作。 + */ + private int type; + + /** + * `relation`属性用于存放与图片相关联的其他数据的唯一标识符,比如可能关联到某个商品的 `id`、某个活动的 `id` 等, + * 其具体含义取决于业务场景中图片与其他实体的关联关系设定,通过这个属性可以建立起图片与其他业务对象之间的联系, + * 方便在进行相关业务操作时(如根据图片查找关联商品、获取关联活动详情等)获取对应的关联数据。 + */ + private Long relation; + + /** + * `pic`属性用于存放商品图片相关信息,它与数据库表字段的映射有所不同,通过 `@TableField(exist = false)` 注解表明该属性 + * 在数据库表中并不实际存在对应的字段,但在业务逻辑处理或者数据传输过程中可能会用到。同时使用 `@JsonSerialize(using = ImgJsonSerializer.class)` + * 注解意味着在将该类对象转换为JSON格式(例如在接口返回数据给前端时)会使用自定义的序列化器对这个属性进行特殊处理, + * 可能是对图片路径进行格式调整、添加域名等操作,使其符合前端展示或者与其他系统交互的要求,便于展示商品图片的相关信息。 + */ + @TableField(exist = false) + @JsonSerialize(using = ImgJsonSerializer.class) + private String pic; + + /** + * `prodName`属性用于存储商品名称,同样是通过 `@TableField(exist = false)` 注解表示其在数据库表中无对应字段, + * 但在涉及与商品相关的业务逻辑以及数据展示时会用到该属性,比如在首页图片关联商品展示时,除了显示图片本身, + * 还可以同时展示对应的商品名称,让用户更直观地了解图片所涉及的商品信息,提升用户体验。 + */ + @TableField(exist = false) + private String prodName; +} \ No newline at end of file -- 2.34.1 From 6099d4e75d9e52f7f0be2b854832f67454810170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 23:50:21 +0800 Subject: [PATCH 070/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/bean/enums/MessageStatus.java | 62 +++++++++++ .../com/yami/shop/bean/model/Message.java | 104 ++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/enums/MessageStatus.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/model/Message.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/enums/MessageStatus.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/enums/MessageStatus.java new file mode 100644 index 0000000..019daeb --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/enums/MessageStatus.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.enums; + +/** + * `MessageStatus`枚举类用于定义消息状态相关的常量值,在电商系统或者其他涉及消息处理的业务场景中, + * 可以通过这些明确的枚举值来清晰地表示消息所处的不同状态,方便在业务逻辑中进行判断、处理以及数据存储等操作, + * 使得代码对于消息状态的处理更加规范、易读且不容易出现歧义。 + + * @author lanhai + */ +public enum MessageStatus { + + /** + * `CANCEL`枚举值表示消息被取消的状态,对应的整数值为 `0`。 + * 在实际业务场景中,例如用户手动取消了某个消息提醒,或者系统根据一定规则判定某个消息不再需要展示、处理时, + * 可以将消息的状态设置为 `CANCEL`,通过这个枚举值来清晰地传达消息已经处于取消状态这一信息, + * 方便后续业务逻辑根据此状态进行相应的操作,比如不再推送该消息、在消息列表中隐藏该条消息等。 + */ + CANCEL(0), + + /** + * `RELEASE`枚举值代表消息已发布的状态,其对应整数值为 `1`。 + * 当一条消息经过相关流程创建完成并且可以对外展示、推送或者供用户查看时, + * 其状态就可以设置为 `RELEASE`,意味着该消息处于正常有效的发布状态,在业务逻辑里, + * 例如消息推送服务会根据这个状态来决定是否将消息发送给对应的用户,前端页面也会依据此状态来展示相应的消息内容等。 + */ + RELEASE(1); + + /** + * `num`属性用于存储每个枚举值对应的整数值,通过这种方式建立起枚举值与具体数值的映射关系, + * 在需要将枚举值转换为对应的数字进行存储(比如存储到数据库中)或者在不同模块间传递以数字形式表示的状态信息时会用到, + * 使得可以用统一的数字规则来表示不同的消息状态,便于系统进行数据处理和交互。 + */ + private Integer num; + + /** + * `value`方法用于获取当前枚举值对应的整数值,它提供了一种外部访问 `num` 属性的途径, + * 在其他类需要知晓某个 `MessageStatus` 枚举值具体对应的数字时,可以通过调用这个方法来获取相应的值, + * 遵循了面向对象编程中对数据访问进行封装控制的原则,保证了数据获取的规范性和安全性。 + */ + public Integer value() { + return num; + } + + /** + * 构造方法用于初始化每个枚举值对应的整数值,在定义枚举常量(如 `CANCEL(0)` 和 `RELEASE(1)`)时会调用此构造方法, + * 将传入的整数值赋给 `num` 属性,建立起枚举常量和对应数值之间的关联,确保每个枚举值都有正确的数值与之对应, + * 以便后续通过 `value` 方法等进行准确的数值获取操作。 + */ + MessageStatus(Integer num) { + this.num = num; + } +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Message.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Message.java new file mode 100644 index 0000000..c2f4328 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Message.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.app.param; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * `LoginParam`类是一个用于接收用户登录相关操作参数的数据对象,在电商系统用户登录的业务逻辑处理中起着关键作用。 + * 它主要用于在前端与后端进行交互时,承载用户发起登录操作时所需的各项必要数据信息,以便后端能够依据这些参数准确地执行相应的业务逻辑, + * 比如验证登录信息的合法性、根据不同的登录方式(小程序登录或账号密码登录)进行对应的身份验证等操作。 + + * @author lanhai + */ +@Schema(description = "登陆参数") +public class LoginParam { + + /** + * `code`属性用于存储小程序登录时返回的 `code` 值。在小程序登录的流程中,用户授权登录后,小程序会向微信等平台获取一个临时的 `code`, + * 这个 `code` 会被传递到后端服务器,后端再凭借该 `code` 向对应的平台(如微信开放平台)发起请求,换取用户的相关信息(如唯一标识等), + * 进而完成登录验证操作。通过 `@Schema` 注解明确其描述为“小程序登陆时返回的 `code`(使用 `code` 登陆必填)”, + * 并标记为必填项(`required = true`),以此确保在用户选择小程序登录方式时,前端必须传递这个关键的 `code` 参数给后端, + * 保证登录流程能够顺利进行,避免因缺少必要参数而导致登录失败。 + */ + @Schema(description = "小程序登陆时返回的code(使用code登陆必填)", required = true) + private String code; + + /** + * `mobile`属性用于存放用户登录时的用户名信息,在采用账号密码登录方式时,这里通常指的是用户的手机号码。 + * 手机号码作为常见的账号形式,方便用户记忆且具有唯一性,后端系统会依据这个手机号码去数据库等存储介质中查找对应的用户记录, + * 并与后续传入的密码进行匹配验证,以确认用户身份的合法性。通过 `@Schema` 注解将其描述为“登陆时的用户名(账号密码登陆必填)”, + * 同时设为必填项,告知接口调用者在使用账号密码登录时必须提供有效的手机号码作为用户名,保障登录验证过程有准确的账号依据。 + */ + @Schema(description = "登陆时的用户名(账号密码登陆必填)", required = true) + private String mobile; + + /** + * `password`属性用于存储用户登录时输入的密码信息,在账号密码登录模式下,它与前面的 `mobile` 属性一同作为验证用户身份的关键要素。 + * 用户输入的密码会与后端存储的已加密的密码(出于安全考虑,通常会对密码进行加密存储)进行比对,只有两者匹配时, + * 才会认定用户登录成功。借助 `@Schema` 注解明确其描述为“登陆时的密码(账号密码登陆必填)”,并标记为必填项, + * 强制要求接口调用者在使用账号密码登录方式时必须提供正确的密码,确保登录操作的安全性和准确性。 + */ + @Schema(description = "登陆时的密码(账号密码登陆必填)", required = true) + private String password; + + /** + * `getCode`方法是一个标准的JavaBean风格的访问器方法(Getter方法),用于获取 `code` 属性的值。 + * 在其他类需要访问本类中存储的小程序登录 `code` 参数信息时,可以通过调用这个方法来获取相应的值,遵循了面向对象编程中封装的原则, + * 对属性的访问进行了一定的控制,保证数据获取的规范性和安全性。 + */ + public String getCode() { + return code; + } + + /** + * `setCode`方法是与 `getCode` 对应的修改器方法(Setter方法),用于设置 `code` 属性的值。 + * 当外部需要更新本类中存储的小程序登录 `code` 信息时,可以通过调用这个方法并传入新的字符串值来实现属性值的修改, + * 同样也是遵循JavaBean规范,方便对属性进行赋值操作,同时在方法内部可以添加一些额外的逻辑(如数据验证等)来保障数据的合法性。 + */ + public void setCode(String code) { + this.code = code; + } + + /** + * `getMobile`方法是用于获取 `mobile` 属性值的Getter方法,其作用与 `getCode` 类似, + * 为外部类提供了获取用户名(手机号码)信息的途径,使得其他部分的代码可以按照规范的方式获取该属性存储的内容,便于后续基于用户名进行相关操作。 + */ + public String getMobile() { + return mobile; + } + + /** + * `setMobile`方法是对应 `getMobile` 的Setter方法,用于设置 `mobile` 属性的值, + * 当需要更新用户名(手机号码)信息时,外部代码可以调用这个方法传入新的字符串值来改变属性的存储内容, + * 并且可以在方法内部根据业务需求添加必要的数据校验等逻辑,确保设置的手机号码符合要求。 + */ + public void setMobile(String mobile) { + this.mobile = mobile; + } + + /** + * `getPassword`方法是获取 `password` 属性值的Getter方法,通过调用它, + * 其他类能够获取到本类中存储的密码信息,方便在后续的业务逻辑中,例如密码验证等操作中使用该密码作为关键的比对依据, + * 确保业务操作围绕准确的登录密码展开。 + */ + public String getPassword() { + return password; + } + + /** + * `setPassword`方法是用于设置 `password` 属性值的Setter方法,当需要更新密码信息时(例如用户修改密码等情况), + * 外部代码可以调用这个方法传入新的字符串值来改变其存储内容,同样也可以在方法内部添加适当的数据验证逻辑,保障密码的合法性和安全性。 + */ + public void setPassword(String password) { + this.password = password; + } +} \ No newline at end of file -- 2.34.1 From cc0e4b6b637a4588c3a1edce87c0b301f8ce9d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 23:52:52 +0800 Subject: [PATCH 071/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/bean/app/dto/MyOrderDto.java | 67 ++++++++++++++++ .../shop/bean/app/dto/MyOrderItemDto.java | 76 +++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/MyOrderDto.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/MyOrderItemDto.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/MyOrderDto.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/MyOrderDto.java new file mode 100644 index 0000000..edb5864 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/MyOrderDto.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.app.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import java.util.List; + +/** + * `MyOrderDto`类是一个数据传输对象(DTO),主要用于在电商系统应用层不同组件或模块之间传递与用户“我的订单”相关的综合信息。 + * 其作用是将订单中关键的数据进行整合封装,方便在诸如订单查询、订单列表展示、订单详情查看等业务场景下进行数据传递, + * 使得数据交互更加清晰、规范,便于不同业务部分基于这些信息进行相应操作,例如向用户展示订单概览、统计订单相关数据等。 + + * @author lanhai + */ +@Data +@Schema(description = "我的订单") +public class MyOrderDto { + + /** + * `orderItemDtos`属性是一个列表类型,其中元素为 `MyOrderItemDto` 类型,用于存放该订单下的各个订单项信息。 + * 在一个订单中,通常会包含多个购买的商品项,每个商品项都有其独立的详细信息,比如商品的具体规格(通过对应的SKU信息体现)、 + * 商品单价、购买数量等,这个列表就完整地记录了所有订单项的这些详细数据,方便在后续业务逻辑中, + * 例如查看订单商品明细、计算每个商品的实际金额、进行商品退换货操作等时使用,确保订单中商品相关信息的完整性和准确性。 + * 通过 `@Schema` 注解明确其描述为“订单项”且标记为必填项(`required = true`),保证在传递“我的订单”相关数据时, + * 订单项信息不会缺失,便于准确展示订单的具体商品构成情况。 + */ + @Schema(description = "订单项", required = true) + private List orderItemDtos; + + /** + * `orderNumber`属性用于存储订单的唯一编号,也就是订单号,它是在整个电商系统中用于精确标识每个订单的字符串。 + * 通过这个订单号,后端系统可以精准地关联到该订单对应的所有详细信息,包括用户信息、下单时间、支付信息等诸多方面, + * 在业务操作中,用户查询特定订单、进行订单相关的各种处理(如确认收货、申请退款等)都是依靠这个订单号来定位目标订单的。 + * 通过 `@Schema` 注解将其描述为“订单号”并设定为必填项,确保在数据传递过程中订单号信息的完整性,便于后续围绕准确的订单开展业务。 + */ + @Schema(description = "订单号", required = true) + private String orderNumber; + + /** + * `actualTotal`属性代表订单的总价,即用户实际需要支付的金额总数,其数据类型为 `Double`,通常以货币形式体现。 + * 这个总价是经过一系列计算得出的,比如考虑了商品原价、优惠活动减免金额(如满减、优惠券使用等)、运费(如果有)等因素后的最终金额, + * 在向用户展示订单信息时,总价是一个非常关键的数据,用户可以直观地了解到此次订单需要花费多少钱, + * 同时在订单统计、财务相关业务操作中也起着重要作用。通过 `@Schema` 注解标记其描述为“总价”且设为必填项, + * 保证在传递订单数据时总价信息准确无误,方便各方基于该金额进行相应的处理和判断。 + */ + @Schema(description = "总价", required = true) + private Double actualTotal; + + /** + * `status`属性用于表示订单当前所处的状态,一般为整数类型,不同的整数值对应不同的订单状态,例如: + * `0` 可以表示订单已提交但未支付,`1` 表示订单已支付等待发货,`2` 表示订单已发货在运输中,`3` 表示订单已签收等, + * 具体的状态值和含义通常会根据电商系统的业务规则来设定。通过这个属性,系统可以清晰地知晓订单目前处于哪个环节, + * 进而根据不同状态执行相应的业务逻辑,比如对于已支付的订单安排发货,对于已签收的订单进行后续评价等操作。 + * 通过 `@Schema` 注解明确其描述为“订单状态”并设为必填项,确保在传递订单数据时状态信息完整,便于准确把握订单的进展情况。 + */ + @Schema(description = "订单状态", required = true) + private Integer status; +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/MyOrderItemDto.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/MyOrderItemDto.java new file mode 100644 index 0000000..6cd1575 --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/MyOrderItemDto.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.app.dto; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.yami.shop.common.serializer.json.ImgJsonSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * `MyOrderItemDto`类是一个数据传输对象(DTO),在电商系统应用层用于传递“我的订单”中具体订单项的相关信息, + * 它将单个商品在订单中的关键数据进行整合封装,方便在诸如订单详情展示、订单商品明细查询等业务场景下进行数据传递, + * 使得各业务环节能基于这些详细且规范的数据来进行相应操作,例如向用户清晰呈现所购买商品的各项属性、进行商品金额计算等。 + + * @author lanhai + */ +@Schema(description = "我的订单-订单项") +@Data +public class MyOrderItemDto { + + /** + * `pic`属性用于存放商品图片相关信息,它可以是图片的网络地址或者在系统中的存储路径等,具体取决于系统的图片存储和访问方式。 + * 例如在使用云存储服务时,可能是该商品图片在云存储中的完整URL地址;若是本地存储,则可能是相对系统根目录的文件路径。 + * 这个属性是展示商品外观等信息的关键数据,前端页面通过获取该 `pic` 来准确显示对应的商品图片内容,让用户直观地知晓所购买商品的样子。 + * 同时,借助 `@JsonSerialize(using = ImgJsonSerializer.class)` 注解,在将该类对象转换为JSON格式数据(如接口返回数据给前端时), + * 会使用自定义的序列化器对这个属性进行特殊处理,可能是对图片路径进行格式调整、添加域名等操作,使其符合前端展示或者与其他系统交互的要求。 + * 通过 `@Schema` 注解明确其描述为“商品图片”并标记为必填项(`required = true`),确保在传递订单项数据时该商品图片信息的完整性, + * 以便顺利实现商品图片展示功能,提升用户体验。 + */ + @Schema(description = "商品图片", required = true) + @JsonSerialize(using = ImgJsonSerializer.class) + private String pic; + + /** + * `prodName`属性用于存储商品的名称,它是一个能够清晰标识商品具体是什么的字符串,例如“华为P50手机”“耐克运动鞋”等。 + * 在订单相关业务中,商品名称是用户快速识别所购买商品的重要依据,无论是在订单详情页面展示给用户查看, + * 还是在进行商品数据统计、查询等操作时,都需要准确的商品名称信息,通过 `@Schema` 注解将其描述为“商品名称”并设为必填项, + * 保证在数据传递过程中商品名称不会缺失,便于各方基于该名称进行相应的业务处理。 + */ + @Schema(description = "商品名称", required = true) + private String prodName; + + /** + * `prodCount`属性代表商品的数量,即用户在该订单中购买此商品的具体个数,其数据类型为整数类型。 + * 这个数量信息对于计算商品的总价(通过与商品价格相乘)、库存管理(确认销售后相应扣减库存)以及后续可能的退换货等业务操作都至关重要, + * 通过 `@Schema` 注解明确其描述为“商品数量”并标记为必填项,确保在传递订单项数据时商品数量信息准确无误,便于准确开展相关业务逻辑。 + */ + @Schema(description = "商品数量", required = true) + private Integer prodCount; + + /** + * `price`属性存放的是商品的价格信息,通常以货币形式体现,数据类型为 `Double`,它代表了该商品在本次订单中的单价。 + * 在订单金额计算(单个商品金额为价格乘以数量,进而汇总得到订单总价)、优惠活动应用(基于商品单价来计算优惠后的价格)等业务场景中, + * 商品价格是关键的基础数据,通过 `@Schema` 注解将其描述为“商品价格”并设为必填项,保证在数据传递过程中价格信息的完整性, + * 便于准确进行与商品价格相关的各种业务操作。 + */ + @Schema(description = "商品价格", required = true) + private Double price; + + /** + * `skuName`属性用于表示商品的规格名称,也就是常说的SKU(Stock Keeping Unit)名称,它具体描述了商品的特定规格组合情况, + * 例如对于一款手机,SKU名称可能是“8GB内存 + 256GB存储 + 黑色”,清晰地体现了该商品在颜色、内存容量、存储容量等方面的具体规格配置。 + * 在订单中,SKU名称有助于更精准地确定用户购买的是该商品的哪一种具体规格,方便进行商品管理、库存管理以及向用户展示详细商品规格信息等操作, + * 通过 `@Schema` 注解明确其描述为“skuName”并设为必填项,确保在传递订单项数据时规格名称信息完整,便于准确把握商品的具体规格情况。 + */ + @Schema(description = "skuName", required = true) + private String skuName; +} \ No newline at end of file -- 2.34.1 From 51536b43015b268c0e6e9d9bcee132e2fc0ea016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Mon, 16 Dec 2024 23:54:34 +0800 Subject: [PATCH 072/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/bean/app/dto/NoticeDto.java | 71 +++++++++++++++++ .../java/com/yami/shop/bean/model/Notice.java | 76 +++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/NoticeDto.java create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/model/Notice.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/NoticeDto.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/NoticeDto.java new file mode 100644 index 0000000..8f6c00e --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/app/dto/NoticeDto.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.app.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import java.util.Date; + +/** + * `NoticeDto`类是一个数据传输对象(DTO),主要用于在电商系统应用层不同组件或模块之间传递公告相关的信息。 + * 其作用是将公告的关键数据进行整合封装,方便在诸如公告发布、公告查询、公告展示等业务场景下进行数据传递, + * 使得数据交互更加清晰、规范且符合业务需求,便于不同业务部分基于这些信息进行相应的操作,例如向用户展示具体公告内容、 + * 后台管理系统对公告进行分类管理等。 + + * @author lanhai + */ +@Schema(description = "公告对象") +@Data +public class NoticeDto { + + /** + * `id`属性用于存放公告的唯一标识符,也就是公告在系统中的ID值,通常为一个Long类型的数字。 + * 这个ID能够在整个系统中精准地定位到某一条具体的公告记录,方便进行数据的管理操作,比如对公告进行更新、删除、 + * 查询其详细属性等操作时,都依赖这个唯一标识来确保操作的准确性和针对性。通过 `@Schema` 注解明确其描述为“公告id”, + * 虽然它在当前设定下并非必填项,但在系统数据管理以及后续可能的功能扩展中,这个ID有着重要的标识作用, + * 有助于区分不同的公告内容,保证数据的唯一性和可追溯性。 + */ + @Schema(description = "公告id") + private Long id; + + /** + * `shopId`属性用于标识该公告所属的店铺ID,在多店铺的电商平台模式下,不同店铺可能会发布各自的公告信息, + * 通过这个属性可以明确公告是与哪个店铺相关联,进而在展示公告时能够按照店铺进行区分和筛选, + * 同时在涉及店铺相关的公告管理操作(如店铺自行发布、修改、删除自己的公告等)时,也依靠这个ID来确定操作对象。 + */ + @Schema(description = "店铺id") + private Long shopId; + + /** + * `title`属性代表公告的标题,通常是一个简短且能够直观体现公告主题的字符串。 + * 例如在电商系统中,可能是“店铺周年庆活动公告”“商品上新通知”等类似的文字表述,它是用户在查看公告列表或者页面展示公告时, + * 首先看到的关键内容,用于吸引用户的注意力,让用户快速了解公告大致的主题,进而决定是否进一步查看公告的详细内容。 + */ + @Schema(description = "标题") + private String title; + + /** + * `content`属性用于存储公告的具体内容信息,它包含了要传达给用户或者相关人员的详细文字描述, + * 比如对于“店铺周年庆活动公告”这个标题,内容里可能会详细介绍活动的时间、参与方式、优惠内容等具体情况; + * 对于“商品上新通知”的公告,内容中则可能会列出上新的商品名称、特点、价格等信息,是公告的核心组成部分, + * 承载着实际要传递的消息,方便用户全面了解公告所涉及的相关事宜。 + */ + @Schema(description = "公告内容") + private String content; + + /** + * `publishTime`属性记录的是公告的发布时间,以 `Date` 类型存储,格式一般遵循系统默认的时间格式(例如 `yyyy-MM-dd HH:mm:ss`)。 + * 它可以用于数据的溯源、统计分析等操作,比如查看不同时间段内店铺发布了哪些公告、了解公告的发布频率等业务场景中会用到该时间信息, + * 同时也能让用户知晓公告的时效性,判断公告内容是否还在有效期内或者是否是最新发布的内容等。 + */ + @Schema(description = "公告发布时间") + private Date publishTime; +} \ No newline at end of file diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Notice.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Notice.java new file mode 100644 index 0000000..dbb4a1a --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Notice.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.bean.model; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.util.Date; + +/** + * 公告管理 + * + * @author hzm + * @date 2019-04-18 21:21:40 + */ +@Data +@TableName("tz_notice") +public class Notice implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 公告id + */ + @TableId + private Long id; + + /** + * 店铺id + */ + private Long shopId; + + /** + * 公告标题 + */ + private String title; + + /** + * 公告内容 + */ + private String content; + + /** + * 状态(1:公布 0:撤回) + */ + private Integer status; + + /** + * 是否置顶(1:是 0:否) + */ + private Integer isTop; + + /** + * 发布时间 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date publishTime; + + /** + * 更新时间 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + +} -- 2.34.1 From 6dd4f54ffdb8391ea37711ef383848dc734104e4 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 00:10:06 +0800 Subject: [PATCH 073/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/common/util/CacheManagerUtil.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/util/CacheManagerUtil.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/util/CacheManagerUtil.java b/yami-shop-common/src/main/java/com/yami/shop/common/util/CacheManagerUtil.java new file mode 100644 index 0000000..ee8e81d --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/util/CacheManagerUtil.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.common.util; + +import lombok.AllArgsConstructor; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Component; + +/** + * CacheManagerUtil类用于方便地操作Spring的缓存机制。 + * 它依赖于Spring的CacheManager来获取、设置和清除缓存中的数据, + * 提供了对缓存数据进行增删查操作的便捷方法。 + * 该类被声明为Spring的一个组件(@Component注解),可以被自动注入到其他需要使用缓存操作功能的类中。 + * + * @author lanhai + */ +@Component +@AllArgsConstructor +public class CacheManagerUtil { + + // Spring的缓存管理器,用于获取具体的缓存对象等操作,通过构造注入的方式获取 + private CacheManager cacheManager; + + /** + * 从指定名称的缓存中根据给定的键获取缓存值。 + * + * @param 泛型参数,表示期望获取的缓存值的类型。 + * @param cacheName 缓存的名称,用于通过CacheManager定位到具体的缓存对象。 + * @param key 缓存中数据对应的键,用于在具体缓存对象里查找对应的值。 + * @return 如果缓存中存在对应键的值,则返回该值(转换为指定的泛型类型T),若缓存不存在或者对应键的值不存在,则返回null。 + */ + @SuppressWarnings({"unchecked"}) + public T getCache(String cacheName, String key) { + // 通过缓存管理器获取指定名称的缓存对象 + Cache cache = cacheManager.getCache(cacheName); + if (cache == null) { + return null; + } + // 从缓存对象中尝试获取对应键的值的包装对象 + Cache.ValueWrapper valueWrapper = cache.get(key); + if (valueWrapper == null) { + return null; + } + // 从包装对象中获取实际的值,并转换为指定的泛型类型T返回 + return (T) valueWrapper.get(); + } + + /** + * 将给定的值存入指定名称的缓存中,对应给定的键。 + * + * @param cacheName 缓存的名称,用于通过CacheManager定位到要操作的具体缓存对象。 + * @param key 缓存中数据对应的键,用于标识存入的值在缓存中的位置。 + * @param value 要存入缓存的值,其类型可以是任意符合缓存要求的Java对象。 + */ + public void putCache(String cacheName, String key, Object value) { + // 通过缓存管理器获取指定名称的缓存对象 + Cache cache = cacheManager.getCache(cacheName); + if (cache!= null) { + // 如果缓存对象存在,则将键值对存入该缓存对象中 + cache.put(key, value); + } + } + + /** + * 从指定名称的缓存中清除对应给定键的缓存数据。 + * + * @param cacheName 缓存的名称,用于通过CacheManager定位到要操作的具体缓存对象。 + * @param key 缓存中数据对应的键,用于确定要清除的缓存数据。 + */ + public void evictCache(String cacheName, String key) { + // 通过缓存管理器获取指定名称的缓存对象 + Cache cache = cacheManager.getCache(cacheName); + if (cache!= null) { + // 如果缓存对象存在,则清除对应键的缓存数据 + cache.evict(key); + } + } +} \ No newline at end of file -- 2.34.1 From 1d5a6ad56e6e5a5a25589619bfea47239121421a Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 00:11:15 +0800 Subject: [PATCH 074/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/dto/CaptchaAuthenticationDTO.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/dto/CaptchaAuthenticationDTO.java diff --git a/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/dto/CaptchaAuthenticationDTO.java b/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/dto/CaptchaAuthenticationDTO.java new file mode 100644 index 0000000..942ce28 --- /dev/null +++ b/yami-shop-security/yami-shop-security-admin/src/main/java/com/yami/shop/security/admin/dto/CaptchaAuthenticationDTO.java @@ -0,0 +1,27 @@ +package com.yami.shop.security.admin.dto; + +import com.yami.shop.security.common.dto.AuthenticationDTO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * CaptchaAuthenticationDTO类用于表示验证码登录相关的数据传输对象(DTO)。 + * 它继承自AuthenticationDTO类,意味着会继承其原有的一些与认证相关的通用属性和方法等。 + * 此类专门用于在涉及验证码登录的场景中,传递包含验证码信息在内的登录相关数据, + * 比如从前端接收用户输入的验证码以及其他必要的认证信息,再传递到后端进行相应的登录验证处理等操作。 + * + * @author 菠萝凤梨 + * @date 2022/3/28 14:57 + */ +@Data +public class CaptchaAuthenticationDTO extends AuthenticationDTO { + + /** + * captchaVerification属性用于存储用户输入的验证码信息。 + * 通过@Schema注解对该属性在Swagger文档中的展示进行了描述说明, + * 指明此属性的描述为“验证码”,并且在相关的请求等场景中是必须提供的(required = true), + * 以便后端服务可以获取该验证码并与系统生成的验证码进行比对等验证操作。 + */ + @Schema(description = "验证码", required = true) + private String captchaVerification; +} \ No newline at end of file -- 2.34.1 From 924d6e9b8ce277fffa6374afe3928e7cd45cedcd Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 00:12:46 +0800 Subject: [PATCH 075/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/CaptchaCacheServiceRedisImpl.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/CaptchaCacheServiceRedisImpl.java diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/CaptchaCacheServiceRedisImpl.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/CaptchaCacheServiceRedisImpl.java new file mode 100644 index 0000000..f85015c --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/CaptchaCacheServiceRedisImpl.java @@ -0,0 +1,79 @@ +package com.yami.shop.security.common.adapter; + +import com.anji.captcha.service.CaptchaCacheService; +import com.yami.shop.common.util.RedisUtil; + +/** + * CaptchaCacheServiceRedisImpl类的主要作用是将验证码相关的数据存储适配到Redis中。 + * 它实现了CaptchaCacheService接口,意味着需要按照该接口定义的方法规范来实现具体的功能, + * 从而为验证码服务提供基于Redis的缓存操作功能,例如存储验证码、判断验证码是否存在、删除验证码以及获取验证码等操作, + * 使得验证码相关的数据可以在Redis中进行有效的管理和操作。 + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +public class CaptchaCacheServiceRedisImpl implements CaptchaCacheService { + + /** + * 此方法用于将指定的键值对存储到Redis中,并设置过期时间(以秒为单位)。 + * 它调用了RedisUtil工具类中的set方法来实际执行向Redis存储数据的操作, + * 将传入的键(key)、值(value)以及过期时间(expiresInSeconds)传递给RedisUtil的对应方法进行处理。 + * + * @param key 要存储到Redis中的键,用于唯一标识验证码相关的数据。 + * @param value 要存储到Redis中的值,通常为验证码的具体内容等相关信息。 + * @param expiresInSeconds 数据在Redis中的过期时间,单位为秒,超过这个时间后数据将自动从Redis中删除。 + */ + @Override + public void set(String key, String value, long expiresInSeconds) { + RedisUtil.set(key, value, expiresInSeconds); + } + + /** + * 此方法用于判断在Redis中是否存在指定的键对应的验证码数据。 + * 通过调用RedisUtil工具类中的hasKey方法来实现判断逻辑, + * 如果Redis中存在该键,则表示对应的验证码数据存在,返回true;否则返回false。 + * + * @param key 要检查其是否存在的键,对应着可能存储在Redis中的验证码相关数据的标识。 + * @return 如果Redis中存在该键对应的验证码数据,则返回true;否则返回false。 + */ + @Override + public boolean exists(String key) { + return RedisUtil.hasKey(key); + } + + /** + * 此方法用于从Redis中删除指定键对应的验证码数据。 + * 调用RedisUtil工具类中的del方法来执行从Redis中删除数据的实际操作, + * 使得对应的验证码相关信息在Redis中被移除,不再占用存储空间。 + * + * @param key 要从Redis中删除其对应数据的键,即要删除的验证码相关数据在Redis中的标识。 + */ + @Override + public void delete(String key) { + RedisUtil.del(key); + } + + /** + * 此方法用于从Redis中获取指定键对应的验证码数据的值(通常就是验证码的具体内容)。 + * 借助RedisUtil工具类中的get方法来实现从Redis中查找并返回对应值的功能, + * 如果键存在,则返回对应的验证码数据的值;如果不存在,则返回相应的默认值(根据RedisUtil的实现来确定)。 + * + * @param key 要从Redis中获取其对应值的键,即要获取的验证码相关数据在Redis中的标识。 + * @return 从Redis中获取到的指定键对应的验证码数据的值,如果键不存在则返回相应结果(由RedisUtil决定)。 + */ + @Override + public String get(String key) { + return RedisUtil.get(key); + } + + /** + * 此方法用于返回当前缓存服务所使用的存储类型,在这里明确返回“redis”, + * 表明本实现类是将验证码相关数据存储在Redis中的,方便外部代码知晓和区分不同的缓存存储实现方式。 + * + * @return 始终返回“redis”,表示该缓存服务基于Redis进行数据存储。 + */ + @Override + public String type() { + return "redis"; + } +} \ No newline at end of file -- 2.34.1 From dbd0ce95f8f82b9053d3bb74d44770c1297cb518 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 00:14:11 +0800 Subject: [PATCH 076/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/common/config/CaptchaConfig.java | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CaptchaConfig.java diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CaptchaConfig.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CaptchaConfig.java new file mode 100644 index 0000000..c3791f8 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CaptchaConfig.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.security.common.config; + +import com.anji.captcha.model.common.CaptchaTypeEnum; +import com.anji.captcha.model.common.Const; +import com.anji.captcha.service.CaptchaService; +import com.anji.captcha.service.impl.CaptchaServiceFactory; +import com.anji.captcha.util.ImageUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.util.Base64Utils; +import org.springframework.util.FileCopyUtils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * CaptchaConfig类是一个Spring的配置类(通过@Configuration注解标识), + * 主要用于配置验证码相关的服务以及处理验证码底图在Redis中的存储等相关操作。 + * 如果出现获取验证码失败的情况,并且提示找管理员查看时,可重点检查Redis中验证码相关数据的存储情况, + * 因为此配置类涉及将验证码底图存入Redis的相关逻辑。 + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@Configuration +public class CaptchaConfig { + + /** + * captchaService方法是一个Spring的Bean定义方法(通过@Bean注解标识), + * 用于创建并配置一个CaptchaService实例,该实例在验证码的生成、验证等流程中起着核心作用。 + * 在这里,通过创建一个Properties对象来配置CaptchaService所需的各项参数, + * 然后使用这些配置参数通过CaptchaServiceFactory获取具体的CaptchaService实例。 + * + * @return 返回配置好的CaptchaService实例,供Spring容器管理并在需要验证码服务的地方注入使用。 + */ + @Bean + public CaptchaService captchaService() { + Properties config = new Properties(); + // 设置验证码的缓存类型为Redis,意味着验证码相关的数据(如底图等)将存储在Redis中 + config.put(Const.CAPTCHA_CACHETYPE, "redis"); + // 设置验证码的水印为空字符串,即不添加水印(具体根据验证码实现逻辑来定是否生效) + config.put(Const.CAPTCHA_WATER_MARK, ""); + // 设置验证码的类型为滑动验证,这里使用了CaptchaTypeEnum枚举中BLOCKPUZZLE对应的代码值来指定 + config.put(Const.CAPTCHA_TYPE, CaptchaTypeEnum.BLOCKPUZZLE.getCodeValue()); + // 设置初始化原始图片相关的标志为true,具体含义取决于验证码服务内部对该参数的使用逻辑 + config.put(Const.CAPTCHA_INIT_ORIGINAL, "true"); + // 调用initializeBaseMap方法进行一些初始化操作,可能与验证码底图相关(比如预加载等) + initializeBaseMap(); + return CaptchaServiceFactory.getInstance(config); + } + + /** + * initializeBaseMap方法用于执行与验证码底图初始化相关的操作。 + * 它调用了ImageUtils类的cacheBootImage方法,传递从指定路径获取的原始图片资源和滑动块图片资源, + * 以及一个空的Map(可能后续会根据业务逻辑在方法内部填充相关数据), + * 目的是将这些图片资源进行缓存处理(比如转换格式、存入特定缓存等,具体取决于ImageUtils的实现), + * 以便后续验证码生成等操作可以方便地使用这些底图资源。 + */ + private static void initializeBaseMap() { + ImageUtils.cacheBootImage(getResourcesImagesFile("classpath:captcha" + "/original/*.png"), + getResourcesImagesFile("classpath:captcha" + "/slidingBlock/*.png"), + Collections.emptyMap()); + } + + /** + * getResourcesImagesFile方法用于从指定的类路径下获取图片资源,并将其转换为Base64编码的字符串格式存储在一个Map中。 + * 这个Map的键是图片的文件名,值是对应的Base64编码后的字符串,方便后续在内存中对图片资源进行管理和使用, + * 例如传递给其他方法用于验证码底图相关的处理等操作。 + * + * @param path 表示要获取图片资源的类路径表达式,例如"classpath:captcha" + "/original/*.png"用于获取指定目录下的所有png格式的原始图片资源。 + * @return 返回一个Map,其中包含了从指定路径获取到的图片文件名及其对应的Base64编码后的字符串表示形式。 + */ + public static Map getResourcesImagesFile(String path) { + Map imgMap = new HashMap<>(16); + // 创建一个PathMatchingResourcePatternResolver实例,用于解析类路径下的资源模式,能够匹配符合指定模式的多个资源文件 + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + try { + // 根据传入的路径表达式获取对应的资源数组,即匹配该模式的所有图片资源文件 + Resource[] resources = resolver.getResources(path); + // 遍历获取到的所有资源文件 + Resource[] var4 = resources; + int var5 = resources.length; + + for (int var6 = 0; var6 < var5; ++var6) { + Resource resource = var4[var6]; + // 将资源文件的内容读取为字节数组,以便后续进行Base64编码操作 + byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream()); + // 将字节数组转换为Base64编码的字符串,使得图片数据可以以文本形式方便地在内存中存储和传输等 + String string = Base64Utils.encodeToString(bytes); + // 获取资源文件的文件名,用于作为Map中的键来标识对应的Base64编码后的图片数据 + String filename = resource.getFilename(); + imgMap.put(filename, string); + } + } catch (Exception var11) { + // 如果在获取资源文件或处理过程中出现异常,打印异常堆栈信息,方便排查问题 + var11.printStackTrace(); + } + + return imgMap; + } +} \ No newline at end of file -- 2.34.1 From cab494a012fbbde2552be8f324d351ae1831a478 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 00:16:25 +0800 Subject: [PATCH 077/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/controller/CaptchaController.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/CaptchaController.java diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/CaptchaController.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/CaptchaController.java new file mode 100644 index 0000000..57f457d --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/CaptchaController.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.security.common.controller; + +import com.anji.captcha.model.common.RepCodeEnum; +import com.anji.captcha.model.common.ResponseModel; +import com.anji.captcha.model.vo.CaptchaVO; +import com.anji.captcha.service.CaptchaService; +import io.swagger.v3.oas.annotations.tags.Tag; +import com.yami.shop.common.response.ServerResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * CaptchaController类是一个Spring RESTful风格的控制器类(通过@RestController注解标识), + * 主要负责处理与验证码相关的HTTP请求,如获取验证码、验证验证码等操作。 + * 它被映射到"/captcha"路径下(通过@RequestMapping注解配置),并且在Swagger文档中被标记为"验证码"相关的接口(通过@Tag注解定义),方便接口文档的展示和查看。 + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@RestController +@RequestMapping("/captcha") +@Tag(name = "验证码") +public class CaptchaController { + + // 通过构造函数注入的方式引入CaptchaService,用于后续调用验证码相关的业务逻辑方法,比如生成验证码、验证验证码有效性等操作 + private final CaptchaService captchaService; + + public CaptchaController(CaptchaService captchaService) { + this.captchaService = captchaService; + } + + /** + * get方法是一个处理HTTP POST请求的方法,用于获取验证码信息。 + * 它接收一个CaptchaVO类型的请求体参数(通过@RequestBody注解标识),该参数包含了获取验证码时可能需要的一些配置信息或参数(具体取决于CaptchaVO的定义)。 + * 方法内部调用captchaService的get方法来实际获取验证码相关的数据,并将获取到的结果包装在ServerResponseEntity中返回给客户端, + * 表示操作成功以及返回对应的验证码相关信息(如果成功获取的话)。 + * + * @param captchaVO 包含获取验证码相关配置或参数的请求体对象。 + * @return 返回一个ServerResponseEntity,其中包装了验证码服务获取到的ResponseModel类型的结果,向客户端表示操作成功并传递验证码相关数据。 + */ + @PostMapping({ "/get" }) + public ServerResponseEntity get(@RequestBody CaptchaVO captchaVO) { + return ServerResponseEntity.success(captchaService.get(captchaVO)); + } + + /** + * check方法同样是一个处理HTTP POST请求的方法,用于验证客户端提交的验证码是否正确。 + * 它也接收一个CaptchaVO类型的请求体参数,该参数包含了待验证的验证码以及可能相关的验证辅助信息(同样取决于CaptchaVO的定义)。 + * 方法内部首先尝试调用captchaService的check方法来验证验证码,如果验证过程中没有出现异常,则将验证结果包装在ServerResponseEntity中返回给客户端, + * 表示验证操作成功以及返回对应的验证结果(如验证码是否正确等信息)。 + * 如果在验证过程中出现异常,会捕获该异常,并返回一个表示验证码坐标错误的默认错误响应(通过ResponseModel.errorMsg方法构建对应错误消息的ResponseModel对象), + * 同样包装在ServerResponseEntity中返回给客户端,提示客户端验证码验证出现问题。 + * + * @param captchaVO 包含待验证验证码及相关信息的请求体对象。 + * @return 返回一个ServerResponseEntity,其中包装了验证码服务验证后的ResponseModel类型的结果,向客户端反馈验证操作的成功与否及相应信息。 + */ + @PostMapping({ "/check" }) + public ServerResponseEntity check(@RequestBody CaptchaVO captchaVO) { + ResponseModel responseModel; + try { + responseModel = captchaService.check(captchaVO); + } catch (Exception e) { + return ServerResponseEntity.success(ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR)); + } + return ServerResponseEntity.success(responseModel); + } +} \ No newline at end of file -- 2.34.1 From 3e1c52b36b3bdbed5993d9cef6ad172bd463b2b6 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 00:18:00 +0800 Subject: [PATCH 078/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/common/constants/Constant.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/constants/Constant.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/constants/Constant.java b/yami-shop-common/src/main/java/com/yami/shop/common/constants/Constant.java new file mode 100644 index 0000000..8b7eba0 --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/constants/Constant.java @@ -0,0 +1,26 @@ +package com.yami.shop.common.constants; + +/** + * Constant类用于集中定义项目中常用的一些常量。 + * 将常量统一放在此类中进行管理,有助于提高代码的可维护性和可读性, + * 方便在项目的不同地方引用这些常量,避免了硬编码具体的值, + * 当需要修改常量的值时,只需在此类中进行更改即可,减少了代码改动的范围。 + * + * @author TRACK + */ +public class Constant { + + /** + * PERIOD常量用于表示句号(英文符号)。 + * 在需要使用英文句号的场景中(例如文件路径的拼接、字符串格式的规范等情况), + * 可以直接引用此常量,确保整个项目中对于英文句号的使用保持一致性。 + */ + public static final String PERIOD = "."; + + /** + * COMMA常量用于表示逗号。 + * 常用于在构造需要以逗号分隔的字符串(比如CSV格式的数据组装、多个元素的列举表示等场景), + * 或者在解析以逗号分隔的字符串时作为分隔标识等操作中,方便统一使用逗号的表示形式。 + */ + public static final String COMMA = ","; +} \ No newline at end of file -- 2.34.1 From 14cecf1f57f3177a3ffe67bde07269156f63c16d Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 00:20:06 +0800 Subject: [PATCH 079/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/common/config/CorsConfig.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CorsConfig.java diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CorsConfig.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CorsConfig.java new file mode 100644 index 0000000..317b7fd --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/config/CorsConfig.java @@ -0,0 +1,61 @@ +package com.yami.shop.security.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +/** + * CorsConfig类是一个Spring的配置类(通过@Configuration注解标识), + * 其主要作用是配置跨域资源共享(CORS)相关的设置,以便在前后端分离的项目中, + * 解决浏览器的同源策略限制,使得不同域名(或端口等不同源情况)下的前端应用能够安全地访问后端接口。 + * + * @author yami + */ +@Configuration +public class CorsConfig { + + /** + * corsConfigurationSource方法是一个Spring的Bean定义方法(通过@Bean注解标识), + * 用于创建并配置一个CorsConfigurationSource对象,该对象作为Spring Web中CORS配置的数据源, + * 定义了哪些来源(origins)的请求可以访问本应用的接口、允许的HTTP方法、请求头以及其他相关的跨域设置等信息。 + * + * 注意事项: + * - 对于生产环境,建议将配置中的通配符(*)修改为实际需要允许跨域访问的具体域名, + * 虽然通配符可以方便开发测试阶段允许所有来源访问,但在生产环境出于安全考虑,应精确配置。 + * - 可以通过多次调用addAllowedOrigin等方法配置多个允许跨域访问的域名,例如: + * configuration.addAllowedOrigin("http://localhost:8080"); + * configuration.addAllowedOrigin("http://192.168.1.6:8080"); + * + * @return 返回配置好的CorsConfigurationSource对象,供Spring Web在处理跨域请求时使用其配置信息。 + */ + @Bean + public CorsConfigurationSource corsConfigurationSource() { + // 创建一个CorsConfiguration对象,用于配置跨域相关的各种规则 + CorsConfiguration configuration = new CorsConfiguration(); + // 使用addAllowedOriginPattern方法添加允许跨域访问的来源模式,这里使用通配符(*)表示允许所有来源访问, + // 但需注意在生产环境应按实际情况修改为具体域名,可参考上面的注释说明。 + configuration.addAllowedOriginPattern("*"); + // 修改为添加(add)而不是设置(set)允许的HTTP方法,使用通配符(*)表示允许所有的HTTP方法(如GET、POST、PUT等), + // 这样前端可以使用各种类型的HTTP请求来访问后端接口。 + configuration.addAllowedMethod("*"); + // 这里配置允许的请求头,同样使用通配符(*)表示允许所有的请求头信息, + // 特别重要的是起码需要允许 Access-Control-Allow-Origin这个请求头,它与跨域访问的合法性验证密切相关。 + configuration.addAllowedHeader("*"); + // 设置是否允许发送Cookie等凭证信息,设置为true表示允许, + // 若前后端交互需要携带用户认证等相关凭证(如Cookie中的登录信息),则需要开启此项。 + configuration.setAllowCredentials(true); + // 设置预检请求(OPTIONS请求)的缓存时间,单位为秒,这里设置为一天(3600 * 24秒), + // 在缓存有效期内,对于相同来源、方法和请求头的请求,浏览器不会再次发送预检请求,提高性能。 + configuration.setMaxAge(3600 * 24L); + + // 创建一个基于URL的CorsConfigurationSource对象,它可以根据不同的URL路径应用不同的CorsConfiguration配置。 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + // 将上面配置好的CorsConfiguration应用到所有路径(/**表示匹配所有的请求路径), + // 意味着所有的接口请求都会按照这个配置来处理跨域相关的规则。 + source.registerCorsConfiguration("/**", configuration); + + return source; + } +} \ No newline at end of file -- 2.34.1 From ffcb1a13fb7984e2efd2a710fc6c23887f4cf337 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 00:21:43 +0800 Subject: [PATCH 080/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/DefaultAuthConfigAdapter.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/DefaultAuthConfigAdapter.java diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/DefaultAuthConfigAdapter.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/DefaultAuthConfigAdapter.java new file mode 100644 index 0000000..22c173c --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/DefaultAuthConfigAdapter.java @@ -0,0 +1,53 @@ +package com.yami.shop.security.common.adapter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.List; + +/** + * DefaultAuthConfigAdapter类实现了AuthConfigAdapter接口,主要用于配置认证相关的路径规则。 + * 它定义了哪些路径需要进行认证(通过pathPatterns方法)以及哪些路径不需要进行认证(通过excludePathPatterns方法), + * 在整个应用的安全认证体系中起着路径筛选和配置的作用。 + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +public class DefaultAuthConfigAdapter implements AuthConfigAdapter { + // 创建一个日志记录器,用于记录与该类相关的日志信息,方便在运行时排查问题、了解类的执行情况等,这里记录器的名称为DefaultAuthConfigAdapter类的全限定名 + private static final Logger logger = LoggerFactory.getLogger(DefaultAuthConfigAdapter.class); + + /** + * 构造方法,在实例化DefaultAuthConfigAdapter对象时会被调用。 + * 此构造方法中主要通过日志记录器输出一条信息,提示没有实现其他的AuthConfigAdapter,当前使用的是DefaultAuthConfigAdapter, + * 并且表示所有的URL都需要进行认证(这是基于当前默认实现的规则设定,具体可能根据业务需求在子类中进行重写来改变)。 + */ + public DefaultAuthConfigAdapter() { + logger.info("not implement other AuthConfigAdapter, use DefaultAuthConfigAdapter... all url need auth..."); + } + + /** + * pathPatterns方法实现了AuthConfigAdapter接口中定义的方法,用于返回需要进行认证的路径模式列表。 + * 在当前的默认实现中,返回一个包含通配符“/*”的列表,表示所有的路径都需要进行认证, + * 当然,具体的业务场景下如果有更精细的认证路径需求,可以通过继承该类并重写此方法来实现不同的路径配置。 + * + * @return 返回一个List类型的列表,其中元素为需要进行认证的路径模式,当前默认返回包含“/*”的列表,表示所有路径。 + */ + @Override + public List pathPatterns() { + return Collections.singletonList("/*"); + } + + /** + * excludePathPatterns方法同样是实现AuthConfigAdapter接口定义的方法,用于返回不需要进行认证的路径模式列表。 + * 在当前默认实现中,返回一个空的列表,表示没有任何路径被排除在认证之外,即所有路径都按照pathPatterns方法定义的规则进行认证, + * 同样,可根据实际业务需求在子类中重写此方法来指定特定的不需要认证的路径。 + * + * @return 返回一个List类型的列表,其中元素为不需要进行认证的路径模式,当前默认返回空列表,表示无排除路径。 + */ + @Override + public List excludePathPatterns() { + return Collections.emptyList(); + } +} \ No newline at end of file -- 2.34.1 From bee0c0721031fd1f10b8e8c514c558ab128dd5a5 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 10:21:53 +0800 Subject: [PATCH 081/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/MyOrderController.java | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/controller/MyOrderController.java diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/MyOrderController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/MyOrderController.java new file mode 100644 index 0000000..69050c7 --- /dev/null +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/MyOrderController.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.api.controller; // 定义类所在的包 + +import cn.hutool.core.bean.BeanUtil; // 引入Hutool工具类库中的BeanUtil工具类 +import com.baomidou.mybatisplus.core.metadata.IPage; // 引入MyBatis-Plus的分页接口 +import com.yami.shop.bean.app.dto.*; // 引入各种DTO类 +import com.yami.shop.bean.enums.OrderStatus; // 引入订单状态枚举 +import com.yami.shop.bean.model.Order; // 引入订单模型 +import com.yami.shop.bean.model.OrderItem; // 引入订单项模型 +import com.yami.shop.bean.model.ShopDetail; // 引入店铺详情模型 +import com.yami.shop.bean.model.UserAddrOrder; // 引入用户地址订单模型 +import com.yami.shop.common.exception.YamiShopBindException; // 引入自定义异常类 +import com.yami.shop.common.response.ServerResponseEntity; // 引入服务器响应实体类 +import com.yami.shop.common.util.Arith; // 引入算术工具类 +import com.yami.shop.common.util.PageParam; // 引入分页参数工具类 +import com.yami.shop.security.api.util.SecurityUtils; // 引入安全工具类 +import com.yami.shop.service.*; // 引入各种服务类 +import io.swagger.v3.oas.annotations.Operation; // 引入Swagger的Operation注解 +import io.swagger.v3.oas.annotations.Parameter; // 引入Swagger的Parameter注解 +import io.swagger.v3.oas.annotations.Parameters; // 引入Swagger的Parameters注解 +import io.swagger.v3.oas.annotations.tags.Tag; // 引入Swagger的Tag注解 +import lombok.AllArgsConstructor; // 引入Lombok的@AllArgsConstructor注解 +import org.springframework.web.bind.annotation.*; + +import java.util.Collections; // 引入Java的Collections工具类 +import java.util.List; // 引入Java的List接口 +import java.util.Objects; // 引入Java的Objects工具类 + +/** + * MyOrderController类,用于管理用户的订单操作。 + * 该类包含获取订单详情、获取订单列表、取消订单、确认收货、删除订单和获取订单数量的方法。 + * @作者 lanhai + */ +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@RequestMapping("/p/myOrder") // 定义请求路径的根地址为/p/myOrder +@Tag(name = "我的订单接口") // 给API文档添加标签,描述这个控制器的功能 +@AllArgsConstructor // 使用Lombok注解生成全参构造函数 +public class MyOrderController { + + private final OrderService orderService; // 注入订单服务类 + + private final UserAddrOrderService userAddrOrderService; // 注入用户地址订单服务类 + + private final ProductService productService; // 注入商品服务类 + + private final SkuService skuService; // 注入SKU服务类 + + private final MyOrderService myOrderService; // 注入我的订单服务类 + + private final ShopDetailService shopDetailService; // 注入店铺详情服务类 + + private final OrderItemService orderItemService; // 注入订单项服务类 + + /** + * 订单详情信息接口 + * @param orderNumber 订单号 + * @return 服务器响应实体,包含订单详情信息 + */ + @GetMapping("/orderDetail") + @Operation(summary = "订单详情信息", description = "根据订单号获取订单详情信息") + @Parameter(name = "orderNumber", description = "订单号", required = true) + public ServerResponseEntity orderDetail(@RequestParam(value = "orderNumber") String orderNumber) { + + String userId = SecurityUtils.getUser().getUserId(); + OrderShopDto orderShopDto = new OrderShopDto(); + + Order order = orderService.getOrderByOrderNumber(orderNumber); + + if (order == null) { + throw new RuntimeException("该订单不存在"); + } + if (!Objects.equals(order.getUserId(), userId)) { + throw new RuntimeException("你没有权限获取该订单信息"); + } + + ShopDetail shopDetail = shopDetailService.getShopDetailByShopId(order.getShopId()); + UserAddrOrder userAddrOrder = userAddrOrderService.getById(order.getAddrOrderId()); + UserAddrDto userAddrDto = BeanUtil.copyProperties(userAddrOrder, UserAddrDto.class); + List orderItems = orderItemService.getOrderItemsByOrderNumber(orderNumber); + List orderItemList = BeanUtil.copyToList(orderItems, OrderItemDto.class); + + orderShopDto.setShopId(shopDetail.getShopId()); + orderShopDto.setShopName(shopDetail.getShopName()); + orderShopDto.setActualTotal(order.getActualTotal()); + orderShopDto.setUserAddrDto(userAddrDto); + orderShopDto.setOrderItemDtos(orderItemList); + orderShopDto.setTransfee(order.getFreightAmount()); + orderShopDto.setReduceAmount(order.getReduceAmount()); + orderShopDto.setCreateTime(order.getCreateTime()); + orderShopDto.setRemarks(order.getRemarks()); + orderShopDto.setStatus(order.getStatus()); + + double total = 0.0; + Integer totalNum = 0; + for (OrderItemDto orderItem : orderShopDto.getOrderItemDtos()) { + total = Arith.add(total, orderItem.getProductTotalAmount()); + totalNum += orderItem.getProdCount(); + } + orderShopDto.setTotal(total); + orderShopDto.setTotalNum(totalNum); + + return ServerResponseEntity.success(orderShopDto); + } + + /** + * 订单列表接口 + * @param status 订单状态 + * @param page 分页参数 + * @return 服务器响应实体,包含订单列表信息 + */ + @GetMapping("/myOrder") + @Operation(summary = "订单列表信息", description = "根据订单状态获取订单列表信息,状态为0时获取所有订单") + @Parameters({ + @Parameter(name = "status", description = "订单状态 1:待付款 2:待发货 3:待收货 4:待评价 5:成功 6:失败") + }) + public ServerResponseEntity> myOrder(@RequestParam(value = "status") Integer status, PageParam page) { + String userId = SecurityUtils.getUser().getUserId(); + IPage myOrderDtoIpage = myOrderService.pageMyOrderByUserIdAndStatus(page, userId, status); + return ServerResponseEntity.success(myOrderDtoIpage); + } + + /** + * 取消订单 + * @param orderNumber 订单号 + * @return 服务器响应实体 + */ + @PutMapping("/cancel/{orderNumber}") + @Operation(summary = "根据订单号取消订单", description = "根据订单号取消订单") + @Parameter(name = "orderNumber", description = "订单号", required = true) + public ServerResponseEntity cancel(@PathVariable("orderNumber") String orderNumber) { + String userId = SecurityUtils.getUser().getUserId(); + Order order = orderService.getOrderByOrderNumber(orderNumber); + if (!Objects.equals(order.getUserId(), userId)) { + throw new YamiShopBindException("你没有权限获取该订单信息"); + } + if (!Objects.equals(order.getStatus(), OrderStatus.UNPAY.value())) { + throw new YamiShopBindException("订单已支付,无法取消订单"); + } + List orderItems = orderItemService.getOrderItemsByOrderNumber(orderNumber); + order.setOrderItems(orderItems); + // 取消订单 + orderService.cancelOrders(Collections.singletonList(order)); + + // 清除缓存 + for (OrderItem orderItem : orderItems) { + productService.removeProductCacheByProdId(orderItem.getProdId()); + skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId()); + } + return ServerResponseEntity.success(); + } + + /** + * 确认收货 + * @param orderNumber 订单号 + * @return 服务器响应实体 + */ + @PutMapping("/receipt/{orderNumber}") + @Operation(summary = "根据订单号确认收货", description = "根据订单号确认收货") + public ServerResponseEntity receipt(@PathVariable("orderNumber") String orderNumber) { + String userId = SecurityUtils.getUser().getUserId(); + Order order = orderService.getOrderByOrderNumber(orderNumber); + if (!Objects.equals(order.getUserId(), userId)) { + throw new YamiShopBindException("你没有权限获取该订单信息"); + } + if (!Objects.equals(order.getStatus(), OrderStatus.CONSIGNMENT.value())) { + throw new YamiShopBindException("订单未发货,无法确认收货"); + } + List orderItems = orderItemService.getOrderItemsByOrderNumber(orderNumber); + order.setOrderItems(orderItems); + // 确认收货 + orderService.confirmOrder(Collections.singletonList(order)); + + for (OrderItem orderItem : orderItems) { + productService.removeProductCacheByProdId(orderItem.getProdId()); + skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId()); + } + return ServerResponseEntity.success(); + } + + /** + * 删除订单 + * @param orderNumber 订单号 + * @return 服务器响应实体 + */ + @DeleteMapping("/{orderNumber}") + @Operation(summary = "根据订单号删除订单", description = "根据订单号删除订单") + @Parameter(name = "orderNumber", description = "订单号", required = true) + public ServerResponseEntity delete(@PathVariable("orderNumber") String orderNumber) { + String userId = SecurityUtils.getUser().getUserId(); + + Order order = orderService.getOrderByOrderNumber(orderNumber); + if (order == null) { + throw new YamiShopBindException("该订单不存在"); + } + if (!Objects.equals(order.getUserId(), userId)) { + throw new YamiShopBindException("你没有权限获取该订单信息"); + } + if (!Objects.equals(order.getStatus(), OrderStatus.SUCCESS.value()) && !Objects.equals(order.getStatus(), OrderStatus.CLOSE.value())) { + throw new YamiShopBindException("订单未完成或未关闭,无法删除订单"); + } + + // 删除订单 + orderService.deleteOrders(Collections.singletonList(order)); + + return ServerResponseEntity.success("删除成功"); + } + + /** + * 获取我的订单订单数量 + */ + @GetMapping("/orderCount") + @Operation(summary = "获取我的订单订单数量", description = "获取我的订单订单数量") + public ServerResponseEntity getOrderCount() { + String userId = SecurityUtils.getUser().getUserId(); + OrderCountData orderCountMap = orderService.getOrderCount(userId); + return ServerResponseEntity.success(orderCountMap); + } +} -- 2.34.1 From a59783546236a98350f400e3594ddac298869c52 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 10:34:18 +0800 Subject: [PATCH 082/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/OrderController.java | 309 ++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/controller/OrderController.java diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/OrderController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/OrderController.java new file mode 100644 index 0000000..caef8f1 --- /dev/null +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/OrderController.java @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.admin.controller; // 定义类所在的包 + +import cn.hutool.core.date.DatePattern; // 引入Hutool工具类库中的日期格式化工具类 +import cn.hutool.core.date.DateUtil; // 引入Hutool工具类库中的日期工具类 +import cn.hutool.core.io.IORuntimeException; // 引入Hutool工具类库中的IO运行时异常类 +import cn.hutool.core.io.IoUtil; // 引入Hutool工具类库中的IO工具类 +import cn.hutool.poi.excel.ExcelUtil; // 引入Hutool工具类库中的Excel工具类 +import cn.hutool.poi.excel.ExcelWriter; // 引入Hutool工具类库中的Excel写入工具类 +import com.baomidou.mybatisplus.core.metadata.IPage; // 引入MyBatis-Plus的分页接口 +import com.google.common.base.Objects; // 引入Google Guava库中的Objects工具类 +import com.yami.shop.bean.enums.OrderStatus; // 引入订单状态枚举类 +import com.yami.shop.bean.model.Order; // 引入订单模型类 +import com.yami.shop.bean.model.OrderItem; // 引入订单项模型类 +import com.yami.shop.bean.model.UserAddrOrder; // 引入用户地址订单模型类 +import com.yami.shop.bean.param.DeliveryOrderParam; // 引入发货订单参数类 +import com.yami.shop.bean.param.OrderParam; // 引入订单参数类 +import com.yami.shop.common.exception.YamiShopBindException; // 引入自定义异常类 +import com.yami.shop.common.response.ServerResponseEntity; // 引入服务器响应实体类 +import com.yami.shop.common.util.PageParam; // 引入分页参数工具类 +import com.yami.shop.security.admin.util.SecurityUtils; // 引入安全工具类 +import com.yami.shop.service.*; // 引入各种服务类 +import jakarta.servlet.ServletOutputStream; // 引入Servlet输出流类 +import jakarta.servlet.http.HttpServletResponse; // 引入HTTP响应类 +import lombok.extern.slf4j.Slf4j; // 引入Lombok的日志记录注解 +import org.apache.poi.ss.usermodel.Sheet; // 引入Apache POI的Sheet类 +import org.springframework.beans.factory.annotation.Autowired; // 引入Spring的@Autowired注解 +import org.springframework.format.annotation.DateTimeFormat; // 引入Spring的日期时间格式化注解 +import org.springframework.security.access.prepost.PreAuthorize; // 引入Spring Security的PreAuthorize注解 +import org.springframework.web.bind.annotation.*; // 引入Spring Web的注解 + +import java.io.IOException; // 引入Java的IO异常类 +import java.util.Arrays; // 引入Java的Arrays工具类 +import java.util.Date; // 引入Java的Date类 +import java.util.List; // 引入Java的List接口 + +/** + * OrderController类,用于管理订单操作,包括分页获取、查看详情、发货、导出订单信息等功能。 + * @作者 lgh on 2018/09/15. + */ +@Slf4j // 标注这是一个需要日志记录的类 +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@RequestMapping("/order/order") // 定义请求路径的根地址为/order/order +public class OrderController { + + @Autowired + private OrderService orderService; // 自动注入订单服务类 + + @Autowired + private OrderItemService orderItemService; // 自动注入订单项服务类 + + @Autowired + private UserAddrOrderService userAddrOrderService; // 自动注入用户地址订单服务类 + + @Autowired + private ProductService productService; // 自动注入商品服务类 + + @Autowired + private SkuService skuService; // 自动注入SKU服务类 + + /** + * 分页获取订单信息 + * @param orderParam 订单查询参数 + * @param page 分页参数 + * @return 服务器响应实体,包含分页后的订单信息 + */ + @GetMapping("/page") + @PreAuthorize("@pms.hasPermission('order:order:page')") // 权限检查 + public ServerResponseEntity> page(OrderParam orderParam, PageParam page) { + Long shopId = SecurityUtils.getSysUser().getShopId(); + orderParam.setShopId(shopId); + IPage orderPage = orderService.pageOrdersDetailByOrderParam(page, orderParam); + return ServerResponseEntity.success(orderPage); // 返回分页结果 + } + + /** + * 获取订单详情信息 + * @param orderNumber 订单编号 + * @return 服务器响应实体,包含订单的详细信息 + */ + @GetMapping("/orderInfo/{orderNumber}") + @PreAuthorize("@pms.hasPermission('order:order:info')") // 权限检查 + public ServerResponseEntity info(@PathVariable("orderNumber") String orderNumber) { + Long shopId = SecurityUtils.getSysUser().getShopId(); + Order order = orderService.getOrderByOrderNumber(orderNumber); + if (!Objects.equal(shopId, order.getShopId())) { + throw new YamiShopBindException("您没有权限获取该订单信息"); + } + List orderItems = orderItemService.getOrderItemsByOrderNumber(orderNumber); + order.setOrderItems(orderItems); + UserAddrOrder userAddrOrder = userAddrOrderService.getById(order.getAddrOrderId()); + order.setUserAddrOrder(userAddrOrder); + return ServerResponseEntity.success(order); + } + + /** + * 发货 + * @param deliveryOrderParam 发货订单参数 + * @return 服务器响应实体 + */ + @PutMapping("/delivery") + @PreAuthorize("@pms.hasPermission('order:order:delivery')") // 权限检查 + public ServerResponseEntity delivery(@RequestBody DeliveryOrderParam deliveryOrderParam) { + Long shopId = SecurityUtils.getSysUser().getShopId(); + Order order = orderService.getOrderByOrderNumber(deliveryOrderParam.getOrderNumber()); + if (!Objects.equal(shopId, order.getShopId())) { + throw new YamiShopBindException("您没有权限修改该订单信息"); + } + + Order orderParam = new Order(); + orderParam.setOrderId(order.getOrderId()); + orderParam.setDvyId(deliveryOrderParam.getDvyId()); + orderParam.setDvyFlowId(deliveryOrderParam.getDvyFlowId()); + orderParam.setDvyTime(new Date()); + orderParam.setStatus(OrderStatus.CONSIGNMENT.value()); + orderParam.setUserId(order.getUserId()); + + orderService.delivery(orderParam); + + List orderItems = orderItemService.getOrderItemsByOrderNumber(deliveryOrderParam.getOrderNumber()); + for (OrderItem orderItem : orderItems) { + productService.removeProductCacheByProdId(orderItem.getProdId()); + skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId()); + } + return ServerResponseEntity.success(); + } + + /** + * 打印待发货的订单表 + * @param order 订单对象 + * @param consignmentName 发件人姓名 + * @param consignmentMobile 发货人手机号 + * @param consignmentAddr 发货地址 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param response HTTP响应对象 + */ + * @param order 订单对象 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param response HTTP响应对象 + */ + @GetMapping("/waitingConsignmentExcel") + @PreAuthorize("@pms.hasPermission('order:order:waitingConsignmentExcel')") // 权限检查 + public void waitingConsignmentExcel(Order order, @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime, + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime, String consignmentName, String consignmentMobile, + String consignmentAddr, HttpServletResponse response) { + Long shopId = SecurityUtils.getSysUser().getShopId(); + order.setShopId(shopId); + order.setStatus(OrderStatus.PADYED.value()); + List orders = orderService.listOrdersDetailByOrder(order, startTime, endTime); + + // 通过工具类创建ExcelWriter + ExcelWriter writer = ExcelUtil.getBigWriter(); + Sheet sheet = writer.getSheet(); + sheet.setColumnWidth(0, 20 * 256); + sheet.setColumnWidth(1, 20 * 256); + sheet.setColumnWidth(2, 20 * 256); + sheet.setColumnWidth(3, 60 * 256); + sheet.setColumnWidth(4, 60 * 256); + sheet.setColumnWidth(7, 60 * 256); + sheet.setColumnWidth(8, 60 * 256); + sheet.setColumnWidth(9, 60 * 256); + // 待发货 + String[] hearder = {"订单编号", "收件人", "手机", "收货地址", "商品名称", "数量", "发件人姓名", "发件人手机号", "发货地址", "备注"}; + writer.merge(hearder.length - 1, "发货信息整理"); + writer.writeRow(Arrays.asList(hearder)); + + int row = 1; + for (Order dbOrder : orders) { + UserAddrOrder addr = dbOrder.getUserAddrOrder(); + String addrInfo = addr.getProvince() + addr.getCity() + addr.getArea() + addr.getAddr(); + List orderItems = dbOrder.getOrderItems(); + row++; + for (OrderItem orderItem : orderItems) { + // 第0列开始 + int col = 0; + writer.writeCellValue(col++, row, dbOrder.getOrderNumber()); + writer.writeCellValue(col++, row, addr.getReceiver()); + writer.writeCellValue(col++, row, addr.getMobile()); + writer.writeCellValue(col++, row, addrInfo); + writer.writeCellValue(col++, row, orderItem.getProdName()); + writer.writeCellValue(col++, row, orderItem.getProdCount()); + writer.writeCellValue(col++, row, consignmentName); + writer.writeCellValue(col++, row, consignmentMobile); + writer.writeCellValue(col++, row, consignmentAddr); + writer.writeCellValue(col++, row, dbOrder.getRemarks()); + } + } + writeExcel(response, writer); + } + + /** + * 已销售订单 + * @param order 订单对象 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param response HTTP响应对象 + */ + + @GetMapping("/soldExcel") + @PreAuthorize("@pms.hasPermission('order:order:soldExcel')") // 权限检查 + public void soldExcel(Order order, @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime, + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime, HttpServletResponse response) { + Long shopId = SecurityUtils.getSysUser().getShopId(); + order.setShopId(shopId); + order.setIsPayed(1); + List orders = orderService.listOrdersDetailByOrder(order, startTime, endTime); + + // 通过工具类创建ExcelWriter + ExcelWriter writer = ExcelUtil.getBigWriter(); + String[] hearder = {"订单编号", "下单时间", "收件人", "手机", "收货地址", "商品名称", "数量", "订单应付", "订单运费", "订单实付"}; + Sheet sheet = writer.getSheet(); + sheet.setColumnWidth(0, 20 * 256); + sheet.setColumnWidth(1, 20 * 256); + sheet.setColumnWidth(3, 20 * 256); + sheet.setColumnWidth(4, 60 * 256); + sheet.setColumnWidth(5, 60 * 256); + + writer.merge(hearder.length - 1, "销售信息整理"); + writer.writeRow(Arrays.asList(hearder)); + + int row = 1; + for (Order dbOrder : orders) { + UserAddrOrder addr = dbOrder.getUserAddrOrder(); + String addrInfo = addr.getProvince() + addr.getCity() + addr.getArea() + addr.getAddr(); + List orderItems = dbOrder.getOrderItems(); + int firstRow = row + 1; + int lastRow = row + orderItems.size(); + int col = -1; + // 订单编号 + mergeIfNeed(writer, firstRow, lastRow, ++col, col, dbOrder.getOrderNumber()); + // 下单时间 + mergeIfNeed(writer, firstRow, lastRow, ++col, col, dbOrder.getCreateTime()); + // 收件人 + mergeIfNeed(writer, firstRow, lastRow, ++col, col, addr.getReceiver()); + // 手机 + mergeIfNeed(writer, firstRow, lastRow, ++col, col, addr.getMobile()); + // 收货地址 + mergeIfNeed(writer, firstRow, lastRow, ++col, col, addrInfo); + int prodNameCol = ++col; + int prodCountCol = ++col; + for (OrderItem orderItem : orderItems) { + row++; + // 商品名称 + writer.writeCellValue(prodNameCol, row, orderItem.getProdName()); + // 数量 + writer.writeCellValue(prodCountCol, row, orderItem.getProdCount()); + } + // 订单应付 + mergeIfNeed(writer, firstRow, lastRow, ++col, col, dbOrder.getTotal()); + // 订单运费 + mergeIfNeed(writer, firstRow, lastRow, ++col, col, dbOrder.getFreightAmount()); + // 订单实付 + mergeIfNeed(writer, firstRow, lastRow, ++col, col, dbOrder.getActualTotal()); + } + writeExcel(response, writer); + } + + /** + * 如果需要合并的话,就合并 + * @param writer ExcelWriter对象 + * @param firstRow 起始行 + * @param lastRow 结束行 + * @param firstColumn 起始列 + * @param lastColumn 结束列 + * @param content 合并内容 + */ + private void mergeIfNeed(ExcelWriter writer, int firstRow, int lastRow, int firstColumn, int lastColumn, Object content) { + if (content instanceof Date) { + content = DateUtil.format((Date) content, DatePattern.NORM_DATETIME_PATTERN); + } + if (lastRow - firstRow > 0 || lastColumn - firstColumn > 0) { + writer.merge(firstRow, lastRow, firstColumn, lastColumn, content, false); + } else { + writer.writeCellValue(firstColumn, firstRow, content); + } + } + + /** + * 写出Excel文件 + * @param response HTTP响应对象 + * @param writer ExcelWriter对象 + */ + private void writeExcel(HttpServletResponse response, ExcelWriter writer) { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment;filename=1.xls"); + + ServletOutputStream servletOutputStream = null; + try { + servletOutputStream = response.getOutputStream(); + writer.flush(servletOutputStream); + servletOutputStream.flush(); + } catch (IORuntimeException | IOException e) { + log.error("写出Excel错误:", e); + } finally { + IoUtil.close(writer); + } + } +} \ No newline at end of file -- 2.34.1 From 3d62bf54e669a47b8e55e12bcb7b9309863cdb0c Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 10:41:47 +0800 Subject: [PATCH 083/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/api/controller/OrderController.java | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/controller/OrderController.java diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/OrderController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/OrderController.java new file mode 100644 index 0000000..3dca831 --- /dev/null +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/OrderController.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.api.controller; // 定义类所在的包 + +import cn.hutool.core.collection.CollectionUtil; // 引入Hutool工具类库中的CollectionUtil工具类 +import com.yami.shop.bean.app.dto.*; // 引入各种DTO类 +import com.yami.shop.bean.app.param.OrderParam; // 引入订单参数类 +import com.yami.shop.bean.app.param.OrderShopParam; // 引入订单店铺参数类 +import com.yami.shop.bean.app.param.SubmitOrderParam; // 引入提交订单参数类 +import com.yami.shop.bean.event.ConfirmOrderEvent; // 引入确认订单事件类 +import com.yami.shop.bean.model.Order; // 引入订单模型类 +import com.yami.shop.bean.model.UserAddr; // 引入用户地址模型类 +import com.yami.shop.common.exception.YamiShopBindException; // 引入自定义异常类 +import com.yami.shop.common.util.Arith; // 引入算术工具类 +import com.yami.shop.security.api.util.SecurityUtils; // 引入安全工具类 +import com.yami.shop.service.*; // 引入各种服务类 +import io.swagger.v3.oas.annotations.tags.Tag; // 引入Swagger的Tag注解 +import io.swagger.v3.oas.annotations.Operation; // 引入Swagger的Operation注解 +import cn.hutool.core.bean.BeanUtil; // 引入Hutool工具类库中的BeanUtil工具类 +import org.springframework.beans.factory.annotation.Autowired; // 引入Spring的@Autowired注解 +import org.springframework.context.ApplicationContext; // 引入Spring的ApplicationContext类 +import com.yami.shop.common.response.ServerResponseEntity; // 引入服务器响应实体类 +import org.springframework.web.bind.annotation.*; // 引入Spring Web的注解 + +import jakarta.validation.Valid; // 引入Jakarta Validation的Valid注解 +import java.util.ArrayList; // 引入Java的ArrayList类 +import java.util.List; // 引入Java的List接口 +import java.util.Objects; // 引入Java的Objects工具类 + +/** + * OrderController类,用于管理订单操作,包括生成订单和提交订单等功能。 + * @作者 lanhai + */ +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@RequestMapping("/p/order") // 定义请求路径的根地址为/p/order +@Tag(name = "订单接口") // 给API文档添加标签,描述这个控制器的功能 +public class OrderController { + + @Autowired + private OrderService orderService; // 自动注入订单服务类 + @Autowired + private SkuService skuService; // 自动注入SKU服务类 + @Autowired + private ProductService productService; // 自动注入商品服务类 + @Autowired + private UserAddrService userAddrService; // 自动注入用户地址服务类 + @Autowired + private BasketService basketService; // 自动注入购物车服务类 + @Autowired + private ApplicationContext applicationContext; // 自动注入Spring应用上下文 + + /** + * 生成订单 + * @param orderParam 订单参数 + * @return 服务器响应实体,包含生成的订单信息 + */ + @PostMapping("/confirm") + @Operation(summary = "结算,生成订单信息" , description = "传入下单所需要的参数进行下单") + public ServerResponseEntity confirm(@Valid @RequestBody OrderParam orderParam) { + String userId = SecurityUtils.getUser().getUserId(); + + // 获取订单的地址信息 + UserAddr userAddr = userAddrService.getUserAddrByUserId(orderParam.getAddrId(), userId); + UserAddrDto userAddrDto = BeanUtil.copyProperties(userAddr, UserAddrDto.class); + + // 获取用户提交的购物车商品项 + List shopCartItems = basketService.getShopCartItemsByOrderItems(orderParam.getBasketIds(), orderParam.getOrderItem(), userId); + if (CollectionUtil.isEmpty(shopCartItems)) { + throw new YamiShopBindException("请选择您需要的商品加入购物车"); + } + + // 根据店铺组装购物车中的商品信息 + List shopCarts = basketService.getShopCarts(shopCartItems); + + // 生成完整的订单信息 + ShopCartOrderMergerDto shopCartOrderMergerDto = new ShopCartOrderMergerDto(); + shopCartOrderMergerDto.setUserAddr(userAddrDto); + List shopCartOrders = new ArrayList<>(); + double actualTotal = 0.0; + double total = 0.0; + int totalCount = 0; + double orderReduce = 0.0; + + for (ShopCartDto shopCart : shopCarts) { + ShopCartOrderDto shopCartOrder = new ShopCartOrderDto(); + shopCartOrder.setShopId(shopCart.getShopId()); + shopCartOrder.setShopName(shopCart.getShopName()); + + List shopCartItemDiscounts = shopCart.getShopCartItemDiscounts(); + List shopAllShopCartItems = new ArrayList<>(); + for (ShopCartItemDiscountDto shopCartItemDiscount : shopCartItemDiscounts) { + List discountShopCartItems = shopCartItemDiscount.getShopCartItems(); + shopAllShopCartItems.addAll(discountShopCartItems); + } + + shopCartOrder.setShopCartItemDiscounts(shopCartItemDiscounts); + applicationContext.publishEvent(new ConfirmOrderEvent(shopCartOrder, orderParam, shopAllShopCartItems)); + + actualTotal = Arith.add(actualTotal, shopCartOrder.getActualTotal()); + total = Arith.add(total, shopCartOrder.getTotal()); + totalCount += shopCartOrder.getTotalCount(); + orderReduce = Arith.add(orderReduce, shopCartOrder.getShopReduce()); + shopCartOrders.add(shopCartOrder); + } + + shopCartOrderMergerDto.setActualTotal(actualTotal); + shopCartOrderMergerDto.setTotal(total); + shopCartOrderMergerDto.setTotalCount(totalCount); + shopCartOrderMergerDto.setShopCartOrders(shopCartOrders); + shopCartOrderMergerDto.setOrderReduce(orderReduce); + + shopCartOrderMergerDto = orderService.putConfirmOrderCache(userId, shopCartOrderMergerDto); + + return ServerResponseEntity.success(shopCartOrderMergerDto); + } + + /** + * 提交订单,根据店铺拆单 + * @param submitOrderParam 提交订单参数 + * @return 服务器响应实体,包含支付流水号 + */ + @PostMapping("/submit") + @Operation(summary = "提交订单,返回支付流水号" , description = "根据传入的参数判断是否为购物车提交订单,同时对购物车进行删除,用户开始进行支付") + public ServerResponseEntity submitOrders(@Valid @RequestBody SubmitOrderParam submitOrderParam) { + String userId = SecurityUtils.getUser().getUserId(); + ShopCartOrderMergerDto mergerOrder = orderService.getConfirmOrderCache(userId); + if (mergerOrder == null) { + throw new YamiShopBindException("订单已过期,请重新下单"); + } + + List orderShopParams = submitOrderParam.getOrderShopParam(); + List shopCartOrders = mergerOrder.getShopCartOrders(); + + // 设置备注 + if (CollectionUtil.isNotEmpty(orderShopParams)) { + for (ShopCartOrderDto shopCartOrder : shopCartOrders) { + for (OrderShopParam orderShopParam : orderShopParams) { + if (Objects.equals(shopCartOrder.getShopId(), orderShopParam.getShopId())) { + shopCartOrder.setRemarks(orderShopParam.getRemarks()); + } + } + } + } + + List orders = orderService.submit(userId, mergerOrder); + + StringBuilder orderNumbers = new StringBuilder(); + for (Order order : orders) { + orderNumbers.append(order.getOrderNumber()).append(","); + } + orderNumbers.deleteCharAt(orderNumbers.length() - 1); + + boolean isShopCartOrder = false; + // 移除缓存 + for (ShopCartOrderDto shopCartOrder : shopCartOrders) { + for (ShopCartItemDiscountDto shopCartItemDiscount : shopCartOrder.getShopCartItemDiscounts()) { + for (ShopCartItemDto shopCartItem : shopCartItemDiscount.getShopCartItems()) { + Long basketId = shopCartItem.getBasketId(); + if (basketId != null && basketId != 0) { + isShopCartOrder = true; + } + skuService.removeSkuCacheBySkuId(shopCartItem.getSkuId(), shopCartItem.getProdId()); + productService.removeProductCacheByProdId(shopCartItem.getProdId()); + } + } + } + // 购物车提交订单时(即有购物车ID时) + if (isShopCartOrder) { + basketService.removeShopCartItemsCacheByUserId(userId); + } + orderService.removeConfirmOrderCache(userId); + return ServerResponseEntity.success(new OrderNumbersDto(orderNumbers.toString())); + } + +} -- 2.34.1 From 432dce51084055839b47af150e269f3935263d38 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 10:44:54 +0800 Subject: [PATCH 084/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/admin/task/OrderTask.java | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/task/OrderTask.java diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/task/OrderTask.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/task/OrderTask.java new file mode 100644 index 0000000..a19d08e --- /dev/null +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/task/OrderTask.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.admin.task; // 定义类所在的包 + +import cn.hutool.core.collection.CollectionUtil; // 引入Hutool工具类库中的CollectionUtil工具类 +import cn.hutool.core.date.DateUtil; // 引入Hutool工具类库中的DateUtil工具类 +import com.xxl.job.core.handler.annotation.XxlJob; // 引入XXL-Job的注解 +import com.yami.shop.bean.enums.OrderStatus; // 引入订单状态枚举类 +import com.yami.shop.bean.model.Order; // 引入订单模型类 +import com.yami.shop.bean.model.OrderItem; // 引入订单项模型类 +import com.yami.shop.service.OrderService; // 引入订单服务类 +import com.yami.shop.service.ProductService; // 引入商品服务类 +import com.yami.shop.service.SkuService; // 引入SKU服务类 +import org.slf4j.Logger; // 引入SLF4J的Logger类 +import org.slf4j.LoggerFactory; // 引入SLF4J的LoggerFactory类 +import org.springframework.beans.factory.annotation.Autowired; // 引入Spring的@Autowired注解 +import org.springframework.stereotype.Component; // 引入Spring的@Component注解 + +import java.util.Date; // 引入Java的Date类 +import java.util.List; // 引入Java的List接口 + +/** + * OrderTask类,用于管理与订单相关的定时任务操作。 + * 定时任务的配置,请查看xxl-job的java配置文件。 + * @作者 FrozenWatermelon + * @参见 com.yami.shop.admin.config.XxlJobConfig + */ +@Component("orderTask") // 标注这是一个Spring组件,并且以orderTask为组件名称 +public class OrderTask { + + private static final Logger logger = LoggerFactory.getLogger(OrderTask.class); // 日志记录器 + + @Autowired + private OrderService orderService; // 自动注入订单服务类 + @Autowired + private ProductService productService; // 自动注入商品服务类 + @Autowired + private SkuService skuService; // 自动注入SKU服务类 + + /** + * 取消超时未支付订单 + */ + @XxlJob("cancelOrder") + public void cancelOrder() { + Date now = new Date(); + logger.info("取消超时未支付订单。。。"); + // 获取30分钟之前未支付的订单 + List orders = orderService.listOrderAndOrderItems(OrderStatus.UNPAY.value(), DateUtil.offsetMinute(now, -30)); + if (CollectionUtil.isEmpty(orders)) { + return; + } + orderService.cancelOrders(orders); + for (Order order : orders) { + List orderItems = order.getOrderItems(); + for (OrderItem orderItem : orderItems) { + productService.removeProductCacheByProdId(orderItem.getProdId()); + skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId()); + } + } + } + + /** + * 确认收货 + */ + @XxlJob("confirmOrder") + public void confirmOrder() { + Date now = new Date(); + logger.info("系统自动确认收货订单。。。"); + // 获取15天之前未支付的订单 + List orders = orderService.listOrderAndOrderItems(OrderStatus.CONSIGNMENT.value(), DateUtil.offsetDay(now, -15)); + if (CollectionUtil.isEmpty(orders)) { + return; + } + orderService.confirmOrder(orders); + for (Order order : orders) { + List orderItems = order.getOrderItems(); + for (OrderItem orderItem : orderItems) { + productService.removeProductCacheByProdId(orderItem.getProdId()); + skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId()); + } + } + } +} \ No newline at end of file -- 2.34.1 From d707c6d800f56cac97e25e11b8cd1410ede77eb3 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 10:48:35 +0800 Subject: [PATCH 085/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/api/controller/PayController.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/controller/PayController.java diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayController.java new file mode 100644 index 0000000..464314a --- /dev/null +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayController.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.api.controller; // 定义类所在的包 + +import com.yami.shop.bean.app.param.PayParam; // 引入支付参数类 +import com.yami.shop.bean.pay.PayInfoDto; // 引入支付信息DTO类 +import com.yami.shop.security.api.model.YamiUser; // 引入用户模型类 +import com.yami.shop.security.api.util.SecurityUtils; // 引入安全工具类 +import com.yami.shop.service.PayService; // 引入支付服务类 +import io.swagger.v3.oas.annotations.tags.Tag; // 引入Swagger的Tag注解 +import io.swagger.v3.oas.annotations.Operation; // 引入Swagger的Operation注解 +import lombok.AllArgsConstructor; // 引入Lombok的@AllArgsConstructor注解 +import com.yami.shop.common.response.ServerResponseEntity; // 引入服务器响应实体类 +import org.springframework.web.bind.annotation.*; // 引入Spring Web的注解 + +/** + * PayController类,用于处理支付相关的操作。 + * @作者 lanhai + */ +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@RequestMapping("/p/order") // 定义请求路径的根地址为/p/order +@Tag(name = "订单接口") // 给API文档添加标签,描述这个控制器的功能 +@AllArgsConstructor // 使用Lombok注解生成全参构造函数 +public class PayController { + + private final PayService payService; // 自动注入支付服务类 + + /** + * 支付接口 + * @param payParam 支付参数 + * @return 服务器响应实体 + */ + @PostMapping("/pay") + @Operation(summary = "根据订单号进行支付", description = "根据订单号进行支付") + public ServerResponseEntity pay(@RequestBody PayParam payParam) { + YamiUser user = SecurityUtils.getUser(); + String userId = user.getUserId(); + + PayInfoDto payInfo = payService.pay(userId, payParam); // 调用支付服务进行支付 + payService.paySuccess(payInfo.getPayNo(), ""); // 调用支付成功处理 + return ServerResponseEntity.success(); // 返回成功响应 + } + + /** + * 普通支付接口 + * @param payParam 支付参数 + * @return 服务器响应实体 + */ + @PostMapping("/normalPay") + @Operation(summary = "根据订单号进行支付", description = "根据订单号进行支付") + public ServerResponseEntity normalPay(@RequestBody PayParam payParam) { + YamiUser user = SecurityUtils.getUser(); + String userId = user.getUserId(); + PayInfoDto pay = payService.pay(userId, payParam); // 调用支付服务进行支付 + + // 根据内部订单号更新订单结算信息 + payService.paySuccess(pay.getPayNo(), ""); + + return ServerResponseEntity.success(true); // 返回成功响应 + } +} -- 2.34.1 From e85cddfa37ad01582d17b38b13a4ca99163b8d9a Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 10:51:01 +0800 Subject: [PATCH 086/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/PayNoticeController.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/controller/PayNoticeController.java diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayNoticeController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayNoticeController.java new file mode 100644 index 0000000..6e6542d --- /dev/null +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/PayNoticeController.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.api.controller; // 定义类所在的包 + +import lombok.AllArgsConstructor; // 引入Lombok的@AllArgsConstructor注解 +import org.springframework.web.bind.annotation.RequestMapping; // 引入Spring的@RequestMapping注解 +import org.springframework.web.bind.annotation.RestController; // 引入Spring的@RestController注解 +import io.swagger.v3.oas.annotations.Hidden; // 引入Swagger的Hidden注解 + +/** + * PayNoticeController类,用于处理支付通知。 + * @作者 lanhai + */ +@Hidden // 隐藏这个控制器,不在Swagger文档中展示 +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@RequestMapping("/notice/pay") // 定义请求路径的根地址为/notice/pay +@AllArgsConstructor // 使用Lombok注解生成全参构造函数 +public class PayNoticeController { +// 模拟支付不需要回调 +// /** +// * 小程序支付 +// */ +// private final WxPayService wxMiniPayService; +// +// private final PayService payService; +// +// @RequestMapping("/order") +// public ServerResponseEntity submit(@RequestBody String xmlData) throws WxPayException { +// WxPayOrderNotifyResult parseOrderNotifyResult = wxMiniPayService.parseOrderNotifyResult(xmlData); +// +// String payNo = parseOrderNotifyResult.getOutTradeNo(); +// String bizPayNo = parseOrderNotifyResult.getTransactionId(); +// +// // 根据内部订单号更新order settlement +// payService.paySuccess(payNo, bizPayNo); +// +// return ServerResponseEntity.success(); +// } +} -- 2.34.1 From 4b385e8eb9028e90a7392617481ea0b3530a12eb Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 10:54:09 +0800 Subject: [PATCH 087/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/PickAddrController.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/controller/PickAddrController.java diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/PickAddrController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/PickAddrController.java new file mode 100644 index 0000000..d3e60de --- /dev/null +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/PickAddrController.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.admin.controller; // 定义类所在的包 + +import cn.hutool.core.util.StrUtil; // 引入Hutool工具类库中的StrUtil工具类 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; // 引入MyBatis-Plus的条件查询包装器 +import com.baomidou.mybatisplus.core.metadata.IPage; // 引入MyBatis-Plus的分页接口 +import com.yami.shop.bean.model.PickAddr; // 引入自提点地址模型类 +import com.yami.shop.common.exception.YamiShopBindException; // 引入自定义异常类 +import com.yami.shop.common.response.ResponseEnum; // 引入响应枚举类 +import com.yami.shop.common.response.ServerResponseEntity; // 引入服务器响应实体类 +import com.yami.shop.common.util.PageParam; // 引入分页参数工具类 +import com.yami.shop.security.admin.util.SecurityUtils; // 引入安全工具类 +import com.yami.shop.service.PickAddrService; // 引入自提点地址服务类 +import org.springframework.beans.factory.annotation.Autowired; // 引入Spring的@Autowired注解 +import org.springframework.security.access.prepost.PreAuthorize; // 引入Spring Security的PreAuthorize注解 +import org.springframework.web.bind.annotation.*; // 引入Spring Web的注解 + +import jakarta.validation.Valid; // 引入Jakarta Validation的Valid注解 +import java.util.Arrays; // 引入Java的Arrays工具类 +import java.util.Objects; // 引入Java的Objects工具类 + +/** + * PickAddrController类,用于管理自提点地址。 + * 该类包含分页获取、获取详细信息、保存、修改和删除自提点地址的方法。 + * @作者 lgh on 2018/10/17. + */ +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@RequestMapping("/shop/pickAddr") // 定义请求路径的根地址为/shop/pickAddr +public class PickAddrController { + + @Autowired + private PickAddrService pickAddrService; // 自动注入自提点地址服务类 + + /** + * 分页获取自提点地址 + * @param pickAddr 自提点地址查询条件 + * @param page 分页参数 + * @return 服务器响应实体,包含分页后的自提点地址信息 + */ + @GetMapping("/page") + @PreAuthorize("@pms.hasPermission('shop:pickAddr:page')") // 权限检查 + public ServerResponseEntity> page(PickAddr pickAddr, PageParam page) { + IPage pickAddrs = pickAddrService.page(page, new LambdaQueryWrapper() + .like(StrUtil.isNotBlank(pickAddr.getAddrName()), PickAddr::getAddrName, pickAddr.getAddrName()) + .orderByDesc(PickAddr::getAddrId)); + return ServerResponseEntity.success(pickAddrs); // 返回分页结果 + } + + /** + * 获取自提点地址详细信息 + * @param id 自提点地址ID + * @return 服务器响应实体,包含自提点地址的详细信息 + */ + @GetMapping("/info/{id}") + @PreAuthorize("@pms.hasPermission('shop:pickAddr:info')") // 权限检查 + public ServerResponseEntity info(@PathVariable("id") Long id) { + PickAddr pickAddr = pickAddrService.getById(id); + return ServerResponseEntity.success(pickAddr); // 返回自提点地址信息 + } + + /** + * 保存自提点地址 + * @param pickAddr 自提点地址信息 + * @return 服务器响应实体 + */ + @PostMapping + @PreAuthorize("@pms.hasPermission('shop:pickAddr:save')") // 权限检查 + public ServerResponseEntity save(@Valid @RequestBody PickAddr pickAddr) { + pickAddr.setShopId(SecurityUtils.getSysUser().getShopId()); // 设置店铺ID + pickAddrService.save(pickAddr); // 保存自提点地址 + return ServerResponseEntity.success(); // 返回成功响应 + } + + /** + * 修改自提点地址 + * @param pickAddr 自提点地址信息 + * @return 服务器响应实体 + */ + @PutMapping + @PreAuthorize("@pms.hasPermission('shop:pickAddr:update')") // 权限检查 + public ServerResponseEntity update(@Valid @RequestBody PickAddr pickAddr) { + PickAddr dbPickAddr = pickAddrService.getById(pickAddr.getAddrId()); + + if (!Objects.equals(dbPickAddr.getShopId(), SecurityUtils.getSysUser().getShopId())) { + throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED); + } + pickAddrService.updateById(pickAddr); // 修改自提点地址 + return ServerResponseEntity.success(); // 返回成功响应 + } + + /** + * 删除自提点地址 + * @param ids 自提点地址ID数组 + * @return 服务器响应实体 + */ + @DeleteMapping + @PreAuthorize("@pms.hasPermission('shop:pickAddr:delete')") // 权限检查 + public ServerResponseEntity delete(@RequestBody Long[] ids) { + pickAddrService.removeByIds(Arrays.asList(ids)); // 删除自提点地址 + return ServerResponseEntity.success(); // 返回成功响应 + } +} -- 2.34.1 From 0e4dd837129eda64a793cce3153b93ec140a3319 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 11:15:30 +0800 Subject: [PATCH 088/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/listener/SubmitOrderListener.java | 270 ++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/listener/SubmitOrderListener.java diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/listener/SubmitOrderListener.java b/yami-shop-api/src/main/java/com/yami/shop/api/listener/SubmitOrderListener.java new file mode 100644 index 0000000..8ada33e --- /dev/null +++ b/yami-shop-api/src/main/java/com/yami/shop/api/listener/SubmitOrderListener.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.api.listener; // 定义类所在的包 + +import cn.hutool.core.lang.Snowflake; // 引入Hutool工具类库中的Snowflake工具类 +import cn.hutool.core.util.StrUtil; // 引入Hutool工具类库中的StrUtil工具类 +import com.yami.shop.bean.app.dto.*; // 引入各种DTO类 +import com.yami.shop.bean.enums.OrderStatus; // 引入订单状态枚举类 +import com.yami.shop.bean.event.SubmitOrderEvent; // 引入提交订单事件类 +import com.yami.shop.bean.model.*; // 引入各种模型类 +import com.yami.shop.bean.order.SubmitOrderOrder; // 引入提交订单顺序类 +import com.yami.shop.common.constants.Constant; // 引入常量类 +import com.yami.shop.common.exception.YamiShopBindException; // 引入自定义异常类 +import com.yami.shop.common.util.Arith; // 引入算术工具类 +import com.yami.shop.dao.*; // 引入各种数据访问对象 +import com.yami.shop.security.api.util.SecurityUtils; // 引入安全工具类 +import com.yami.shop.service.*; // 引入各种服务类 +import lombok.AllArgsConstructor; // 引入Lombok的@AllArgsConstructor注解 +import cn.hutool.core.bean.BeanUtil; // 引入Hutool工具类库中的BeanUtil工具类 +import org.springframework.context.event.EventListener; // 引入Spring的EventListener注解 +import org.springframework.core.annotation.Order; // 引入Spring的Order注解 +import org.springframework.stereotype.Component; // 引入Spring的Component注解 + +import java.util.*; // 引入Java的各种集合类 + +/** + * 确认订单信息时的默认操作 + * 该类包含处理提交订单事件的逻辑,包括订单的创建和库存的更新。 + * @作者 LGH + */ +@Component("defaultSubmitOrderListener") // 标注这是一个Spring组件,并且以defaultSubmitOrderListener为组件名称 +@AllArgsConstructor // 使用Lombok注解生成全参构造函数 +public class SubmitOrderListener { + + private final UserAddrOrderService userAddrOrderService; // 自动注入用户地址订单服务类 + private final ProductService productService; // 自动注入商品服务类 + private final SkuService skuService; // 自动注入SKU服务类 + private final Snowflake snowflake; // 自动注入雪花算法实例 + private final OrderItemMapper orderItemMapper; // 自动注入订单项数据访问对象 + private final SkuMapper skuMapper; // 自动注入SKU数据访问对象 + private final ProductMapper productMapper; // 自动注入商品数据访问对象 + private final OrderMapper orderMapper; // 自动注入订单数据访问对象 + private final OrderSettlementMapper orderSettlementMapper; // 自动注入订单结算数据访问对象 + private final BasketMapper basketMapper; // 自动注入购物车数据访问对象 + + /** + * 计算订单金额 + * @param event 提交订单事件 + */ + @EventListener(SubmitOrderEvent.class) + @Order(SubmitOrderOrder.DEFAULT) + public void defaultSubmitOrderListener(SubmitOrderEvent event) { + Date now = new Date(); + String userId = SecurityUtils.getUser().getUserId(); + + ShopCartOrderMergerDto mergerOrder = event.getMergerOrder(); + + // 订单商品参数 + List shopCartOrders = mergerOrder.getShopCartOrders(); + + List basketIds = new ArrayList<>(); + // 商品skuId为key,需要更新的sku为value的map + Map skuStocksMap = new HashMap<>(16); + // 商品productId为key,需要更新的product为value的map + Map prodStocksMap = new HashMap<>(16); + + // 把订单地址保存到数据库 + UserAddrOrder userAddrOrder = BeanUtil.copyProperties(mergerOrder.getUserAddr(), UserAddrOrder.class); + if (userAddrOrder == null) { + throw new YamiShopBindException("请填写收货地址"); + } + userAddrOrder.setUserId(userId); + userAddrOrder.setCreateTime(now); + userAddrOrderService.save(userAddrOrder); + + // 订单地址id + Long addrOrderId = userAddrOrder.getAddrOrderId(); + + // 每个店铺生成一个订单 + for (ShopCartOrderDto shopCartOrderDto : shopCartOrders) { + createOrder(event, now, userId, basketIds, skuStocksMap, prodStocksMap, addrOrderId, shopCartOrderDto); + } + + // 删除购物车的商品信息 + if (!basketIds.isEmpty()) { + basketMapper.deleteShopCartItemsByBasketIds(userId, basketIds); + } + + // 更新sku库存 + skuStocksMap.forEach((key, sku) -> { + if (skuMapper.updateStocks(sku) == 0) { + skuService.removeSkuCacheBySkuId(key, sku.getProdId()); + throw new YamiShopBindException("商品:[" + sku.getProdName() + "]库存不足"); + } + }); + + // 更新商品库存 + prodStocksMap.forEach((prodId, prod) -> { + if (productMapper.updateStocks(prod) == 0) { + productService.removeProductCacheByProdId(prodId); + throw new YamiShopBindException("商品:[" + prod.getProdName() + "]库存不足"); + } + }); + } + + private void createOrder(SubmitOrderEvent event, Date now, String userId, List basketIds, Map skuStocksMap, Map prodStocksMap, Long addrOrderId, ShopCartOrderDto shopCartOrderDto) { + // 使用雪花算法生成的订单号 + String orderNumber = String.valueOf(snowflake.nextId()); + shopCartOrderDto.setOrderNumber(orderNumber); + + Long shopId = shopCartOrderDto.getShopId(); + + // 订单商品名称 + StringBuilder orderProdName = new StringBuilder(100); + + List orderItems = new ArrayList<>(); + + List shopCartItemDiscounts = shopCartOrderDto.getShopCartItemDiscounts(); + for (ShopCartItemDiscountDto shopCartItemDiscount : shopCartItemDiscounts) { + List shopCartItems = shopCartItemDiscount.getShopCartItems(); + for (ShopCartItemDto shopCartItem : shopCartItems) { + Sku sku = checkAndGetSku(shopCartItem.getSkuId(), shopCartItem, skuStocksMap); + Product product = checkAndGetProd(shopCartItem.getProdId(), shopCartItem, prodStocksMap); + + OrderItem orderItem = getOrderItem(now, userId, orderNumber, shopId, orderProdName, shopCartItem, sku, product); + + orderItems.add(orderItem); + + if (shopCartItem.getBasketId() != null && shopCartItem.getBasketId() != 0) { + basketIds.add(shopCartItem.getBasketId()); + } + } + } + + orderProdName.subSequence(0, Math.min(orderProdName.length() - 1, 100)); + if (orderProdName.lastIndexOf(Constant.COMMA) == orderProdName.length() - 1) { + orderProdName.deleteCharAt(orderProdName.length() - 1); + } + + // 订单信息 + com.yami.shop.bean.model.Order order = getOrder(now, userId, addrOrderId, shopCartOrderDto, orderNumber, shopId, orderProdName, orderItems); + event.getOrders().add(order); + // 插入订单结算表 + OrderSettlement orderSettlement = new OrderSettlement(); + orderSettlement.setUserId(userId); + orderSettlement.setIsClearing(0); + orderSettlement.setCreateTime(now); + orderSettlement.setOrderNumber(orderNumber); + orderSettlement.setPayAmount(order.getActualTotal()); + orderSettlement.setPayStatus(0); + orderSettlement.setVersion(0); + orderSettlementMapper.insert(orderSettlement); + } + + private com.yami.shop.bean.model.Order getOrder(Date now, String userId, Long addrOrderId, ShopCartOrderDto shopCartOrderDto, String orderNumber, Long shopId, StringBuilder orderProdName, List orderItems) { + com.yami.shop.bean.model.Order order = new com.yami.shop.bean.model.Order(); + + order.setShopId(shopId); + order.setOrderNumber(orderNumber); + order.setProdName(orderProdName.toString()); // 订单商品名称 + order.setUserId(userId); // 用户ID + order.setTotal(shopCartOrderDto.getTotal()); // 商品总额 + order.setActualTotal(shopCartOrderDto.getActualTotal()); // 实际总额 + order.setStatus(OrderStatus.UNPAY.value()); // 订单状态为未支付 + order.setUpdateTime(now); + order.setCreateTime(now); + order.setIsPayed(0); + order.setDeleteStatus(0); + order.setProductNums(shopCartOrderDto.getTotalCount()); + order.setAddrOrderId(addrOrderId); + order.setReduceAmount(Arith.sub(Arith.add(shopCartOrderDto.getTotal(), shopCartOrderDto.getTransfee()), shopCartOrderDto.getActualTotal())); + order.setFreightAmount(shopCartOrderDto.getTransfee()); + order.setRemarks(shopCartOrderDto.getRemarks()); + + order.setOrderItems(orderItems); + return order; + } + + private OrderItem getOrderItem(Date now, String userId, String orderNumber, Long shopId, StringBuilder orderProdName, ShopCartItemDto shopCartItem, Sku sku, Product product) { + OrderItem orderItem = new OrderItem(); + orderItem.setShopId(shopId); + orderItem.setOrderNumber(orderNumber); + orderItem.setProdId(sku.getProdId()); + orderItem.setSkuId(sku.getSkuId()); + orderItem.setSkuName(sku.getSkuName()); + orderItem.setProdCount(shopCartItem.getProdCount()); + orderItem.setProdName(sku.getProdName()); + orderItem.setPic(StrUtil.isBlank(sku.getPic()) ? product.getPic() : sku.getPic()); + orderItem.setPrice(shopCartItem.getPrice()); + orderItem.setUserId(userId); + orderItem.setProductTotalAmount(shopCartItem.getProductTotalAmount()); + orderItem.setRecTime(now); + orderItem.setCommSts(0); + orderItem.setBasketDate(shopCartItem.getBasketDate()); + orderProdName.append(orderItem.getProdName()).append(","); + //推广员卡号 + orderItem.setDistributionCardNo(shopCartItem.getDistributionCardNo()); + return orderItem; + } + + @SuppressWarnings({"Duplicates"}) + private Product checkAndGetProd(Long prodId, ShopCartItemDto shopCartItem, Map prodStocksMap) { + Product product = productService.getProductByProdId(prodId); + if (product == null) { + throw new YamiShopBindException("购物车包含无法识别的商品"); + } + + if (product.getStatus() != 1) { + throw new YamiShopBindException("商品[" + product.getProdName() + "]已下架"); + } + + // 商品需要改变的库存 + Product mapProduct = prodStocksMap.get(prodId); + + if (mapProduct == null) { + mapProduct = new Product(); + mapProduct.setTotalStocks(0); + mapProduct.setProdId(prodId); + mapProduct.setProdName(product.getProdName()); + } + + if (product.getTotalStocks() != -1) { + mapProduct.setTotalStocks(mapProduct.getTotalStocks() + shopCartItem.getProdCount()); + prodStocksMap.put(product.getProdId(), mapProduct); + } + + // -1为无限库存 + if (product.getTotalStocks() != -1 && mapProduct.getTotalStocks() > product.getTotalStocks()) { + throw new YamiShopBindException("商品:[" + product.getProdName() + "]库存不足"); + } + + return product; + } + + @SuppressWarnings({"Duplicates"}) + private Sku checkAndGetSku(Long skuId, ShopCartItemDto shopCartItem, Map skuStocksMap) { + // 获取sku信息 + Sku sku = skuService.getSkuBySkuId(skuId); + if (sku == null) { + throw new YamiShopBindException("购物车包含无法识别的商品"); + } + + if (sku.getStatus() != 1) { + throw new YamiShopBindException("商品[" + sku.getProdName() + "]已下架"); + } + // -1为无限库存 + if (sku.getStocks() != -1 && shopCartItem.getProdCount() > sku.getStocks()) { + throw new YamiShopBindException("商品:[" + sku.getProdName() + "]库存不足"); + } + + if (sku.getStocks() != -1) { + Sku mapSku = new Sku(); + mapSku.setProdId(sku.getProdId()); + // 这里的库存是改变的库存 + mapSku.setStocks(shopCartItem.getProdCount()); + mapSku.setSkuId(sku.getSkuId()); + mapSku.setProdName(sku.getProdName()); + skuStocksMap.put(sku.getSkuId(), mapSku); + } + return sku; + } +} -- 2.34.1 From cda43bd9b436e0f6ff4addf5b1cb86524e51b8e1 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 11:18:23 +0800 Subject: [PATCH 089/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/TransportController.java | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/controller/TransportController.java diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/TransportController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/TransportController.java new file mode 100644 index 0000000..1fcfc5d --- /dev/null +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/TransportController.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.admin.controller; // 定义类所在的包 + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; // 引入MyBatis-Plus的条件查询包装器 +import com.baomidou.mybatisplus.core.metadata.IPage; // 引入MyBatis-Plus的分页接口 +import com.yami.shop.bean.model.Transport; // 引入运费模板模型类 +import com.yami.shop.common.util.PageParam; // 引入分页参数工具类 +import com.yami.shop.security.admin.util.SecurityUtils; // 引入安全工具类 +import com.yami.shop.service.TransportService; // 引入运费模板服务类 +import org.apache.commons.lang3.StringUtils; // 引入Apache Commons的StringUtils工具类 +import org.springframework.beans.factory.annotation.Autowired; // 引入Spring的@Autowired注解 +import com.yami.shop.common.response.ServerResponseEntity; // 引入服务器响应实体类 +import org.springframework.security.access.prepost.PreAuthorize; // 引入Spring Security的PreAuthorize注解 +import org.springframework.web.bind.annotation.*; + +import java.util.Date; // 引入Java的Date类 +import java.util.List; // 引入Java的List接口 + +/** + * 运费模板管理控制器 + * 该类包含分页获取、获取详细信息、保存、修改和删除运费模板的方法。 + * @作者 lgh on 2018/11/16. + */ +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@RequestMapping("/shop/transport") // 定义请求路径的根地址为/shop/transport +public class TransportController { + + @Autowired + private TransportService transportService; // 自动注入运费模板服务类 + + /** + * 分页获取运费模板 + * @param transport 运费模板查询条件 + * @param page 分页参数 + * @return 服务器响应实体,包含分页后的运费模板信息 + */ + @GetMapping("/page") + @PreAuthorize("@pms.hasPermission('shop:transport:page')") // 权限检查 + public ServerResponseEntity> page(Transport transport, PageParam page) { + Long shopId = SecurityUtils.getSysUser().getShopId(); + IPage transports = transportService.page(page, + new LambdaQueryWrapper() + .eq(Transport::getShopId, shopId) + .like(StringUtils.isNotBlank(transport.getTransName()), Transport::getTransName, transport.getTransName())); + return ServerResponseEntity.success(transports); + } + + /** + * 获取运费模板详细信息 + * @param id 运费模板ID + * @return 服务器响应实体,包含运费模板的详细信息 + */ + @GetMapping("/info/{id}") + @PreAuthorize("@pms.hasPermission('shop:transport:info')") // 权限检查 + public ServerResponseEntity info(@PathVariable("id") Long id) { + Transport transport = transportService.getTransportAndAllItems(id); + return ServerResponseEntity.success(transport); + } + + /** + * 保存运费模板 + * @param transport 运费模板信息 + * @return 服务器响应实体 + */ + @PostMapping + @PreAuthorize("@pms.hasPermission('shop:transport:save')") // 权限检查 + public ServerResponseEntity save(@RequestBody Transport transport) { + Long shopId = SecurityUtils.getSysUser().getShopId(); + transport.setShopId(shopId); + Date createTime = new Date(); + transport.setCreateTime(createTime); + transportService.insertTransportAndTransfee(transport); + return ServerResponseEntity.success(); + } + + /** + * 修改运费模板 + * @param transport 运费模板信息 + * @return 服务器响应实体 + */ + @PutMapping + @PreAuthorize("@pms.hasPermission('shop:transport:update')") // 权限检查 + public ServerResponseEntity update(@RequestBody Transport transport) { + transportService.updateTransportAndTransfee(transport); + return ServerResponseEntity.success(); + } + + /** + * 删除运费模板 + * @param ids 运费模板ID数组 + * @return 服务器响应实体 + */ + @DeleteMapping + @PreAuthorize("@pms.hasPermission('shop:transport:delete')") // 权限检查 + public ServerResponseEntity delete(@RequestBody Long[] ids) { + transportService.deleteTransportAndTransfeeAndTranscity(ids); + // 删除运费模板的缓存 + for (Long id : ids) { + transportService.removeTransportAndAllItemsCache(id); + } + return ServerResponseEntity.success(); + } + + /** + * 获取运费模板列表 + * @return 服务器响应实体,包含运费模板列表信息 + */ + @GetMapping("/list") + public ServerResponseEntity> list() { + Long shopId = SecurityUtils.getSysUser().getShopId(); + List list = transportService.list(new LambdaQueryWrapper().eq(Transport::getShopId, shopId)); + return ServerResponseEntity.success(list); + } +} \ No newline at end of file -- 2.34.1 From 29790ef1e4c566f54599e4bdb9b99c18191323d8 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 11:22:25 +0800 Subject: [PATCH 090/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/UserAddrController.java | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/controller/UserAddrController.java diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/UserAddrController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/UserAddrController.java new file mode 100644 index 0000000..8e9b8ba --- /dev/null +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/UserAddrController.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.admin.controller; // 定义类所在的包 + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; // 引入MyBatis-Plus的条件查询包装器 +import com.baomidou.mybatisplus.core.metadata.IPage; // 引入MyBatis-Plus的分页接口 +import com.yami.shop.common.util.PageParam; // 引入分页参数工具类 +import com.yami.shop.bean.model.UserAddr; // 引入用户地址模型类 +import com.yami.shop.common.annotation.SysLog; // 引入自定义日志注解 +import com.yami.shop.service.UserAddrService; // 引入用户地址服务类 +import lombok.AllArgsConstructor; // 引入Lombok的@AllArgsConstructor注解 +import com.yami.shop.common.response.ServerResponseEntity; // 引入服务器响应实体类 +import org.springframework.security.access.prepost.PreAuthorize; // 引入Spring Security的PreAuthorize注解 +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.Valid; // 引入Jakarta Validation的Valid注解 + +/** + * 用户地址管理控制器 + * 该类包含分页获取、获取详细信息、保存、修改和删除用户地址的方法。 + * @作者 hzm + * @日期 2019-04-15 10:49:40 + */ +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@AllArgsConstructor // 使用Lombok注解生成全参构造函数 +@RequestMapping("/user/addr") // 定义请求路径的根地址为/user/addr +public class UserAddrController { + + private final UserAddrService userAddrService; // 自动注入用户地址服务类 + + /** + * 分页获取用户地址 + * @param page 分页对象 + * @param userAddr 用户地址查询条件 + * @return 服务器响应实体,包含分页后的用户地址信息 + */ + @GetMapping("/page") + public ServerResponseEntity> getUserAddrPage(PageParam page, UserAddr userAddr) { + return ServerResponseEntity.success(userAddrService.page(page, new LambdaQueryWrapper())); // 返回分页结果 + } + + /** + * 通过ID查询用户地址 + * @param addrId 用户地址ID + * @return 服务器响应实体,包含用户地址的详细信息 + */ + @GetMapping("/info/{addrId}") + public ServerResponseEntity getById(@PathVariable("addrId") Long addrId) { + return ServerResponseEntity.success(userAddrService.getById(addrId)); // 返回用户地址信息 + } + + /** + * 新增用户地址 + * @param userAddr 用户地址信息 + * @return 服务器响应实体,包含新增结果 + */ + @SysLog("新增用户地址管理") // 自定义日志注解 + @PostMapping + @PreAuthorize("@pms.hasPermission('user:addr:save')") // 权限检查 + public ServerResponseEntity save(@RequestBody @Valid UserAddr userAddr) { + return ServerResponseEntity.success(userAddrService.save(userAddr)); // 保存用户地址并返回结果 + } + + /** + * 修改用户地址 + * @param userAddr 用户地址信息 + * @return 服务器响应实体,包含修改结果 + */ + @SysLog("修改用户地址管理") // 自定义日志注解 + @PutMapping + @PreAuthorize("@pms.hasPermission('user:addr:update')") // 权限检查 + public ServerResponseEntity updateById(@RequestBody @Valid UserAddr userAddr) { + return ServerResponseEntity.success(userAddrService.updateById(userAddr)); // 修改用户地址并返回结果 + } + + /** + * 通过ID删除用户地址 + * @param addrId 用户地址ID + * @return 服务器响应实体,包含删除结果 + */ + @SysLog("删除用户地址管理") // 自定义日志注解 + @DeleteMapping("/{addrId}") + @PreAuthorize("@pms.hasPermission('user:addr:delete')") // 权限检查 + public ServerResponseEntity removeById(@PathVariable Long addrId) { + return ServerResponseEntity.success(userAddrService.removeById(addrId)); // 删除用户地址并返回结果 + } +} -- 2.34.1 From c78536267de19c5b33d9f383af4bc3dbfe0177d3 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 12:52:51 +0800 Subject: [PATCH 091/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/CategoryController.java | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 yami-shop-admin/src/main/java/com/yami/shop/admin/controller/CategoryController.java diff --git a/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/CategoryController.java b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/CategoryController.java new file mode 100644 index 0000000..f42c71e --- /dev/null +++ b/yami-shop-admin/src/main/java/com/yami/shop/admin/controller/CategoryController.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.admin.controller; // 定义类所在的包 + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; // 引入MyBatis-Plus的条件查询包装器 +import com.yami.shop.bean.model.Category; // 引入商品分类模型类 +import com.yami.shop.common.annotation.SysLog; // 引入自定义日志注解 +import com.yami.shop.common.exception.YamiShopBindException; // 引入自定义异常类 +import com.yami.shop.security.admin.util.SecurityUtils; // 引入安全工具类 +import com.yami.shop.service.CategoryService; // 引入商品分类服务类 +import org.springframework.beans.factory.annotation.Autowired; // 引入Spring的@Autowired注解 +import com.yami.shop.common.response.ServerResponseEntity; // 引入服务器响应实体类 +import org.springframework.security.access.prepost.PreAuthorize; // 引入Spring Security的PreAuthorize注解 +import org.springframework.web.bind.annotation.*; + +import java.util.Date; // 引入Java的Date类 +import java.util.List; // 引入Java的List接口 +import java.util.Objects; // 引入Java的Objects工具类 + +/** + * 商品分类管理控制器 + * 该类包含分页获取、获取详细信息、保存、修改和删除商品分类的方法。 + * @作者 lgh + */ +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@RequestMapping("/prod/category") // 定义请求路径的根地址为/prod/category +public class CategoryController { + + @Autowired + private CategoryService categoryService; // 自动注入商品分类服务类 + + /** + * 获取菜单页面的表 + * @return 服务器响应实体,包含商品分类列表 + */ + @GetMapping("/table") + @PreAuthorize("@pms.hasPermission('prod:category:page')") // 权限检查 + public ServerResponseEntity> table() { + List categoryMenuList = categoryService.tableCategory(SecurityUtils.getSysUser().getShopId()); + return ServerResponseEntity.success(categoryMenuList); + } + + /** + * 获取分类信息 + * @param categoryId 商品分类ID + * @return 服务器响应实体,包含商品分类的详细信息 + */ + @GetMapping("/info/{categoryId}") + public ServerResponseEntity info(@PathVariable("categoryId") Long categoryId) { + Category category = categoryService.getById(categoryId); + return ServerResponseEntity.success(category); + } + + /** + * 保存分类 + * @param category 商品分类信息 + * @return 服务器响应实体 + */ + @SysLog("保存分类") // 自定义日志注解 + @PostMapping + @PreAuthorize("@pms.hasPermission('prod:category:save')") // 权限检查 + public ServerResponseEntity save(@RequestBody Category category) { + category.setShopId(SecurityUtils.getSysUser().getShopId()); + category.setRecTime(new Date()); + Category categoryName = categoryService.getOne(new LambdaQueryWrapper().eq(Category::getCategoryName, category.getCategoryName()) + .eq(Category::getShopId, category.getShopId())); + if (Objects.nonNull(categoryName)) { + throw new YamiShopBindException("类目名称已存在!"); + } + categoryService.saveCategory(category); + return ServerResponseEntity.success(); + } + + /** + * 更新分类 + * @param category 商品分类信息 + * @return 服务器响应实体 + */ + @SysLog("更新分类") // 自定义日志注解 + @PutMapping + @PreAuthorize("@pms.hasPermission('prod:category:update')") // 权限检查 + public ServerResponseEntity update(@RequestBody Category category) { + category.setShopId(SecurityUtils.getSysUser().getShopId()); + if (Objects.equals(category.getParentId(), category.getCategoryId())) { + return ServerResponseEntity.showFailMsg("分类的上级不能是自己本身"); + } + Category categoryName = categoryService.getOne(new LambdaQueryWrapper().eq(Category::getCategoryName, category.getCategoryName()) + .eq(Category::getShopId, category.getShopId()).ne(Category::getCategoryId, category.getCategoryId())); + if (categoryName != null) { + throw new YamiShopBindException("类目名称已存在!"); + } + Category categoryDb = categoryService.getById(category.getCategoryId()); + // 如果从下线改成正常,则需要判断上级的状态 + if (Objects.equals(categoryDb.getStatus(), 0) && Objects.equals(category.getStatus(), 1) && !Objects.equals(category.getParentId(), 0L)) { + Category parentCategory = categoryService.getOne(new LambdaQueryWrapper().eq(Category::getCategoryId, category.getParentId())); + if (Objects.isNull(parentCategory) || Objects.equals(parentCategory.getStatus(), 0)) { + // 修改失败,上级分类不存在或者不为正常状态 + throw new YamiShopBindException("修改失败,上级分类不存在或者不为正常状态"); + } + } + categoryService.updateCategory(category); + return ServerResponseEntity.success(); + } + + /** + * 删除分类 + * @param categoryId 商品分类ID + * @return 服务器响应实体 + */ + @SysLog("删除分类") // 自定义日志注解 + @DeleteMapping("/{categoryId}") + @PreAuthorize("@pms.hasPermission('prod:category:delete')") // 权限检查 + public ServerResponseEntity delete(@PathVariable("categoryId") Long categoryId) { + if (categoryService.count(new LambdaQueryWrapper().eq(Category::getParentId, categoryId)) > 0) { + return ServerResponseEntity.showFailMsg("请删除子分类,再删除该分类"); + } + categoryService.deleteCategory(categoryId); + return ServerResponseEntity.success(); + } + + /** + * 获取所有的分类 + * @return 服务器响应实体,包含商品分类列表 + */ + @GetMapping("/listCategory") + public ServerResponseEntity> listCategory() { + return ServerResponseEntity.success(categoryService.list(new LambdaQueryWrapper() + .le(Category::getGrade, 2) + .eq(Category::getShopId, SecurityUtils.getSysUser().getShopId()) + .orderByAsc(Category::getSeq))); + } + + /** + * 获取所有的产品分类 + * @return 服务器响应实体,包含产品分类列表 + */ + @GetMapping("/listProdCategory") + public ServerResponseEntity> listProdCategory() { + List categories = categoryService.treeSelect(SecurityUtils.getSysUser().getShopId(), 2); + return ServerResponseEntity.success(categories); + } +} -- 2.34.1 From 756ccbd6b316a45df5ccd5c899aaa479bbb57b61 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 12:55:10 +0800 Subject: [PATCH 092/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/CategoryController.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 yami-shop-api/src/main/java/com/yami/shop/api/controller/CategoryController.java diff --git a/yami-shop-api/src/main/java/com/yami/shop/api/controller/CategoryController.java b/yami-shop-api/src/main/java/com/yami/shop/api/controller/CategoryController.java new file mode 100644 index 0000000..acacb22 --- /dev/null +++ b/yami-shop-api/src/main/java/com/yami/shop/api/controller/CategoryController.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.api.controller; // 定义类所在的包 + +import java.util.List; // 引入Java的List接口 + +import org.springframework.beans.factory.annotation.Autowired; // 引入Spring的@Autowired注解 +import com.yami.shop.common.response.ServerResponseEntity; // 引入服务器响应实体类 +import org.springframework.web.bind.annotation.*; // 引入Spring Web的注解 + +import com.yami.shop.bean.app.dto.CategoryDto; // 引入分类DTO类 +import com.yami.shop.bean.model.Category; // 引入分类模型类 +import com.yami.shop.service.CategoryService; // 引入分类服务类 + +import io.swagger.v3.oas.annotations.tags.Tag; // 引入Swagger的Tag注解 +import io.swagger.v3.oas.annotations.Parameter; // 引入Swagger的Parameter注解 +import io.swagger.v3.oas.annotations.Operation; // 引入Swagger的Operation注解 +import cn.hutool.core.bean.BeanUtil; // 引入Hutool工具类库中的BeanUtil工具类 + +/** + * CategoryController类,用于管理商品分类。 + * 该类包含获取分类信息列表的方法。 + * @作者 lanhai + */ +@RestController // 标注这是一个控制器类,并且其返回结果直接写入HTTP响应体中,而不是视图名称 +@RequestMapping("/category") // 定义请求路径的根地址为/category +@Tag(name = "分类接口") // 给API文档添加标签,描述这个控制器的功能 +public class CategoryController { + + @Autowired + private CategoryService categoryService; // 自动注入分类服务类 + + /** + * 分类信息列表接口 + * @param parentId 分类ID,默认值为0 + * @return 服务器响应实体,包含分类信息列表 + */ + @GetMapping("/categoryInfo") + @Operation(summary = "分类信息列表", description = "获取所有的产品分类信息,顶级分类的parentId为0,默认为顶级分类") + @Parameter(name = "parentId", description = "分类ID", required = false) + public ServerResponseEntity> categoryInfo(@RequestParam(value = "parentId", defaultValue = "0") Long parentId) { + List categories = categoryService.listByParentId(parentId); + List categoryDtos = BeanUtil.copyToList(categories, CategoryDto.class); + return ServerResponseEntity.success(categoryDtos); + } +} \ No newline at end of file -- 2.34.1 From f4b1f98cc4e5ddc47f69e773b69924d71f6f9cde Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 19:07:02 +0800 Subject: [PATCH 093/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/DefaultExceptionHandlerConfig.java | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/config/DefaultExceptionHandlerConfig.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/config/DefaultExceptionHandlerConfig.java b/yami-shop-common/src/main/java/com/yami/shop/common/config/DefaultExceptionHandlerConfig.java new file mode 100644 index 0000000..3dd3af1 --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/config/DefaultExceptionHandlerConfig.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.common.config; + +// 导入相关的自定义异常类、响应相关的枚举、响应实体类等 +import com.yami.shop.common.exception.YamiShopBindException; +import com.yami.shop.common.response.ResponseEnum; +import com.yami.shop.common.response.ServerResponseEntity; +// 导入日志相关的Lombok注解,用于简化日志记录代码 +import lombok.extern.slf4j.Slf4j; +// 导入Spring的HTTP状态码相关枚举类 +import org.springframework.http.HttpStatus; +// 导入Spring用于构建HTTP响应实体的类 +import org.springframework.http.ResponseEntity; +// 导入Spring用于标记控制器类的注解(此处结合异常处理相关特性使用) +import org.springframework.stereotype.Controller; +// 导入Spring在数据校验场景下的异常类以及表示字段错误的类 +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +// 导入Spring用于定义异常处理方法的注解以及标记全局异常处理类的注解 +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +// 导入Spring用于处理资源未找到异常的类 +import org.springframework.web.servlet.resource.NoResourceFoundException; + +import java.util.ArrayList; +import java.util.List; + +/** + * 自定义错误处理器,用于统一处理项目中的各类异常情况, + * 并将异常信息按照一定格式封装到响应实体中返回给客户端。 + * @author LGH + */ +@Slf4j +@Controller +@RestControllerAdvice +public class DefaultExceptionHandlerConfig { + + /** + * 处理MethodArgumentNotValidException和BindException这两种参数校验相关异常的方法。 + * 当在Spring的参数校验过程中出现这两种异常时,会进入此方法进行处理, + * 将异常中的字段错误信息提取出来,封装到响应实体中返回给客户端。 + * + * @param e 捕获到的异常对象,可能是MethodArgumentNotValidException或者BindException类型 + * @return ResponseEntity>> 包含了处理后的响应实体的HTTP响应实体, + * 响应实体中包含了参数校验失败的具体字段错误信息列表(如果有)以及对应的响应状态等信息。 + */ + @ExceptionHandler({ MethodArgumentNotValidException.class, BindException.class }) + public ResponseEntity>> methodArgumentNotValidExceptionHandler(Exception e) { + // 记录异常信息,方便后续查看出现异常的具体情况,用于调试和问题排查 + log.error("methodArgumentNotValidExceptionHandler", e); + List fieldErrors = null; + // 判断异常类型是否是MethodArgumentNotValidException,如果是则获取对应的字段错误列表 + if (e instanceof MethodArgumentNotValidException) { + fieldErrors = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors(); + } + // 判断异常类型是否是BindException,如果是则获取对应的字段错误列表 + if (e instanceof BindException) { + fieldErrors = ((BindException) e).getBindingResult().getFieldErrors(); + } + // 如果没有获取到字段错误列表(可能出现不符合预期的异常情况等) + if (fieldErrors == null) { + // 返回一个包含特定失败响应枚举(表示参数校验不通过)的响应实体,没有具体字段错误信息 + return ResponseEntity.status(HttpStatus.OK) + .body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID)); + } + + // 用于存放每个字段的错误信息字符串(格式为:字段名:默认错误消息) + List defaultMessages = new ArrayList<>(fieldErrors.size()); + // 遍历字段错误列表,拼接每个字段的错误信息字符串并添加到defaultMessages列表中 + for (FieldError fieldError : fieldErrors) { + defaultMessages.add(fieldError.getField() + ":" + fieldError.getDefaultMessage()); + } + // 返回一个包含具体字段错误信息列表的失败响应实体,使用参数校验不通过的响应枚举标识 + return ResponseEntity.status(HttpStatus.OK) + .body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID, defaultMessages)); + } + + /** + * 处理YamiShopBindException这种自定义异常的方法。 + * 当项目中抛出YamiShopBindException异常时,会进入此方法进行处理, + * 根据异常中封装的响应实体(如果有)或者异常本身的错误码和错误消息构建响应实体返回给客户端。 + * + * @param e 捕获到的YamiShopBindException异常对象 + * @return ResponseEntity> 包含了处理后的响应实体的HTTP响应实体, + * 响应实体中包含了对应业务逻辑异常的相关信息以及响应状态等。 + */ + @ExceptionHandler(YamiShopBindException.class) + public ResponseEntity> unauthorizedExceptionHandler(YamiShopBindException e){ + // 记录异常信息,方便后续排查问题,了解异常出现的具体情况 + log.error("mall4jExceptionHandler", e); + + ServerResponseEntity serverResponseEntity = e.getServerResponseEntity(); + // 如果异常中已经封装好了响应实体(在抛出异常时可能已经构建好了特定的响应内容) + if (serverResponseEntity!=null) { + // 直接将其作为响应内容返回给客户端,设置状态码为OK + return ResponseEntity.status(HttpStatus.OK).body(serverResponseEntity); + } + // 失败返回消息,状态码固定为直接显示消息的状态码,根据异常的错误码和错误消息构建失败响应实体并返回 + return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(e.getCode(),e.getMessage())); + } + + /** + * 兜底的异常处理方法,处理所有未被其他异常处理方法处理的Exception类型异常。 + * 对于不同类型的通用异常进行相应处理,如资源未找到异常返回特定消息的响应实体, + * 其他异常则返回默认的异常响应实体告知客户端出现了未预期的异常情况。 + * + * @param e 捕获到的异常对象,是Exception类型 + * @return ResponseEntity> 包含了处理后的响应实体的HTTP响应实体, + * 响应实体中包含了对应异常的相关信息以及响应状态等。 + */ + @ExceptionHandler(Exception.class) + public ResponseEntity> exceptionHandler(Exception e){ + // 判断异常是否是资源未找到异常类型 + if (e instanceof NoResourceFoundException) { + // 如果是,则返回一个包含异常消息的失败响应实体,告知客户端资源未找到相关错误信息,状态码设为OK + return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.showFailMsg(e.getMessage())); + } + // 记录异常信息,方便后续排查问题,知道出现异常的具体情况 + log.error("exceptionHandler", e); + // 返回一个使用默认的异常响应枚举构建的失败响应实体,告知客户端出现了未预期的通用异常情况 + return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(ResponseEnum.EXCEPTION)); + } +} \ No newline at end of file -- 2.34.1 From 488914ffc5ff60b5e13fe14ecb98fcd690500ebc Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 19:09:18 +0800 Subject: [PATCH 094/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/common/config/FileUploadConfig.java | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/config/FileUploadConfig.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/config/FileUploadConfig.java b/yami-shop-common/src/main/java/com/yami/shop/common/config/FileUploadConfig.java new file mode 100644 index 0000000..d680318 --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/config/FileUploadConfig.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.common.config; + +// 导入相关的自定义类,可能用于存储图片上传相关的配置、枚举等信息 +import com.yami.shop.common.bean.ImgUpload; +import com.yami.shop.common.enums.QiniuZone; +// 导入Spring的注解相关类,用于实现依赖注入和配置相关功能 +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +// 导入七牛云相关的类,用于与七牛云存储服务进行交互,涉及区域、上传管理、认证等功能 +import com.qiniu.common.Zone; +import com.qiniu.storage.BucketManager; +import com.qiniu.storage.UploadManager; +import com.qiniu.util.Auth; +import com.yami.shop.common.bean.Qiniu; + +import java.util.Objects; + +/** + * 文件上传配置类,主要用于整合七牛云存储相关的配置与实例创建, + * 通过依赖注入获取配置信息,并根据配置来构建七牛云存储操作所需的各种实例, + * 如存储配置、上传管理实例、认证实例以及空间管理实例等,方便在项目中进行文件上传等操作。 + * @author lgh + */ +@Configuration +public class FileUploadConfig { + + // 通过Spring的依赖注入,获取Qiniu类型的配置对象,该对象应该包含了七牛云相关的配置信息,如密钥、机房区域等 + @Autowired + private Qiniu qiniu; + + /** + * 根据配置文件选择机房,创建七牛云存储的配置实例(com.qiniu.storage.Configuration)。 + * 会根据注入的Qiniu对象中的机房区域配置(QiniuZone枚举类型)来确定对应的七牛云机房区域(Zone), + * 并以此构建七牛云存储配置实例,该实例后续会被其他七牛云相关操作的实例所使用。 + * + * @return com.qiniu.storage.Configuration 返回构建好的七牛云存储配置实例,包含了指定的机房区域信息。 + */ + @Bean + public com.qiniu.storage.Configuration qiniuConfig() { + Zone zone = null; + // 判断配置中的机房区域是否为华北区,如果是则设置对应的七牛云机房区域为华北区(Zone.huabei()) + if (Objects.equals(qiniu.getZone(), QiniuZone.HUA_BEI)) { + zone = Zone.huabei(); + } + // 判断是否为华东区,若是则设置对应的七牛云机房区域为华东区(Zone.huadong()) + else if (Objects.equals(qiniu.getZone(), QiniuZone.HUA_DONG)) { + zone = Zone.huadong(); + } + // 判断是否为华南区,若是则设置对应的七牛云机房区域为华南区(Zone.huanan()) + else if (Objects.equals(qiniu.getZone(), QiniuZone.HUA_NAN)) { + zone = Zone.huanan(); + } + // 判断是否为北美区,若是则设置对应的七牛云机房区域为北美区(Zone.beimei()) + else if (Objects.equals(qiniu.getZone(), QiniuZone.BEI_MEI)) { + zone = Zone.beimei(); + } + // 判断是否为新加坡区,若是则设置对应的七牛云机房区域为新加坡区(Zone.xinjiapo()) + else if (Objects.equals(qiniu.getZone(), QiniuZone.XIN_JIA_PO)) { + zone = Zone.xinjiapo(); + } + // 使用确定好的机房区域(zone)构建七牛云存储配置实例并返回 + return new com.qiniu.storage.Configuration(zone); + } + + /** + * 构建一个七牛上传工具实例(UploadManager), + * 该实例依赖于前面创建的七牛云存储配置实例(qiniuConfig方法创建的com.qiniu.storage.Configuration实例), + * 用于在项目中执行文件上传到七牛云存储的操作。 + * + * @return UploadManager 返回构建好的七牛上传工具实例,可用于文件上传操作。 + */ + @Bean + public UploadManager uploadManager() { + return new UploadManager(qiniuConfig()); + } + + /** + * 构建七牛云认证信息实例(Auth), + * 通过获取注入的Qiniu对象中的访问密钥(accessKey)和秘密密钥(secretKey)来创建认证实例, + * 该认证实例在后续与七牛云服务进行交互时(如上传、管理空间等操作)用于验证身份,确保操作的合法性。 + * + * @return Auth 返回构建好的七牛云认证信息实例,包含了访问密钥和秘密密钥信息用于认证。 + */ + @Bean + public Auth auth() { + return Auth.create(qiniu.getAccessKey(), qiniu.getSecretKey()); + } + + /** + * 构建七牛空间管理实例(BucketManager), + * 该实例依赖于前面创建的认证信息实例(auth方法创建的Auth实例)和七牛云存储配置实例(qiniuConfig方法创建的实例), + * 用于在七牛云存储中进行空间相关的管理操作,比如查看、删除、移动文件等操作。 + * + * @return BucketManager 返回构建好的七牛空间管理实例,可用于七牛云存储空间的管理操作。 + */ + @Bean + public BucketManager bucketManager() { + return new BucketManager(auth(), qiniuConfig()); + } +} \ No newline at end of file -- 2.34.1 From ce088810be0c5ed14e57f2cf4bd2d14825954315 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 19:17:36 +0800 Subject: [PATCH 095/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/common/util/HttpContextUtils.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/util/HttpContextUtils.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/util/HttpContextUtils.java b/yami-shop-common/src/main/java/com/yami/shop/common/util/HttpContextUtils.java new file mode 100644 index 0000000..90af9e7 --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/util/HttpContextUtils.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.common.util; + +// 导入Spring相关的类,用于获取请求相关的上下文信息,基于请求上下文来获取HttpServletRequest对象 +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +// 导入Servlet相关的类,用于操作HTTP请求,这里主要是HttpServletRequest,它包含了请求相关的各种信息 +import jakarta.servlet.http.HttpServletRequest; + +/** + * HttpContextUtils工具类,主要提供了一些与HTTP请求上下文相关的便捷获取方法, + * 通过Spring的请求上下文机制来获取HttpServletRequest对象,并基于该对象进一步获取如请求域名、请求来源等相关信息, + * 方便在项目的其他地方使用这些信息进行业务逻辑处理,例如跨域处理、链接构建等场景。 + * + * @author lanhai + */ +public class HttpContextUtils { + + /** + * 获取当前线程绑定的HttpServletRequest对象。 + * 通过Spring的RequestContextHolder从请求上下文中获取ServletRequestAttributes对象, + * 再从中提取出HttpServletRequest对象。该方法是后续获取其他请求相关信息的基础。 + * + * @return HttpServletRequest 返回当前请求对应的HttpServletRequest对象, + * 如果不存在绑定的请求上下文则会抛出异常(运行时异常),调用者需要进行相应处理。 + */ + public static HttpServletRequest getHttpServletRequest() { + return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + } + + /** + * 获取当前请求的域名信息。 + * 首先调用getHttpServletRequest方法获取当前的HttpServletRequest对象, + * 然后通过该对象的getRequestURL方法获取完整的请求URL(包含协议、域名、端口、路径等), + * 再利用getRequestURI方法获取请求的路径部分,通过删除URL中路径及后面的部分来得到域名相关信息(包含协议、域名、端口)。 + * + * @return String 返回当前请求的域名信息(格式类似:http://example.com:8080,具体取决于实际请求情况)。 + */ + public static String getDomain() { + HttpServletRequest request = getHttpServletRequest(); + StringBuffer url = request.getRequestURL(); + return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString(); + } + + /** + * 获取当前请求的来源(Origin)信息,通常用于处理跨域相关场景。 + * 先获取当前的HttpServletRequest对象,然后通过该对象的getHeader方法获取名为"Origin"的请求头信息, + * 该请求头一般由浏览器自动添加,标识了请求的来源域名等信息,在跨域请求等场景中有重要作用。 + * + * @return String 返回当前请求的Origin信息(格式类似:http://example.com,如果有Origin请求头的话,否则返回null等情况)。 + */ + public static String getOrigin() { + HttpServletRequest request = getHttpServletRequest(); + return request.getHeader("Origin"); + } +} \ No newline at end of file -- 2.34.1 From 2e37262a8bf9ed1a7cadf95b355b19e827834876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=BB=BA=E5=86=9B?= <2300486727@qq.com> Date: Tue, 17 Dec 2024 19:20:34 +0800 Subject: [PATCH 096/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yami/shop/bean/model/Order.java | 217 ++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 yami-shop-bean/src/main/java/com/yami/shop/bean/model/Order.java diff --git a/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Order.java b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Order.java new file mode 100644 index 0000000..312b99e --- /dev/null +++ b/yami-shop-bean/src/main/java/com/yami/shop/bean/model/Order.java @@ -0,0 +1,217 @@ +package com.yami.shop.bean.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * @author lanhai + * @description Order类用于表示电商系统中的订单信息实体,它涵盖了从订单创建到完结整个过程涉及的各类关键数据, + * 并且通过相关注解与数据库表进行映射以及方便地生成常用方法,便于在各业务层对订单数据进行操作处理。 + */ +@Data +// 使用@TableName注解指定该实体类对应的数据库表名为"tz_order",用于MyBatis Plus框架建立实体与表的映射关系 +@TableName("tz_order") +public class Order implements Serializable { + private static final long serialVersionUID = 6222259729062826852L; + + /** + * @description 订单ID,是订单在系统中的唯一标识符,使用@TableId注解表明在数据库表"tz_order"中此字段为表的主键。 + */ + @TableId + private Long orderId; + + /** + * @description 店铺id,用于标识该订单所属的店铺,通过此id可以关联查询店铺的其他相关信息,如店铺名称、店铺配置等。 + */ + private Long shopId; + + /** + * @description 产品名称,当订单包含多个产品时,多个产品名称将会以逗号隔开。 + * 这种方式可以简单呈现订单内商品情况,但对于复杂产品信息处理不够灵活,如需详细产品信息可能需关联其他表。 + */ + private String prodName; + + /** + * @description 订购用户ID,唯一标识下此订单的用户,借助该字段可以关联到用户的各种信息,例如用户基本资料、历史订单等, + * 方便从用户角度对订单进行诸如查询、统计等操作。 + */ + private String userId; + + /** + * @description 订购流水号,一般具有唯一性,常用于在业务系统中作为订单的外部标识, + * 在与外部系统对接(如支付回调、物流查询等场景)或者在内部业务流程中通过流水号快速定位和查询具体订单时会用到。 + */ + private String orderNumber; + + /** + * @description 总值,代表订单中商品在未考虑优惠、运费等因素之前的总价值,是统计订单商品价值的基础数据项。 + */ + private Double total; + + /** + * @description 实际总值,是订单经过各种优惠、折扣以及加上运费等调整之后,用户实际需要支付的总金额, + * 反映了订单最终真实的消费金额情况,对于财务统计等业务操作十分重要。 + */ + private Double actualTotal; + + /** + * @description 支付方式,通过约定的整数值来区分不同支付渠道,例如:1 表示微信支付,2 表示支付宝支付。 + * 方便后续根据支付方式进行支付相关的统计、对账以及不同支付方式下对应的业务逻辑处理(如退款逻辑差异等)。 + */ + private Integer payType; + + /** + * @description 订单备注,是一个文本字段,用于用户或者业务操作人员填写关于订单的一些特殊说明或者备注信息, + * 比如对商品的特殊要求、对配送时间的额外说明等,方便在查看订单详情时了解额外情况。 + */ + private String remarks; + + /** + * @description 订单状态,通过定义的整数值来标识订单目前所处的业务流程阶段,具体含义如下: + * - -1:已取消,表示订单已被取消,可能需要进行相关资源释放、数据清理等操作; + * - 0:待付款,对应订单创建后等待用户付款的阶段,可设置定时提醒用户付款等功能; + * - 1:待发货,意味着订单已付款,等待商家发货,此时可通知仓库进行发货操作; + * - 2:待收货,代表商品已发货,等待用户收货确认; + * - 3:已完成,标志着订单整个业务流程全部结束,例如用户确认收货或者系统自动确认收货等情况后所处的状态。 + * 不同状态用于驱动不同的业务流程流转以及相应的业务逻辑处理。 + */ + private Integer status; + + /** + * @description 配送类型,用于记录订单的配送类型相关信息,不过目前代码中未明确具体约定的取值内容, + * 常见的可能如快递、自提等不同的配送分类方式,方便后续根据配送类型来安排具体的物流配送环节以及在前端展示给用户相应的配送信息提示。 + */ + private String dvyType; + + /** + * @description 配送方式ID,关联具体配送方式的唯一标识,可能对应数据库中存储配送方式详细信息(如快递的具体快递公司、自提的具体门店等信息)的表的主键, + * 通过这个字段可以进一步获取详细的配送相关细节内容。 + */ + private Long dvyId; + + /** + * @description 物流单号,当订单进入发货阶段,物流单号用于跟踪物流运输的进度,用户可以通过这个单号在对应的物流查询平台查询包裹的实时位置等信息, + * 同时系统内部也可以通过对接物流接口,利用物流单号来获取物流状态更新到订单信息中,方便用户查看。 + */ + private String dvyFlowId; + + /** + * @description 订单运费,记录该订单产生的运费金额,如果订单有包邮规则或者运费计算逻辑,这个字段会记录最终计算得出的需要用户支付或者商家承担的运费数值, + * 对于统计订单成本、用户实际支付金额等方面是一个重要的数据项。 + */ + private Double freightAmount; + + /** + * @description 用户订单地址Id,关联用户订单地址的唯一标识,通过这个字段可以从数据库中查询到订单对应的收货地址或者发货地址(如果有不同的地址需求场景)的详细信息, + * 比如地址、联系人、联系电话等,确保货物能准确地送达指定地点。 + */ + private Long addrOrderId; + + /** + * @description 订单商品总数,用于统计该订单中包含的商品总数量,便于快速了解订单的规模大小, + * 也可以在一些业务逻辑中,比如根据商品数量判断是否满足某些优惠条件、统计商品销售总量等方面起到作用。 + */ + private Integer productNums; + + /** + * @description 订购时间,使用@DateTimeFormat注解指定日期格式为"yyyy-MM-dd HH:mm:ss",用于记录订单创建的时间点, + * 在业务中可以根据创建时间进行订单的排序、查询某个时间段内的订单等操作,例如查询当天的新订单、统计每月新增订单数量等都依赖于这个时间字段。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * @description 订单更新时间,同样使用@DateTimeFormat注解指定日期格式,记录订单信息最后一次更新的时间, + * 比如订单状态改变、地址修改、备注添加等任何对订单数据有修改操作时,都会更新这个时间字段,方便跟踪订单数据的变更历史以及在一些业务场景中判断数据的时效性等。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** + * @description 付款时间,使用@DateTimeFormat注解指定日期格式,当用户完成订单支付后,会记录支付操作完成的具体时间, + * 这个时间对于统计支付成功率、分析支付时间段的分布等支付相关的业务分析以及后续涉及到退款等业务逻辑判断支付是否已完成等方面都有重要作用。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date payTime; + + /** + * @description 发货时间,使用@DateTimeFormat注解指定日期格式,记录订单商品实际发货的时间, + * 通过这个时间可以计算物流运输时长、统计发货效率等,同时也可以作为判断订单状态是否准确(比如是否应该从待发货变为待收货等状态)的依据之一。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date dvyTime; + + /** + * @description 完成时间,使用@DateTimeFormat注解指定日期格式,表示订单整个业务流程全部结束(例如用户确认收货或者系统自动确认收货等情况)后记录的时间, + * 标志着订单的最终完结,对于统计订单完成率、分析订单平均完成时长等业务指标有帮助。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date finallyTime; + + /** + * @description 取消时间,使用@DateTimeFormat注解指定日期格式,当订单被取消时,会记录取消操作发生的时间, + * 对于后续分析订单取消原因、统计取消订单的时间分布等情况提供数据支持,也有助于确保业务数据的完整性和可追溯性。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date cancelTime; + + /** + * @description 是否已经支付,通过整数值来表示,1表示已经支付过,0表示没有支付过。 + * 在很多业务逻辑中,比如判断是否可以发货、是否可以进行退款等都需要先判断这个支付状态,避免出现业务流程上的错误。 + */ + private Integer isPayed; + + /** + * @description 用户订单删除状态,通过整数值来区分不同的删除程度,具体含义如下: + * - 0:没有删除,表示订单正常存在于系统中,未进行删除操作; + * - 1:回收站,意味着订单已被标记为删除,进入回收站状态,此时可能还可以恢复; + * - 2:永久删除,代表订单已被彻底删除,无法再恢复了。 + * 方便对订单数据的管理和数据存储空间的合理利用,同时也符合一般系统中数据删除的逻辑设计习惯。 + */ + private Integer deleteStatus; + + /** + * @description 退款状态,用于记录订单退款相关的状态,具体含义如下: + * - 0:默认,表示退款相关操作未开始或者初始状态; + * - 1:在处理,代表用户发起退款申请后,退款流程正在进行中,例如处于审核等阶段; + * - 2:处理完成,说明退款操作经过相应流程后已完成,便于跟踪退款业务的进展情况以及在不同阶段进行相应的业务处理和提醒等操作。 + */ + private Integer refundSts; + + /** + * @description 优惠总额,记录该订单享受到的各种优惠(如优惠券、满减活动、折扣等)累计的金额总数, + * 通过这个字段可以清晰地了解到用户在该订单上节省了多少钱,同时对于商家统计营销活动成本、分析优惠策略效果等方面也是重要的数据依据。 + */ + private Double reduceAmount; + + /** + * @description 店铺名称,使用@TableField(exist = false)注解表明此字段并不对应数据库表"tz_order"中的实际列, + * 它可能是在业务逻辑处理或者数据查询展示过程中,通过关联查询等方式获取到店铺名称后,临时存储在这个实体类对象中的一个属性, + * 方便在前端展示或者其他业务场景中直接获取店铺名称信息,而不用再次去查询数据库。 + */ + @TableField(exist = false) + private String shopName; + + /** + * @description 订单商品明细列表,使用@TableField(exist = false)注解说明这个字段不对应数据库表中的列, + * 它一般用于存储订单中包含的具体商品明细信息(每个OrderItem可以表示一个商品项的详细情况,如商品ID、商品数量、商品单价等), + * 通常是在查询订单详情等场景下,通过关联查询或者其他业务逻辑组装好商品明细列表后赋值给这个字段,方便一次性获取订单的完整商品信息。 + */ + @TableField(exist = false) + private List orderItems; + + /** + * @description 用户订单地址信息对象,使用@TableField(exist = false)注解表示该字段不对应数据库表列, + * 它用于存储与该订单关联的用户地址详细信息对象(UserAddrOrder类应该包含地址相关的各种详细内容,如地址、联系人、联系电话等), + * 方便在获取订单信息时能同时获取到对应的完整地址信息,减少额外查询数据库获取地址的操作。 + */ + @TableField(exist = false) + private UserAddrOrder userAddrOrder; +} \ No newline at end of file -- 2.34.1 From 60e2da7b5bca09214b40c3e0908d081ad9f8cbdd Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 19:25:43 +0800 Subject: [PATCH 097/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/common/handler/HttpHandler.java | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/handler/HttpHandler.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/handler/HttpHandler.java b/yami-shop-common/src/main/java/com/yami/shop/common/handler/HttpHandler.java new file mode 100644 index 0000000..e669c94 --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/handler/HttpHandler.java @@ -0,0 +1,131 @@ +package com.yami.shop.common.handler; + +// 导入 Hutool 工具库中处理字符集相关的工具类,用于设置响应的字符编码 +import cn.hutool.core.util.CharsetUtil; +// 导入 Jackson 库中用于将对象转换为 JSON 字符串以及反序列化等操作的核心类 +import com.fasterxml.jackson.databind.ObjectMapper; +// 导入自定义的业务异常类,可能在项目中用于处理特定业务逻辑出错的情况 +import com.yami.shop.common.exception.YamiShopBindException; +// 导入自定义的用于封装服务器响应信息的实体类,包含响应状态码、消息、数据等内容 +import com.yami.shop.common.response.ServerResponseEntity; +// 导入 Slf4j 框架的日志记录相关类,用于创建日志记录器来记录不同情况的日志信息 +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +// 导入 Spring 框架用于实现依赖注入的注解,表明某个属性需要由 Spring 容器进行注入 +import org.springframework.beans.factory.annotation.Autowired; +// 导入 Spring 框架中定义媒体类型的枚举类,用于设置响应的内容类型为 JSON 格式 +import org.springframework.http.MediaType; +// 导入 Spring 框架用于将类标记为组件的注解,表明该类是一个 Spring 管理的组件,可被自动扫描并注入到其他需要的地方 +import org.springframework.stereotype.Component; +// 导入 Spring 框架中用于获取请求上下文相关信息的类和接口 +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +// 导入 Servlet 相关的用于操作 HTTP 响应的类,用于设置响应的各种属性以及向客户端输出内容 +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Objects; + +/** + * HttpHandler 类主要用于处理将服务器响应信息输出到 Web 端(客户端)的相关操作, + * 它能够根据不同的输入参数(如服务器响应实体或特定业务异常),将对应的响应信息以 JSON 格式正确地写入到 HTTP 响应中, + * 同时进行了必要的日志记录以及异常处理,确保响应输出过程的可靠性和稳定性。 + * + * @author 菠萝凤梨 + * @date 2022/3/28 14:15 + */ +@Component +public class HttpHandler { + + // 创建一个日志记录器,用于记录该类中不同操作阶段的日志信息,方便后续进行问题排查和调试 + private static final Logger logger = LoggerFactory.getLogger(HttpHandler.class); + + // 通过 Spring 的依赖注入机制,注入一个 ObjectMapper 对象,用于将对象转换为 JSON 字符串以便输出到客户端 + @Autowired + private ObjectMapper objectMapper; + + /** + * 将服务器响应实体(ServerResponseEntity)的内容以 JSON 格式输出到 Web 端(客户端)的 HTTP 响应中。 + * 如果传入的服务器响应实体为 null,则仅记录相应日志并直接返回,不进行实际的输出操作。 + * 同时会进行一系列的有效性检查,确保能够获取到正确的 HTTP 响应对象,并设置响应的字符编码和内容类型等属性, + * 若在输出过程中出现 I/O 异常,则抛出自定义的业务异常(YamiShopBindException)。 + * + * @param serverResponseEntity 要输出到客户端的服务器响应实体,包含了响应的各种信息,如状态码、消息、数据等 + * @param 泛型参数,用于表示服务器响应实体中携带的数据的具体类型,具有通用性,可以适应不同类型的数据返回情况 + */ + public void printServerResponseToWeb(ServerResponseEntity serverResponseEntity) { + // 如果传入的服务器响应实体为 null,记录日志提示信息,并直接返回,不进行后续操作 + if (serverResponseEntity == null) { + logger.info("print obj is null"); + return; + } + + // 从 Spring 的请求上下文中获取 ServletRequestAttributes 对象,它包含了与当前请求相关的信息,如请求和响应对象等 + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder + .getRequestAttributes(); + // 如果获取到的 ServletRequestAttributes 对象为 null,说明无法获取到请求上下文相关信息,记录错误日志并返回,无法进行响应输出操作 + if (requestAttributes == null) { + logger.error("requestAttributes is null, can not print to web"); + return; + } + + // 从 ServletRequestAttributes 对象中获取 HttpServletResponse 对象,用于后续设置响应属性和向客户端输出内容 + HttpServletResponse response = requestAttributes.getResponse(); + // 如果获取到的 HttpServletResponse 对象为 null,说明无法获取到有效的 HTTP 响应对象,记录错误日志并返回,无法进行响应输出操作 + if (response == null) { + logger.error("httpServletResponse is null, can not print to web"); + return; + } + + // 记录响应的错误消息(这里假设 getMsg 方法获取的是错误相关信息,实际情况可能根据 ServerResponseEntity 的具体实现而定)到日志中,方便排查问题 + logger.error("response error: " + serverResponseEntity.getMsg()); + + // 设置 HTTP 响应的字符编码为 UTF-8,确保输出的内容能够正确地被客户端解析,尤其是包含中文等多字节字符的情况 + response.setCharacterEncoding(CharsetUtil.UTF_8); + // 设置 HTTP 响应的内容类型为 application/json,表明响应的内容是 JSON 格式的数据,让客户端能够正确识别并解析 + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + + // 用于向 HTTP 响应中写入字符数据的对象,初始化为 null,后续通过获取响应的输出流来实例化 + PrintWriter printWriter = null; + try { + // 获取 HttpServletResponse 的输出流对象,用于向客户端写入响应内容(这里是将服务器响应实体转换后的 JSON 字符串写入) + printWriter = response.getWriter(); + // 使用注入的 ObjectMapper 对象将服务器响应实体转换为 JSON 字符串,并写入到 HTTP 响应的输出流中,从而输出到客户端 + printWriter.write(objectMapper.writeValueAsString(serverResponseEntity)); + } catch (IOException e) { + // 如果在写入过程中出现 I/O 异常,抛出自定义的业务异常(YamiShopBindException),并将原始的 I/O 异常作为原因传递,方便上层进行统一的异常处理和日志记录 + throw new YamiShopBindException("io 异常", e); + } + } + + /** + * 针对 YamiShopBindException 类型的异常进行处理,将其相关的响应信息以 JSON 格式输出到 Web 端(客户端)的 HTTP 响应中。 + * 如果传入的异常对象为 null,则仅记录相应日志并直接返回,不进行实际的输出操作。 + * 如果异常对象中已经包含了服务器响应实体(ServerResponseEntity),则调用 printServerResponseToWeb 方法直接输出该实体内容。 + * 否则,会根据异常对象中的错误码和错误消息构建一个新的服务器响应实体,并调用 printServerResponseToWeb 方法输出到客户端。 + * + * @param yamiShopBindException 要处理并输出响应信息的 YamiShopBindException 异常对象,包含了业务异常相关的错误码、消息等信息 + * @param 泛型参数,用于表示服务器响应实体中携带的数据的具体类型(虽然此处构建新实体时可能没有实际数据,但保持与其他方法的通用性) + */ + public void printServerResponseToWeb(YamiShopBindException yamiShopBindException) { + // 如果传入的 YamiShopBindException 异常对象为 null,记录日志提示信息,并直接返回,不进行后续操作 + if (yamiShopBindException == null) { + logger.info("print obj is null"); + return; + } + + // 判断异常对象中是否包含了服务器响应实体(ServerResponseEntity),如果包含则直接调用 printServerResponseToWeb 方法输出该实体内容到客户端 + if (Objects.nonNull(yamiShopBindException.getServerResponseEntity())) { + printServerResponseToWeb(yamiShopBindException.getServerResponseEntity()); + return; + } + + // 如果异常对象中没有包含服务器响应实体,则创建一个新的 ServerResponseEntity 对象,用于封装异常中的错误码和错误消息等信息 + ServerResponseEntity serverResponseEntity = new ServerResponseEntity<>(); + serverResponseEntity.setCode(yamiShopBindException.getCode()); + serverResponseEntity.setMsg(yamiShopBindException.getMessage()); + // 调用 printServerResponseToWeb 方法将构建好的服务器响应实体输出到客户端,完成响应信息的输出操作 + printServerResponseToWeb(serverResponseEntity); + } +} \ No newline at end of file -- 2.34.1 From 57cbf1bd6423a08c471cb5365f2fae4494ebbafd Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 19:33:01 +0800 Subject: [PATCH 098/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/common/util/IdUtil.java | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/util/IdUtil.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/util/IdUtil.java b/yami-shop-common/src/main/java/com/yami/shop/common/util/IdUtil.java new file mode 100644 index 0000000..8b87adb --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/util/IdUtil.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.common.util; + +// 导入 Hutool 库中用于生成分布式唯一ID的 Snowflake 类,通常基于雪花算法实现 +import cn.hutool.core.lang.Snowflake; +// 导入 Spring 框架用于实现依赖注入的注解以及将类标记为组件的注解,表明该类是受 Spring 管理的组件 +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * IdUtil 是一个进制转换相关的工具类,主要提供了以下功能: + * 1. 支持在十进制和基于特定字符集(DICT)定义的进制之间进行转换,最大支持十进制和 DICT.length() 进制的相互转换操作。 + * 2. 能够根据从数据库中获取的记录 ID(通常是较大范围的数值)生成对应的短网址编码,便于在一些场景下使用更简短的编码来表示较长的 ID。 + * 3. 可以依据给定的短网址编码解析出其在数据库中对应的原始记录 ID,实现编码与原始 ID 的相互转换。 + * 4. 借助注入的 Snowflake 实例,还能生成下一个短 ID(基于雪花算法生成唯一 ID 并转换为短网址编码形式)。 + * + * @author xuliugen + * @date 2018/04/23 + */ +@Component +public class IdUtil { + + // 通过 Spring 的依赖注入机制,注入一个 Snowflake 实例,用于生成分布式唯一 ID(可能用于生成短 ID 的基础) + @Autowired + private Snowflake snowflake; + + // 定义了一个包含数字和大小写字母(去除了容易混淆的部分字母)的字符串,作为自定义进制的字符集,用于进制转换操作 + private static final String DICT = "0123456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; + // 计算出基于 DICT 字符集的进制数,即字符集的长度,用于进制转换过程中的计算 + private static final int SEED = DICT.length(); + // 定义了短网址编码的最小长度,用于在生成短网址编码时,如果长度不足则进行补位操作,保证编码长度符合一定要求 + private static final int ID_MIN_LENGTH = 6; + /** + * 将 DICT 字符串转换为字符数组,方便在进制转换过程中通过索引快速获取对应字符,用于数字到字符的映射操作。 + */ + private static final char[] CHARS = DICT.toCharArray(); + /** + * 创建一个字符到数字的映射关系的 Map,用于在解析短网址编码时,根据字符快速获取对应的数字(在自定义进制下的数字表示),实现字符到数字的转换操作。 + */ + private static final Map NUMBERS = new HashMap<>(); + + // 静态代码块,用于初始化 NUMBERS 这个字符到数字的映射 Map,遍历 CHARS 字符数组,将每个字符与其对应的索引(在自定义进制下的数字)存入 Map 中 + static { + int len = CHARS.length; + for (int i = 0; i < len; i++) { + NUMBERS.put(CHARS[i], i); + } + } + + /** + * 将给定的十进制数字(通常是较大范围的数值,如数据库记录 ID)转换为基于自定义进制(由 DICT 定义)的短网址编码字符串。 + * 转换过程采用除基取余法,不断将十进制数除以自定义进制数(SEED),取余数作为对应字符在 DICT 中的索引,构建短网址编码字符串, + * 并且如果生成的编码长度小于最小长度要求(ID_MIN_LENGTH),会在前面补位(添加 DICT 中的第一个字符)以达到最小长度。 + * + * @param id 要转换的十进制数字,通常表示数据库中记录的 ID,范围大致在 (1 - 56.8 billion),不过实际范围取决于具体业务场景 + * @return 转换后的基于自定义进制的短网址编码字符串,例如可能返回类似 "RwTji8"、"GijT7Y" 等形式的字符串 + */ + public static String encode(long id) { + // 创建一个可变的字符串构建器,用于逐步构建短网址编码字符串 + StringBuilder shortUrl = new StringBuilder(); + // 当传入的十进制数字大于 0 时,进行进制转换操作,采用除基取余法 + while (id > 0) { + // 计算当前十进制数除以自定义进制数(SEED)的余数,将其转换为整数类型,该余数将作为在 DICT 中查找对应字符的索引 + int r = (int) (id % SEED); + // 将根据余数获取到的对应字符插入到短网址编码字符串的开头(逆序构建编码字符串) + shortUrl.insert(0, CHARS[r]); + // 更新十进制数,将其除以自定义进制数,得到下一轮循环要处理的数字 + id = id / SEED; + } + // 获取当前已经构建好的短网址编码字符串的长度 + int len = shortUrl.length(); + // 如果长度小于最小长度要求(ID_MIN_LENGTH),进行补位操作 + while (len < ID_MIN_LENGTH) { + // 在短网址编码字符串的开头插入 DICT 中的第一个字符(通常是 '0')进行补位 + shortUrl.insert(0, CHARS[0]); + // 更新长度 + len++; + } + // 返回最终构建好的短网址编码字符串 + return shortUrl.toString(); + } + + /** + * 根据给定的短网址编码字符串(基于自定义进制)解析出其对应的十进制数字,该十进制数字通常对应数据库中的记录 ID。 + * 解析过程是将短网址编码中的每个字符按照其在自定义进制下的权重(通过幂运算计算)转换为十进制数,再累加起来得到最终的十进制结果。 + * + * @param key 要解析的短网址编码字符串,例如 "RwTji8"、"GijT7Y" 等形式的字符串,每个字符都代表自定义进制下的一位数字 + * @return 解析出的十进制数字,对应数据库中记录的 ID + */ + public static long decode(String key) { + // 将传入的短网址编码字符串转换为字符数组,方便逐个字符进行处理 + char[] shorts = key.toCharArray(); + // 获取字符数组的长度,即短网址编码的长度 + int len = shorts.length; + // 初始化用于累加计算的十进制数字为 0 + long id = 0L; + // 遍历短网址编码的每个字符,从左到右(按照权重从高到低)进行解析计算 + for (int i = 0; i < len; i++) { + // 根据当前字符在 NUMBERS 映射 Map 中获取其对应的数字(在自定义进制下的数字表示),并乘以当前位置对应的权重(SEED 的幂次方),然后累加到结果中 + id = id + (long) (NUMBERS.get(shorts[i]) * Math.pow(SEED, len - i - 1)); + } + // 返回解析得到的十进制数字(数据库记录 ID) + return id; + } + + /** + * 借助注入的 Snowflake 实例生成一个分布式唯一 ID(基于雪花算法),然后将该唯一 ID 转换为基于自定义进制的短网址编码形式, + * 作为下一个短 ID 返回,可用于在业务中生成具有唯一性且简短的标识符,例如用于短链接、短编号等场景。 + * + * @return 基于雪花算法生成唯一 ID 并转换后的短网址编码字符串,作为下一个短 ID + */ + public String nextShortId() { + return encode(snowflake.nextId()); + } +} \ No newline at end of file -- 2.34.1 From 656c6fc44966e54e236e990fb86cd58880dbf21f Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 20:57:01 +0800 Subject: [PATCH 099/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../serializer/json/ImgJsonSerializer.java | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/serializer/json/ImgJsonSerializer.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/serializer/json/ImgJsonSerializer.java b/yami-shop-common/src/main/java/com/yami/shop/common/serializer/json/ImgJsonSerializer.java new file mode 100644 index 0000000..91d692a --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/serializer/json/ImgJsonSerializer.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.common.serializer.json; + +// 导入Hutool工具库中用于字符串操作的工具类,例如判断字符串是否为空、处理字符串拼接等操作 +import cn.hutool.core.util.StrUtil; +// 导入Jackson库中用于自定义JSON序列化相关的核心类,用于定义如何将Java对象序列化为JSON格式 +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +// 导入自定义的类,Qiniu类可能包含了七牛云相关的配置信息,如资源访问地址等 +import com.yami.shop.common.bean.Qiniu; +// 导入自定义的图片上传工具类,可能用于获取图片上传相关的配置信息,比如上传类型、资源地址等 +import com.yami.shop.common.util.ImgUploadUtil; +// 导入Spring框架用于实现依赖注入的注解以及将类标记为组件的注解,表明该类是受Spring管理的组件,可被自动注入到需要的地方 +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * ImgJsonSerializer类是一个自定义的JSON序列化器,用于处理将特定格式的字符串(可能是图片相关的路径字符串)序列化为JSON格式时的逻辑定制。 + * 它主要根据图片上传的类型以及图片路径本身的格式(是否以http或https开头),来对图片路径进行处理,添加相应的资源访问地址前缀, + * 最终将处理后的图片路径以合适的格式序列化为JSON字符串输出,适用于在将包含图片路径信息的对象转换为JSON时进行特定的路径格式调整需求。 + * + * @author lanhai + */ +@Component +public class ImgJsonSerializer extends JsonSerializer { + + // 通过Spring的依赖注入机制,注入一个Qiniu对象,该对象可能包含了七牛云存储相关的配置信息,比如七牛云资源的访问地址等 + @Autowired + private Qiniu qiniu; + // 注入一个ImgUploadUtil对象,用于获取图片上传相关的配置信息,例如图片上传类型等,辅助对图片路径进行处理 + @Autowired + private ImgUploadUtil imgUploadUtil; + + /** + * 重写了Jackson库中的serialize方法,用于定义具体的序列化逻辑,也就是将传入的字符串(此处假设与图片路径相关)按照特定规则转换为JSON格式的字符串输出。 + * + * @param value 要进行序列化的字符串,在这里可能表示图片的路径信息,可能是单个图片路径或者多个图片路径用逗号分隔的形式 + * @param gen Jackson库中的JsonGenerator对象,用于生成JSON输出内容,通过它可以将处理后的字符串写入到最终的JSON输出中 + * @param serializers Jackson库中的SerializerProvider对象,提供了序列化相关的上下文信息,比如配置等,但在这个方法中通常不需要直接操作它 + * @throws IOException 如果在向JsonGenerator写入内容过程中出现I/O异常,会抛出此异常,需要上层调用者进行相应处理 + */ + @Override + public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + // 判断传入的要序列化的字符串是否为空(空白字符串,包括null、空字符串以及只包含空格等不可见字符的字符串) + if (StrUtil.isBlank(value)) { + // 如果为空,则向JsonGenerator写入一个空字符串,保持JSON格式的一致性,然后直接返回,不再进行后续处理 + gen.writeString(StrUtil.EMPTY); + return; + } + + // 将传入的字符串按照逗号进行分割,得到一个字符串数组,假设这里的字符串表示多个图片路径,以逗号分隔开,每个元素就是一个单独的图片路径 + String[] imgs = value.split(StrUtil.COMMA); + // 创建一个可变的字符串构建器,用于拼接处理后的图片路径,方便后续构建最终的序列化字符串 + StringBuilder sb = new StringBuilder(); + // 用于存储资源访问地址,根据不同的上传类型来确定具体的值,后续会将其添加到图片路径前面(如果图片路径本身不符合要求的话) + String resourceUrl = ""; + // 定义一个正则表达式字符串,用于匹配以"http"或"https"开头的字符串,目的是判断图片路径是否已经是完整的网络访问地址形式 + String rule = "^((http[s]{0,1})://)"; + // 使用定义好的正则表达式创建一个Pattern对象,用于后续进行正则匹配操作 + Pattern pattern = Pattern.compile(rule); + + // 根据ImgUploadUtil对象获取的上传类型来确定资源访问地址(resourceUrl)的值 + if (Objects.equals(imgUploadUtil.getUploadType(), 2)) { + // 如果上传类型为2,从注入的Qiniu对象中获取资源访问地址,可能是七牛云存储资源的访问地址,用于构建完整的图片访问路径 + resourceUrl = qiniu.getResourcesUrl(); + } else if (Objects.equals(imgUploadUtil.getUploadType(), 1)) { + // 如果上传类型为1,从ImgUploadUtil对象中获取资源访问地址,可能是其他存储方式对应的资源访问地址 + resourceUrl = imgUploadUtil.getResourceUrl(); + } + + // 遍历分割后的每个图片路径字符串 + for (String img : imgs) { + // 使用创建的Pattern对象对当前图片路径字符串进行正则匹配,得到一个Matcher对象,用于查看是否匹配成功 + Matcher matcher = pattern.matcher(img); + // 如果匹配成功,说明图片路径已经是以"http"或"https"开头的完整网络访问地址形式,直接将其添加到字符串构建器中,并添加逗号(保持与传入格式的一致性,后续会处理末尾多余的逗号) + if (matcher.find()) { + sb.append(img).append(StrUtil.COMMA); + } else { + // 如果图片路径不是完整的网络访问地址形式,则将前面确定的资源访问地址(resourceUrl)、当前图片路径以及逗号依次添加到字符串构建器中,构建完整的图片访问路径格式 + sb.append(resourceUrl).append(img).append(StrUtil.COMMA); + } + } + + // 删除字符串构建器中最后一个字符(末尾多余的逗号),得到最终处理好的图片路径字符串 + sb.deleteCharAt(sb.length() - 1); + // 将处理好的图片路径字符串通过JsonGenerator写入到最终的JSON输出中,完成序列化操作 + gen.writeString(sb.toString()); + } +} \ No newline at end of file -- 2.34.1 From f3420c36aae7b65da7236115434c4d8509d9c10c Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 21:10:04 +0800 Subject: [PATCH 100/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/common/bean/ImgUpload.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/bean/ImgUpload.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/bean/ImgUpload.java b/yami-shop-common/src/main/java/com/yami/shop/common/bean/ImgUpload.java new file mode 100644 index 0000000..8d9195f --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/bean/ImgUpload.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.common.bean; + +// 引入Lombok的Data注解,通过该注解,编译器会自动帮我们生成类的常用方法, +// 比如各属性的Getter、Setter方法,以及toString、equals、hashCode方法等,简化代码编写 +import lombok.Data; + +/** + * ImgUpload类用于封装本地存储相关的配置信息,主要包含了本地文件上传过程中涉及到的关键配置项, + * 方便在项目中统一管理和获取这些配置,以根据不同的配置情况来执行相应的文件上传逻辑,例如选择本地上传还是使用七牛云等外部存储服务上传。 + * + * @author lgh + */ +@Data +public class ImgUpload { + + /** + * 用于指定本地文件上传时,文件存储的文件夹路径, + * 通过设置该属性,可以明确文件上传后在本地服务器存储的具体位置,便于后续的文件管理和访问操作。 + */ + private String imagePath; + + /** + * 用于标识文件上传的方式,是一个整数类型的枚举值(这里虽然没用枚举类严格定义,但约定了取值的含义), + * 取值为1时表示采用本地文件上传方式,也就是将文件存储到由imagePath指定的本地文件夹中; + * 取值为2时表示使用七牛云存储服务进行文件上传,此时会结合其他相关配置与七牛云进行交互来完成上传操作。 + */ + private Integer uploadType; + + /** + * 用于指定网站相关资源的访问URL,在文件上传的场景下,可能用于构建完整的文件访问链接, + * 比如当通过某种方式获取到文件在本地或者云端存储的相对路径后,结合该resourceUrl就能生成完整的可访问的文件URL,提供给前端等进行访问展示。 + */ + private String resourceUrl; +} \ No newline at end of file -- 2.34.1 From 8c83805490907f4cd9650236970e653c215ba7eb Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 21:14:24 +0800 Subject: [PATCH 101/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/common/util/ImgUploadUtil.java | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/util/ImgUploadUtil.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/util/ImgUploadUtil.java b/yami-shop-common/src/main/java/com/yami/shop/common/util/ImgUploadUtil.java new file mode 100644 index 0000000..a98d66c --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/util/ImgUploadUtil.java @@ -0,0 +1,127 @@ +package com.yami.shop.common.util; + +// 导入Hutool工具库中用于字符串操作的工具类,可用于判断字符串是否为空等操作 +import cn.hutool.core.util.StrUtil; +// 导入自定义的用于封装图片上传相关配置信息的类,包含存储路径、上传类型、资源访问URL等属性 +import com.yami.shop.common.bean.ImgUpload; +// 导入自定义的业务异常类,用于在特定业务逻辑出现问题时抛出相应的异常信息,方便统一处理 +import com.yami.shop.common.exception.YamiShopBindException; +// 导入Spring框架用于实现依赖注入的注解以及将类标记为组件的注解,表明该类是受Spring管理的组件,可在项目中被自动注入和使用 +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +// 导入Spring用于处理文件上传的核心类,代表上传的文件对象,包含文件内容、文件名等信息 +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; + +/** + * ImgUploadUtil类是一个工具类,主要提供了本地文件上传与删除相关的功能方法,同时也能获取与文件上传配置相关的一些关键信息, + * 例如上传类型、上传路径、资源访问URL等,并且在配置信息不完整或者文件操作出现异常等情况下会抛出相应的业务异常, + * 便于在项目中进行统一的异常处理以及基于配置进行可靠的本地文件管理操作。 + * + * @author TRACK + */ +@Component +public class ImgUploadUtil { + + // 通过Spring的依赖注入机制,注入一个ImgUpload对象,该对象包含了本地文件上传相关的配置信息,如存储路径、上传方式等 + @Autowired + private ImgUpload imgUpload; + + /** + * 获取文件上传类型的方法,从注入的ImgUpload对象中获取上传类型属性值。 + * 如果获取到的值为null,说明没有配置图片存储方式,此时会抛出自定义的业务异常(YamiShopBindException), + * 提醒调用者需要进行相应的配置,否则返回获取到的上传类型值。 + * + * @return Integer 返回表示文件上传类型的整数值,1通常表示本地文件上传,2可能表示使用其他云存储等方式(具体由业务定义) + */ + public Integer getUploadType() { + Integer uploadType = imgUpload.getUploadType(); + if (Objects.isNull(uploadType)) { + throw new YamiShopBindException("请配置图片存储方式"); + } + return uploadType; + } + + /** + * 获取文件上传路径的方法,从注入的ImgUpload对象中获取图片存储的路径属性值(imagePath)。 + * 如果获取到的值为null或者是空字符串,说明没有配置图片存储路径,此时会抛出自定义的业务异常(YamiShopBindException), + * 提醒调用者需要进行相应的配置,否则返回获取到的图片存储路径字符串。 + * + * @return String 返回表示本地文件上传存储路径的字符串,用于确定文件上传后在本地服务器存储的具体位置 + */ + public String getUploadPath() { + String imagePath = imgUpload.getImagePath(); + if (Objects.isNull(imagePath) || StrUtil.isBlank(imagePath)) { + throw new YamiShopBindException("请配置图片存储路径"); + } + return imagePath; + } + + /** + * 获取图片资源访问URL的方法,从注入的ImgUpload对象中获取资源访问URL属性值(resourceUrl)。 + * 如果获取到的值为null或者是空字符串,说明没有配置图片路径相关的资源访问URL,此时会抛出自定义的业务异常(YamiShopBindException), + * 提醒调用者需要进行相应的配置,否则返回获取到的资源访问URL字符串,该URL可用于构建完整的图片访问链接等场景。 + * + * @return String 返回表示图片资源访问URL的字符串,用于生成可对外访问的图片链接 + */ + public String getResourceUrl() { + String resourceUrl = imgUpload.getResourceUrl(); + if (Objects.isNull(resourceUrl) || StrUtil.isBlank(resourceUrl)) { + throw new YamiShopBindException("请配置图片路径"); + } + return resourceUrl; + } + + /** + * 执行本地文件上传的方法,将传入的MultipartFile类型的文件(代表上传的文件对象)保存到本地指定的路径下。 + * 首先获取配置的本地文件上传路径,然后根据传入的文件名构建目标文件对象,若目标文件所在目录不存在,则尝试创建目录, + * 如果目录创建失败会抛出自定义的业务异常;接着将上传的文件内容转移到目标文件中,如果出现I/O异常则同样抛出自定义的业务异常, + * 若文件上传成功则返回上传后的文件名。 + * + * @param img 代表要上传的文件对象,包含了文件的内容、原始文件名等信息,是Spring在处理文件上传时封装的对象 + * @param fileName 要保存的文件名,通常可以是基于一定规则生成或者获取自上传文件的原始文件名等情况 + * @return String 返回上传后的文件名,可用于后续的业务逻辑中,比如记录文件上传情况、构建文件访问链接等 + */ + public String upload(MultipartFile img, String fileName) { + // 获取配置的本地文件上传路径(存储文件夹路径) + String filePath = imgUpload.getImagePath(); + // 根据文件上传路径和传入的文件名构建一个File对象,代表要保存的目标文件 + File file = new File(filePath + fileName); + // 判断目标文件所在的目录是否存在,如果不存在则尝试创建目录 + if (!file.exists()) { + boolean result = file.mkdirs(); + // 如果目录创建失败(返回false),抛出自定义的业务异常,提示创建目录失败的具体路径信息 + if (!result) { + throw new YamiShopBindException("创建目录:" + filePath + "失败"); + } + } + try { + // 将上传的文件内容转移到目标文件中,即将MultipartFile中的文件数据写入到本地创建好的目标文件里 + img.transferTo(file); + } catch (IOException e) { + // 如果在文件转移(写入)过程中出现I/O异常,抛出自定义的业务异常,提示图片上传失败 + throw new YamiShopBindException("图片上传失败"); + } + // 文件上传成功后,返回上传后的文件名,方便后续业务使用 + return fileName; + } + + /** + * 执行本地文件删除的方法,根据传入的文件名尝试删除本地指定路径下对应的文件。 + * 通过获取配置的本地文件上传路径和传入的文件名构建目标文件对象,然后调用其deleteOnExit方法标记文件在JVM退出时删除, + * 不过要注意这种删除方式只是标记,实际的删除操作可能会在JVM正常退出时执行。 + * + * @param fileName 要删除的文件名,需对应本地文件上传路径下实际存在的文件 + */ + public void delete(String fileName) { + // 获取配置的本地文件上传路径(存储文件夹路径) + String filePath = imgUpload.getImagePath(); + // 根据文件上传路径和传入的文件名构建一个File对象,代表要删除的目标文件 + File file = new File(filePath + fileName); + // 标记文件在JVM退出时删除,注意这并不一定会立即删除文件,而是在JVM正常退出时执行删除操作(如果文件存在且可删除的话) + file.deleteOnExit(); + } +} \ No newline at end of file -- 2.34.1 From 089bb1f584cc8adf189bfd32ac089a11dc982a7a Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 21:16:29 +0800 Subject: [PATCH 102/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/common/util/IpHelper.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/util/IpHelper.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/util/IpHelper.java b/yami-shop-common/src/main/java/com/yami/shop/common/util/IpHelper.java new file mode 100644 index 0000000..37e22c8 --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/util/IpHelper.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.common.util; + +// 导入Servlet相关的用于操作HTTP请求的类,后续用于获取请求头以及客户端IP地址等信息 +import jakarta.servlet.http.HttpServletRequest; + +/** + * IpHelper类是一个工具类,主要用于获取客户端(用户)的真实IP地址。 + * 在网络环境中,由于可能存在代理服务器等情况,获取IP地址的方式会有所不同, + * 该类通过依次检查不同的请求头信息以及直接获取远程地址的方式来尝试获取客户端真实IP地址, + * 并且如果获取到多个IP地址(例如经过多层代理的情况),则取第一个作为客户端的真实IP地址返回。 + * + * @author lanhai + */ +public class IpHelper { + + // 定义一个表示未知IP地址的常量字符串,用于后续判断请求头中获取到的IP地址是否有效 + private static final String UNKNOWN = "unknown"; + + /** + * 获取用户真实IP地址的方法,按照一定的顺序检查不同的请求头以及直接获取远程地址来确定客户端的IP地址。 + * 首先尝试从"x-forwarded-for"请求头获取IP地址,因为在经过代理服务器转发时,该请求头可能包含了客户端的真实IP地址; + * 如果该请求头获取到的IP地址为空、长度为0或者是表示未知的字符串("unknown"),则接着尝试从"Proxy-Client-IP"请求头获取; + * 若还是不符合要求,再尝试从"WL-Proxy-Client-IP"请求头获取; + * 若以上请求头都无法获取到有效IP地址,则直接获取请求的远程地址(RemoteAddr)作为IP地址; + * 最后,如果获取到的IP地址字符串包含多个IP(以逗号分隔,可能经过多层代理的情况),则取第一个IP作为最终的客户端真实IP地址返回。 + * + * @return String 返回获取到的客户端真实IP地址,如果由于各种原因无法获取到(例如没有有效的请求上下文等)则返回null + */ + public static String getIpAddr() { + // 通过HttpContextUtils工具类获取当前线程绑定的HttpServletRequest对象,该对象包含了请求相关的各种信息 + HttpServletRequest request = HttpContextUtils.getHttpServletRequest(); + // 如果获取到的HttpServletRequest对象为null,说明无法获取到请求上下文相关信息,直接返回null,无法获取IP地址 + if (request == null) { + return null; + } + + // 首先尝试从"x-forwarded-for"请求头获取IP地址,该请求头在经过代理服务器转发时可能包含客户端真实IP + String ip = request.getHeader("x-forwarded-for"); + // 如果获取到的IP地址为空、长度为0或者等于表示未知的字符串(不区分大小写比较),则继续尝试从其他请求头获取 + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + // 如果从"Proxy-Client-IP"请求头获取到的IP地址仍不符合要求(为空、长度为0或者是未知字符串),则再尝试从"WL-Proxy-Client-IP"请求头获取 + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + // 如果经过前面的尝试还是没有获取到有效IP地址,则直接获取请求的远程地址(RemoteAddr)作为IP地址,这是最基本的获取客户端IP的方式 + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + + // 将获取到的IP地址字符串按照逗号进行分割,因为可能存在经过多层代理,IP地址有多个的情况(以逗号分隔) + String[] ips = ip.split(","); + // 返回分割后的第一个IP地址(去除两端的空白字符)作为客户端的真实IP地址,如果只有一个IP则就是该IP本身 + return ips[0].trim(); + } +} \ No newline at end of file -- 2.34.1 From 0badd3fdcf8db3e2ff765dcfe2f17890e589edeb Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 21:18:40 +0800 Subject: [PATCH 103/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yami/shop/common/util/Json.java | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/util/Json.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/util/Json.java b/yami-shop-common/src/main/java/com/yami/shop/common/util/Json.java new file mode 100644 index 0000000..087e6e9 --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/util/Json.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.common.util; + +// 导入Jackson相关的注解和类,用于配置JSON序列化和反序列化过程中的一些行为,比如包含哪些属性、如何处理未知属性等 +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.json.JsonWriteFeature; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +// 导入Lombok的日志记录相关注解,用于简化日志记录代码,自动生成名为log的日志记录对象 +import lombok.extern.slf4j.Slf4j; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Json工具类,主要基于Jackson库提供了一系列便捷的JSON处理方法, + * 包括将Java对象转换为JSON字符串、将JSON字符串反序列化为Java对象(单个对象、对象数组、JSON节点等多种形式), + * 同时在类初始化时对Jackson的ObjectMapper进行了一些常用的配置,以满足特定的JSON处理需求,例如处理空值、未知属性、日期格式等情况。 + * + * @author lanhai + */ +@Slf4j +public class Json { + + // 创建一个静态的ObjectMapper实例,ObjectMapper是Jackson库中用于进行JSON序列化和反序列化的核心类,后续所有的JSON操作都基于它来实现 + private static ObjectMapper objectMapper = new ObjectMapper(); + + // 静态代码块,用于对ObjectMapper实例进行一系列的配置,这些配置会影响JSON序列化和反序列化的行为。 + static { + // 设置序列化时的包含规则,这里配置为JsonInclude.Include.NON_EMPTY,表示如果属性值为空(比如null、空字符串、空集合等)则不输出该属性到JSON字符串中,减少不必要的JSON数据量。 + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + // 配置在序列化时,对于空的Java对象(没有任何属性值的对象)转JSON的时候不抛出错误,而是正常返回一个空的JSON对象(如 {}),增强程序的健壮性,避免因空对象序列化失败导致整个操作中断。 + objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + // 禁用将日期类型属性序列化为时间戳的功能,这样在处理日期类型数据时可以按照更符合业务需求的日期格式(比如特定的字符串格式)进行序列化,而不是默认的时间戳形式。 + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + // 禁用在反序列化时遇到未知属性(即JSON字符串中的属性在对应的Java类中不存在定义)抛出异常的功能,这样即使JSON数据有额外的属性,也能尽量解析出已知的属性值,避免因未知属性导致整个反序列化失败。 + objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + // 取消对非ASCII字符的转码操作,使得JSON字符串中可以直接包含如中文等非ASCII字符,而不需要进行转义编码,方便查看和处理,更符合实际业务场景中对中文等字符的使用需求。 + objectMapper.configure(JsonWriteFeature.ESCAPE_NON_ASCII.mappedFeature(), false); + + } + + /** + * 将给定的Java对象转换为JSON字符串的方法。 + * 通过调用ObjectMapper的writeValueAsString方法来实现转换,如果在转换过程中出现JSON处理异常(如对象的属性类型不支持序列化等情况), + * 则会记录错误日志,并返回null,由调用者根据实际情况进行后续处理。 + * + * @param object 要转换为JSON字符串的Java对象,可以是任意符合Jackson序列化规则的对象,如POJO、集合、基本数据类型包装类等。 + * @return String 返回转换后的JSON字符串,如果转换过程出现异常则返回null。 + */ + public static String toJsonString(Object object) { + try { + return objectMapper.writeValueAsString(object); + } catch (JsonProcessingException e) { + // 记录对象转JSON时出现的错误日志,方便后续排查问题,这里记录了异常的详细信息(通过传入e作为参数) + log.error("对象转json错误:", e); + } + return null; + } + + /** + * 将给定的JSON字符串反序列化为指定类型的Java对象的方法。 + * 通过调用ObjectMapper的readValue方法,按照传入的目标类类型(clazz)信息将JSON字符串解析为对应的Java对象, + * 如果在反序列化过程中出现异常(如JSON格式不符合要求、属性类型不匹配等情况),则会记录错误日志,并返回null, + * 由调用者根据实际情况进行后续处理。 + * + * @param json 要反序列化的JSON字符串,需要符合对应Java类的结构和Jackson的解析规则。 + * @param clazz 目标Java类的类型,用于告诉ObjectMapper将JSON字符串解析成什么类型的对象,必须是具体的类类型,不能是接口或抽象类等无法实例化的类型。 + * @return 返回反序列化后的Java对象,类型与传入的clazz参数指定的类型一致,如果反序列化过程出现异常则返回null。 + */ + public static T parseObject(String json, Class clazz) { + T result = null; + try { + result = objectMapper.readValue(json, clazz); + } catch (Exception e) { + // 记录JSON转对象时出现的错误日志,方便后续排查问题,这里记录了异常的详细信息(通过传入e作为参数) + log.error("对象转json错误:", e); + } + return result; + } + + /** + * 获取配置好的ObjectMapper实例的方法,外部代码可以通过获取该实例来进行更复杂、自定义的JSON序列化和反序列化操作, + * 利用已有的配置来满足一些特殊的JSON处理需求,比如使用不同的JSON格式、自定义序列化器和反序列化器等情况。 + * + * @return ObjectMapper 返回配置好的ObjectMapper实例,供外部调用进行JSON相关操作。 + */ + public static ObjectMapper getObjectMapper() { + return objectMapper; + } + + /** + * 将给定的JSON字符串反序列化为指定类型的Java对象数组,并将其转换为对应的List集合的方法。 + * 先通过ObjectMapper的readValue方法将JSON字符串按照传入的对象数组类型(clazz)解析为对象数组, + * 如果解析成功,则将对象数组转换为List集合返回;如果解析过程出现异常或者解析结果为null,则返回一个空的List集合, + * 保证方法的返回结果具有一定的稳定性,方便调用者进行后续操作而不用担心空指针等问题。 + * 此方法相较于使用TypeReference来处理数组反序列化,在特定情况下(如文档中提到的,最多可快10倍)性能更好。 + * + * @param json 要反序列化的JSON字符串,其结构需要符合对应Java对象数组的格式要求,即包含多个符合指定类型的JSON对象表示。 + * @param clazz 目标Java对象数组的类型,用于告诉ObjectMapper将JSON字符串解析成什么类型的对象数组,必须是具体的类数组类型,如MyClass[].class。 + * @return 返回反序列化后的Java对象列表,类型与传入的clazz参数指定的对象数组中的元素类型一致,如果反序列化出现异常则返回空的List集合。 + */ + public static List parseArray(String json, Class clazz) { + T[] result = null; + try { + result = objectMapper.readValue(json, clazz); + } catch (Exception e) { + // 记录JSON转换时出现的错误日志,方便后续排查问题,这里记录了异常的详细信息(通过传入e作为参数) + log.error("Json转换错误:", e); + } + if (result == null) { + return Collections.emptyList(); + } + return Arrays.asList(result); + } + + /** + * 将给定的JSON字符串转换为JsonNode对象的方法,JsonNode类似于JSON数据的树形结构表示,可以方便地对JSON数据进行灵活的遍历、查询和操作, + * 相当于将JSON字符串解析为一个通用的、可以灵活处理的JSON节点对象(类似Map结构,可以按节点层次获取属性值等)。 + * 如果在转换过程中出现异常,则会记录错误日志,并返回null,由调用者根据实际情况进行后续处理。 + * + * @param jsonStr 要转换为JsonNode对象的JSON字符串,格式需符合JSON语法规范。 + * @return JsonNode 返回解析后的JsonNode对象,如果转换过程出现异常则返回null,通过该对象可以进一步操作JSON数据的各个节点和属性。 + */ + public static JsonNode parseJson(String jsonStr) { + JsonNode jsonNode = null; + try { + jsonNode = objectMapper.readTree(jsonStr); + } catch (Exception e) { + // 记录JSON转换时出现的错误日志,方便后续排查问题,这里记录了异常的详细信息(通过传入e作为参数) + log.error("Json转换错误:", e); + } + return jsonNode; + } +} \ No newline at end of file -- 2.34.1 From 71b960cc0b8126a5133b29183afaf8e6a92b7319 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 21:24:17 +0800 Subject: [PATCH 104/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/LoginController.java | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/controller/LoginController.java diff --git a/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/controller/LoginController.java b/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/controller/LoginController.java new file mode 100644 index 0000000..f95929c --- /dev/null +++ b/yami-shop-security/yami-shop-security-api/src/main/java/com/yami/shop/security/api/controller/LoginController.java @@ -0,0 +1,136 @@ +package com.yami.shop.security.api.controller; + +// 导入MyBatis Plus框架中用于构建条件查询的核心类,方便编写数据库查询条件,此处用于查询用户信息 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +// 导入项目中自定义的用户实体类,代表数据库中存储的用户相关信息,包含各种用户属性,如用户名、手机号、密码等 +import com.yami.shop.bean.model.User; +// 导入项目自定义的业务异常类,用于在业务逻辑出现特定错误情况时抛出异常,方便统一处理和向客户端返回错误信息 +import com.yami.shop.common.exception.YamiShopBindException; +// 导入项目自定义的工具类,可能包含一些通用的业务逻辑处理方法,此处具体功能需看其内部实现(从名字推测和主体逻辑相关) +import com.yami.shop.common.util.PrincipalUtil; +// 导入项目中定义的用于操作数据库中用户表的Mapper接口,通过它可以调用数据库相关的查询、插入等操作方法(由MyBatis框架生成实现类来具体执行SQL操作) +import com.yami.shop.dao.UserMapper; +// 导入与安全相关的业务对象类,用于封装在Token中存储的用户信息,方便在不同模块间传递和使用用户相关数据 +import com.yami.shop.security.common.bo.UserInfoInTokenBO; +// 导入与安全相关的用于接收登录认证信息的数据传输对象(DTO)类,包含用户名、密码以及用户所属系统类型等信息,用于接收前端传入的登录数据 +import com.yami.shop.security.common.dto.AuthenticationDTO; +// 导入与安全相关的枚举类,用于区分不同的系统类型,此处有个普通用户类型(ORDINARY),可能还有其他类型用于不同业务场景下的用户分类 +import com.yami.shop.security.common.enums.SysTypeEnum; +// 导入与安全相关的用于密码检查的管理类,可能包含验证密码是否符合规则、检查密码错误次数等逻辑,保障登录密码的安全性 +import com.yami.shop.security.common.manager.PasswordCheckManager; +// 导入与安全相关的用于密码管理的类,可能包含密码加密、解密等相关操作方法,确保密码在存储和传输过程中的安全性 +import com.yami.shop.security.common.manager.PasswordManager; +// 导入与安全相关的用于管理Token存储和操作的类,负责生成、存储Token以及基于用户信息获取相应的Token相关信息等功能 +import com.yami.shop.security.common.manager.TokenStore; +// 导入与安全相关的用于返回给前端包含Token信息的视图对象(VO)类,将后端处理后的Token相关数据以合适的格式返回给前端展示和后续使用 +import com.yami.shop.security.common.vo.TokenInfoVO; +// 导入Swagger相关的注解,用于生成API文档,@Tag注解用于给接口分类添加标签,方便文档中进行分组展示和说明 +import io.swagger.v3.oas.annotations.tags.Tag; +// 导入Swagger相关的注解,用于在API文档中描述接口的功能摘要和详细信息,方便前端开发人员等查看接口的作用和使用方式 +import io.swagger.v3.oas.annotations.Operation; +// 导入Spring框架用于实现依赖注入的注解,表明后续的属性需要由Spring容器进行注入实例化 +import org.springframework.beans.factory.annotation.Autowired; +// 导入项目自定义的用于封装服务器响应信息的实体类,包含响应状态码、消息、数据等内容,用于统一向客户端返回响应结果 +import com.yami.shop.common.response.ServerResponseEntity; +// 导入Spring框架用于定义处理POST请求的注解,将下面的方法映射为一个接收POST请求的接口,处理对应的业务逻辑 +import org.springframework.web.bind.annotation.PostMapping; +// 导入Spring框架用于接收请求体数据并将其绑定到方法参数的注解,确保前端传入的JSON等格式的数据能正确映射到对应的参数对象上 +import org.springframework.web.bind.annotation.RequestBody; +// 导入Spring框架用于将类标记为RESTful风格的控制器的注解,表明该类中的方法主要用于处理HTTP请求并返回JSON等格式的响应数据 +import org.springframework.web.bind.annotation.RestController; + +// 导入Jakarta验证相关的注解,用于对传入的参数对象进行数据校验,确保参数符合一定的规则,比如非空等要求 +import jakarta.validation.Valid; + +/** + * LoginController类是一个Spring RESTful风格的控制器类,主要负责处理用户登录相关的业务逻辑, + * 通过接收前端传入的登录认证信息(用户名、密码以及用户所属系统类型等),验证用户信息的合法性, + * 包括检查用户是否存在、密码是否正确以及密码错误次数是否超限等情况,若验证通过则生成并返回对应的Token信息给前端, + * 方便后续的用户鉴权以及其他需要身份验证的业务操作,同时使用Swagger注解生成相关的API文档,便于接口的查看和使用说明。 + * + * @author 菠萝凤梨 + * @date 2022/3/28 15:20 + */ +@RestController +@Tag(name = "登录") +public class LoginController { + + // 通过Spring的依赖注入机制,注入一个TokenStore实例,用于管理Token的存储、生成以及获取相关信息等操作 + @Autowired + private TokenStore tokenStore; + + // 注入一个UserMapper实例,用于调用数据库中用户表相关的查询、操作方法,以便获取用户信息进行登录验证等操作 + @Autowired + private UserMapper userMapper; + + // 注入一个PasswordCheckManager实例,用于对用户输入的密码进行各种检查,如密码错误次数限制等安全相关的验证操作 + @Autowired + private PasswordCheckManager passwordCheckManager; + + // 注入一个PasswordManager实例,用于对密码进行加密、解密等管理操作,此处主要用于解密前端传入的密码进行后续验证 + @Autowired + private PasswordManager passwordManager; + + /** + * 处理用户登录请求的接口方法,接收前端传入的经过验证的登录认证信息(通过@Valid注解确保数据格式符合要求), + * 首先根据传入的用户名(可能是手机号或者用户名)查找对应的用户信息,然后解密前端传入的密码, + * 接着检查密码是否正确以及是否达到密码错误次数限制,若一切验证通过,则构建用户在Token中存储的信息对象, + * 通过TokenStore生成并存储Token,获取相应的Token信息视图对象(TokenInfoVO)返回给前端,告知登录成功以及返回Token相关信息。 + * + * @param authenticationDTO 经过验证的登录认证信息对象,包含用户名、密码以及用户所属系统类型等信息,由前端传入,通过请求体(@RequestBody)接收并绑定到该参数上 + * @return ServerResponseEntity 返回一个包含Token信息视图对象(TokenInfoVO)的服务器响应实体,若登录成功则状态码为成功状态,包含有效的Token信息返回给前端;若出现异常则返回相应的错误状态和提示信息 + */ + @PostMapping("/login") + @Operation(summary = "账号密码(用于前端登录)", description = "通过账号/手机号/用户名密码登录,还要携带用户的类型,也就是用户所在的系统") + public ServerResponseEntity login( + @Valid @RequestBody AuthenticationDTO authenticationDTO) { + + // 从登录认证信息对象中获取用户名(可能是手机号或者普通用户名形式),用于后续查找用户信息 + String mobileOrUserName = authenticationDTO.getUserName(); + // 根据获取到的用户名查找对应的用户信息,若找不到则会抛出异常告知账号或密码不正确 + User user = getUser(mobileOrUserName); + + // 通过PasswordManager对前端传入的密码进行解密操作,获取解密后的密码,以便后续和数据库中存储的密码进行比对验证 + String decryptPassword = passwordManager.decryptPassword(authenticationDTO.getPassWord()); + + // 通过PasswordCheckManager检查密码是否正确以及是否达到密码错误次数限制(半小时内密码输入错误十次,已限制登录30分钟),若不符合要求会抛出相应异常进行处理 + passwordCheckManager.checkPassword(SysTypeEnum.ORDINARY, authenticationDTO.getUserName(), decryptPassword, user.getLoginPassword()); + + // 创建一个用于在Token中存储用户信息的业务对象,将用户的ID、所属系统类型以及是否启用等信息设置进去,用于后续生成Token以及存储相关用户标识信息 + UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO(); + userInfoInToken.setUserId(user.getUserId()); + userInfoInToken.setSysType(SysTypeEnum.ORDINARY.value()); + userInfoInToken.setEnabled(user.getStatus() == 1); + + // 通过TokenStore存储用户信息并生成Token,同时获取包含Token相关信息的视图对象(TokenInfoVO),用于返回给前端告知登录成功以及提供后续鉴权等所需的Token信息 + TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken); + return ServerResponseEntity.success(tokenInfoVO); + } + + /** + * 根据传入的用户名(可能是手机号或者用户名)查找对应的用户信息的私有方法, + * 首先判断传入的用户名是否符合手机号格式,如果是则通过手机号在数据库中查找用户信息; + * 如果通过手机号没找到用户,则再尝试通过用户名在数据库中查找用户; + * 如果最终还是没找到对应的用户,则抛出自定义的业务异常(YamiShopBindException)告知账号或密码不正确。 + * + * @param mobileOrUserName 要查找用户信息对应的用户名,可能是手机号或者普通用户名形式 + * @return User 返回查找到的用户信息对象,如果没找到则抛出异常,不会返回null + */ + private User getUser(String mobileOrUserName) { + User user = null; + // 通过PrincipalUtil工具类判断传入的用户名是否符合手机号格式,如果是则进行下一步操作(通过手机号查找用户) + if (PrincipalUtil.isMobile(mobileOrUserName)) { + // 使用MyBatis Plus构建的条件查询对象(LambdaQueryWrapper),根据手机号在数据库中查找对应的用户信息,通过UserMapper调用数据库查询方法 + user = userMapper.selectOne(new LambdaQueryWrapper().eq(User::getUserMobile, mobileOrUserName)); + } + // 如果通过手机号没找到用户(即user为null),则尝试通过用户名在数据库中查找用户 + if (user == null) { + user = userMapper.selectOneByUserName(mobileOrUserName); + } + // 如果最终还是没找到对应的用户,则抛出自定义的业务异常,告知账号或密码不正确 + if (user == null) { + throw new YamiShopBindException("账号或密码不正确"); + } + return user; + } +} \ No newline at end of file -- 2.34.1 From 1dc9ea7ad351a247428a0d490f687d76f77608dd Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 21:25:15 +0800 Subject: [PATCH 105/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/controller/LogoutController.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/LogoutController.java diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/LogoutController.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/LogoutController.java new file mode 100644 index 0000000..49c46b0 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/controller/LogoutController.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.controller; + +// 导入Hutool工具库中用于字符串操作的工具类,此处用于判断字符串是否为空(空白字符串),方便进行逻辑判断 +import cn.hutool.core.util.StrUtil; +// 导入项目自定义的用于封装服务器响应信息的实体类,包含响应状态码、消息、数据等内容,用于统一向客户端返回响应结果,此处响应数据为Void表示无具体数据返回 +import com.yami.shop.common.response.ServerResponseEntity; +// 导入与安全相关的用于管理Token存储和操作的类,负责对Token进行删除等相关管理操作,此处用于删除用户的登录Token +import com.yami.shop.security.common.manager.TokenStore; +// 导入Swagger相关的注解,用于生成API文档,@Operation注解用于描述接口的功能摘要和详细信息,方便前端开发人员等查看接口的作用和使用方式 +import io.swagger.v3.oas.annotations.Operation; +// 导入Swagger相关的注解,用于给接口分类添加标签,方便文档中进行分组展示和说明,此处标记为"注销"类别 +import io.swagger.v3.oas.annotations.tags.Tag; +// 导入Spring框架用于实现依赖注入的注解,表明后续的属性需要由Spring容器进行注入实例化,此处用于注入TokenStore实例 +import org.springframework.beans.factory.annotation.Autowired; +// 导入Spring框架用于定义处理POST请求的注解,将下面的方法映射为一个接收POST请求的接口,处理对应的业务逻辑,即处理用户退出登录的请求 +import org.springframework.web.bind.annotation.PostMapping; +// 导入Spring框架用于将类标记为RESTful风格的控制器的注解,表明该类中的方法主要用于处理HTTP请求并返回JSON等格式的响应数据 +import org.springframework.web.bind.annotation.RestController; + +// 导入Servlet相关的用于操作HTTP请求的类,用于获取请求头中的信息,此处主要用于获取包含Token的"Authorization"请求头内容 +import jakarta.servlet.http.HttpServletRequest; + +/** + * LogoutController类是一个Spring RESTful风格的控制器类,主要负责处理用户退出登录相关的业务逻辑, + * 通过接收客户端发送的退出登录请求,获取请求头中的Token信息(如果存在),然后调用相关的Token管理组件(TokenStore)删除对应的Token, + * 实现清除用户登录状态以及可能相关的缓存(如菜单缓存等,具体依赖于业务实现)的功能,同时使用Swagger注解生成相关的API文档,便于接口的查看和使用说明。 + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@RestController +@Tag(name = "注销") +public class LogoutController { + + // 通过Spring的依赖注入机制,注入一个TokenStore实例,用于管理Token的删除等相关操作,以实现清除用户登录Token的功能 + @Autowired + private TokenStore tokenStore; + + /** + * 处理用户退出登录请求的接口方法,接收客户端发送的HTTP请求(通过HttpServletRequest对象获取请求相关信息), + * 首先尝试从请求头中获取名为"Authorization"的Token信息,如果获取到的Token信息为空(空白字符串), + * 则直接返回成功的服务器响应实体,表示可以直接视为已退出登录(可能本身就没有有效的登录状态); + * 如果获取到了Token信息,则调用TokenStore的方法删除该用户对应的当前Token,然后返回成功的服务器响应实体,告知客户端已成功退出登录。 + * + * @param request 包含客户端发送的退出登录请求相关信息的HttpServletRequest对象,用于获取请求头中的Token信息等操作 + * @return ServerResponseEntity 返回一个服务器响应实体,响应数据类型为Void表示无具体数据返回,若成功退出登录则状态码为成功状态;若出现异常等情况,相关的异常处理机制会在更上层进行处理并返回合适的错误响应 + */ + @PostMapping("/logOut") + @Operation(summary = "退出登陆", description = "点击退出登陆,清除token,清除菜单缓存") + public ServerResponseEntity logOut(HttpServletRequest request) { + // 从请求头中获取名为"Authorization"的Token信息,通常在登录成功后,客户端会在后续请求的这个请求头中携带Token用于身份验证等操作 + String accessToken = request.getHeader("Authorization"); + // 通过Hutool工具类判断获取到的Token信息是否为空(空白字符串),如果为空则直接返回成功的服务器响应实体,表示可视为已退出登录(可能本身就没登录) + if (StrUtil.isBlank(accessToken)) { + return ServerResponseEntity.success(); + } + + // 如果获取到了有效的Token信息,则调用注入的TokenStore实例的方法,删除该用户在当前系统对应的Token,实现清除登录状态的功能 + tokenStore.deleteCurrentToken(accessToken); + return ServerResponseEntity.success(); + } +} \ No newline at end of file -- 2.34.1 From 58291f167dd1da026bea433d8fde0b607b29a3a4 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 21:29:07 +0800 Subject: [PATCH 106/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MallWebSecurityConfigurerAdapter.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/MallWebSecurityConfigurerAdapter.java diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/MallWebSecurityConfigurerAdapter.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/MallWebSecurityConfigurerAdapter.java new file mode 100644 index 0000000..2b6c87a --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/adapter/MallWebSecurityConfigurerAdapter.java @@ -0,0 +1,59 @@ +package com.yami.shop.security.common.adapter; + +// 导入Spring框架中用于将方法标记为创建Bean的注解,通过该注解定义的方法返回的对象会被Spring容器管理,可在其他地方进行注入使用 +import org.springframework.context.annotation.Bean; +// 导入Spring Security框架中用于配置基于Web的安全相关设置的类,通过链式调用的方式可以配置如认证、授权、跨域、会话管理等多个方面的安全策略 +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +// 导入Spring Security框架中用于启用Web安全功能的注解,表明在当前应用中要开启Spring Security相关的Web安全配置机制 +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +// 导入Spring Security框架中用于定义会话创建策略的枚举类,此处配置为无状态(STATELESS)会话策略,适用于基于Token的认证方式 +import org.springframework.security.config.http.SessionCreationPolicy; +// 导入Spring Security框架中用于构建安全过滤链的核心接口,配置好的安全规则最终会形成这样一个过滤链来处理进入应用的请求,进行安全相关的检查和过滤操作 +import org.springframework.security.web.SecurityFilterChain; +// 导入Spring框架中用于将类标记为组件的注解,表明该类是一个Spring管理的组件,会被自动扫描并实例化,可在其他地方进行依赖注入使用 +import org.springframework.stereotype.Component; +// 导入Spring框架中用于处理跨域相关的工具类,此处用于判断请求是否是跨域预检请求(OPTIONS请求),以便对不同类型请求做不同的授权处理 +import org.springframework.web.cors.CorsUtils; + +/** + * MallWebSecurityConfigurerAdapter类主要用于配置Spring Security在Web应用中的安全策略, + * 其特点是利用了Spring Security的防火墙等安全相关功能,但并不使用Spring Security自带的认证授权登录机制, + * 而是自行处理认证授权逻辑(可能通过其他自定义方式,比如基于Token的方式等),在这个类中重点配置了如跨域、会话管理以及请求的授权规则等方面的内容, + * 通过定义SecurityFilterChain来构建整个安全过滤链,使得进入应用的请求按照设定的规则进行相应的安全处理。 + * + * @author 菠萝凤梨 + * @date 2022/3/25 17:33 + */ +@Component +@EnableWebSecurity +public class MallWebSecurityConfigurerAdapter { + + /** + * 定义一个名为filterChain的方法,并通过@Bean注解将其返回的SecurityFilterChain对象交由Spring容器管理,成为一个可被注入使用的Bean。 + * 这个方法用于构建安全过滤链,配置了一系列的安全相关规则,具体如下: + * + * @param http HttpSecurity对象,用于以链式调用的方式配置基于HTTP请求的安全策略,如认证、授权、跨域、会话管理等各个方面 + * @return SecurityFilterChain 返回配置好的安全过滤链对象,该过滤链会应用到整个Web应用中,对进入的请求进行安全相关的处理和过滤 + * @throws Exception 如果在配置HttpSecurity过程中出现异常(比如配置的规则不符合要求等情况),则会抛出异常,需要进行相应的处理 + */ + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + // 关闭跨站请求伪造(CSRF)防护功能,因为基于Token的认证方式下通常不需要CSRF防护,Token本身就用于身份验证,可避免此类安全风险 + return http.csrf().disable().cors() + + // 配置跨域相关设置,启用跨域支持(后续可能还需要配置更具体的跨域规则等,这里只是开启了基本的支持), + // 接着配置会话管理相关内容,将会话创建策略设置为无状态(STATELESS),意味着服务器不会为客户端创建和维护会话状态, + // 适用于基于Token的认证场景,每次请求都通过携带Token来验证身份,服务器不依赖会话来识别客户端 + .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + + // 配置请求授权相关规则,对于跨域预检请求(OPTIONS请求,通过CorsUtils::isPreFlightRequest判断),允许所有的此类请求通过, + // 因为跨域预检请求主要是浏览器在正式发起跨域请求前进行的一种询问服务器是否允许跨域的请求,通常需要放行 + .and().authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll() + + // 继续配置请求授权规则,对于所有其他请求(通过"/**"表示任意路径的请求),也都允许通过,这意味着在这个配置下,没有进行严格的基于Spring Security自带认证授权的访问限制, + // 可能后续会通过其他自定义的中间件或者业务逻辑来对请求进行更细致的权限判断等操作,此处只是简单放开了所有请求的访问权限 + .and().authorizeRequests().requestMatchers( + "/**").permitAll().and().build(); + } +} \ No newline at end of file -- 2.34.1 From 590a8ff6b2a2fef9d67fc1ea38a47b71a7e89e3e Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 21:36:00 +0800 Subject: [PATCH 107/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/common/util/PageAdapter.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/util/PageAdapter.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/util/PageAdapter.java b/yami-shop-common/src/main/java/com/yami/shop/common/util/PageAdapter.java new file mode 100644 index 0000000..54b48d8 --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/util/PageAdapter.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.common.util; + +// 导入Hutool工具库中用于分页相关操作的工具类,这里主要用于将页码、每页数量等信息转换为数据库查询中起始位置和结束位置的相关操作 +import cn.hutool.core.util.PageUtil; +// 导入MyBatis Plus框架中用于表示分页信息的核心类,包含了当前页码、每页显示数量等分页相关的属性和方法 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +// 引入Lombok的Data注解,通过该注解,编译器会自动帮我们生成类的常用方法,比如各属性的Getter、Setter方法,以及toString、equals、hashCode方法等,简化代码编写 +import lombok.Data; + +/** + * PageAdapter类主要起到了一个适配的作用,用于将MyBatis Plus框架中的分页对象(Page)的相关信息, + * 转换为适用于其他分页逻辑或者数据库查询中分页参数的表示形式, + * 具体是将基于页码和每页数量的分页信息转换为起始位置(begin)和每页数量(size)的形式,方便后续在数据库查询等场景中使用, + * 借助Hutool工具库的分页相关功能来实现这种转换操作。 + * + * @author lh + */ +@Data +public class PageAdapter { + + // 用于表示分页查询时的起始位置(通常对应数据库查询中的偏移量,从第几条记录开始查询) + private int begin; + + // 用于表示分页查询时每页的记录数量,即每页显示多少条数据 + private int size; + + /** + * 构造方法,接收一个MyBatis Plus框架中的Page对象作为参数, + * 通过调用Hutool工具库的PageUtil工具类的transToStartEnd方法, + * 将Page对象中的当前页码和每页数量信息转换为对应的起始位置和每页数量信息,分别赋值给类中的begin和size属性,完成适配转换操作。 + * + * @param page MyBatis Plus框架中的Page对象,包含了当前页码、每页显示数量等分页相关的属性信息,通过这些信息来计算出合适的begin和size值 + */ + public PageAdapter(Page page) { + // 调用Hutool的PageUtil工具类的transToStartEnd方法,将当前页码(需要减1,因为数据库查询中页码通常从0开始计数)和每页数量转换为起始位置和结束位置的数组,这里取数组的第一个元素作为起始位置(begin) + int[] startEnd = PageUtil.transToStartEnd((int) page.getCurrent() - 1, (int) page.getSize()); + this.begin = startEnd[0]; + // 将传入的Page对象中的每页数量赋值给size属性,作为每页的记录数量 + this.size = (int) page.getSize(); + } +} \ No newline at end of file -- 2.34.1 From 143989191cd8cc2d6f0b334ee86f687c131b4e36 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Tue, 17 Dec 2024 21:42:50 +0800 Subject: [PATCH 108/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/common/util/PageParam.java | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 yami-shop-common/src/main/java/com/yami/shop/common/util/PageParam.java diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/util/PageParam.java b/yami-shop-common/src/main/java/com/yami/shop/common/util/PageParam.java new file mode 100644 index 0000000..9951090 --- /dev/null +++ b/yami-shop-common/src/main/java/com/yami/shop/common/util/PageParam.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.common.util; + +// 导入MyBatis Plus框架中用于表示分页信息的核心类,包含了分页相关的属性(如当前页、每页大小等)以及操作方法(如获取记录列表、设置总数等) +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +// 导入Jackson相关的注解,用于在JSON序列化和反序列化过程中忽略指定的属性,这里用于标记某些属性在转换为JSON时不进行处理 +import com.fasterxml.jackson.annotation.JsonIgnore; +// 导入Swagger相关的注解,用于在生成API文档时隐藏某些属性,使得这些属性不在文档中展示出来,一般用于内部使用或不需要对外暴露的属性 +import io.swagger.v3.oas.annotations.Hidden; +// 导入Swagger相关的注解,用于在生成API文档时为类、属性等添加描述信息,方便前端开发人员等查看其含义和作用 +import io.swagger.v3.oas.annotations.media.Schema; +// 导入SpringDoc相关的注解,用于标记该类作为参数对象,在生成API文档以及进行参数校验等场景下可以被识别和处理 +import org.springdoc.core.annotations.ParameterObject; + +import java.util.List; + +/** + * PageParam类是一个泛型类,继承自MyBatis Plus的Page类,主要用于扩展和定制分页相关的功能及属性。 + * 它在原有Page类的基础上,重新定义了一些分页属性(如每页显示条数、当前页等)的默认值,并对部分属性添加了文档注释以及JSON序列化相关的控制, + * 同时重写了Page类中的一些获取和设置属性的方法,以满足特定业务场景下对分页参数更灵活的控制和处理需求, + * 例如限制每页最大显示条数、根据总数情况判断是否进行count查询等功能。 + * + * @author lanhai + */ +@Schema +@ParameterObject +public class PageParam extends Page { + + /** + * 每页显示条数属性,用于指定每页展示的数据记录数量,默认值设置为10。 + * 通过@Schema注解添加了描述信息,在生成API文档时可以展示该属性的含义,方便使用者了解其作用。 + */ + @Schema(description = "每页大小,默认10") + private long size = 10; + + /** + * 当前页属性,用于表示当前所处的页码,默认值设置为1,同样通过@Schema注解添加了描述信息以便在API文档中展示其含义。 + */ + @Schema(description = "当前页,默认1") + private long current = 1; + + /** + * 用于存储查询到的数据记录列表,通过@Hidden注解标记该属性在生成API文档时隐藏起来,因为该属性通常是内部使用,不需要对外展示具体的数据内容。 + */ + @Hidden + private List records; + + /** + * 用于记录符合查询条件的数据总条数,通过@Hidden注解在生成API文档时隐藏该属性,它主要在内部用于分页相关的计算和判断等操作。 + */ + @Hidden + private long total = 0; + + /** + * 是否进行count查询的标志属性,用于控制在分页查询时是否需要统计符合条件的数据总条数,默认值为true,表示默认进行count查询。 + * 通过@JsonIgnore注解标记该属性,在将对象转换为JSON时忽略该属性,因为它通常不需要在对外传输的数据中体现。 + */ + @JsonIgnore + private boolean isSearchCount = true; + + /** + * 一个用于特定场景的属性(具体功能可能根据业务而定),通过@JsonIgnore注解标记在JSON序列化时忽略该属性,一般是内部使用的标识信息等情况。 + */ + @JsonIgnore + private String countId; + + /** + * 用于限制每页最大显示条数的属性(具体值在这里设置为100),通过@JsonIgnore注解在JSON序列化时忽略它,主要在内部用于对每页大小进行限制调整。 + */ + @JsonIgnore + private Long maxLimit; + + /** + * 用于优化count查询SQL语句相关的属性(具体优化逻辑取决于具体业务实现),通过@JsonIgnore注解在JSON序列化时忽略它,也是内部使用的一个标识属性。 + */ + @JsonIgnore + private boolean optimizeCountSql; + + /** + * 重写了父类Page中获取记录列表的方法,返回当前对象中存储的记录列表(records属性),用于在业务逻辑中获取查询到的数据内容。 + * + * @return List 返回存储的数据记录列表,类型与泛型参数T一致,包含了符合查询条件的具体数据记录。 + */ + @Override + public List getRecords() { + return this.records; + } + + /** + * 重写了父类Page中设置记录列表的方法,将传入的记录列表赋值给当前对象的records属性,并返回当前对象(this),方便进行链式调用, + * 用于在查询到数据后更新对象中的记录列表信息,以便后续进行分页相关的处理和展示等操作。 + * + * @param records 要设置的数据记录列表,类型与泛型参数T一致,通常是从数据库查询得到的符合条件的数据集合。 + * @return Page 返回当前对象本身,可用于链式调用,例如继续调用其他设置属性的方法等操作。 + */ + @Override + public Page setRecords(List records) { + this.records = records; + return this; + } + + /** + * 重写了父类Page中获取数据总条数的方法,返回当前对象中存储的总条数(total属性),用于在分页相关的计算(如计算总页数等)以及业务判断中使用。 + * + * @return long 返回符合查询条件的数据总条数,用于后续的分页逻辑处理。 + */ + @Override + public long getTotal() { + return this.total; + } + + /** + * 重写了父类Page中设置数据总条数的方法,将传入的总条数赋值给当前对象的total属性,并返回当前对象(this),方便进行链式调用, + * 用于在获取到准确的总条数后更新对象中的相关信息,例如在执行完count查询后更新总条数用于后续的分页展示等操作。 + * + * @param total 要设置的数据总条数,通过数据库查询或其他方式统计得到的符合条件的数据记录总数。 + * @return Page 返回当前对象本身,可用于链式调用,例如继续调用其他设置属性的方法等操作。 + */ + @Override + public Page setTotal(long total) { + this.total = total; + return this; + } + + /** + * 获取是否进行count查询的方法,在原有的isSearchCount属性基础上进行了额外的判断逻辑。 + * 如果数据总条数(total属性)小于0,则返回false,表示不进行count查询(可能是因为数据不符合要求等情况); + * 否则返回isSearchCount属性的值,即按照默认或之前设置的值来确定是否进行count查询,用于在分页查询前根据实际情况灵活决定是否需要统计总数。 + * + * @return boolean 返回是否进行count查询的结果,true表示进行count查询,false表示不进行,根据具体情况判断。 + */ + @JsonIgnore + public boolean getSearchCount() { + if (total < 0) { + return false; + } + return isSearchCount; + } + + /** + * 重写了父类Page中设置是否进行count查询的方法,将传入的布尔值赋值给当前对象的isSearchCount属性,并返回当前对象(this),方便进行链式调用, + * 用于在业务逻辑中根据不同需求动态设置是否需要进行count查询操作,例如在某些特定条件下不需要统计总数时可以通过该方法进行设置。 + * + * @param isSearchCount 要设置的是否进行count查询的布尔值,true表示进行,false表示不进行。 + * @return Page 返回当前对象本身,可用于链式调用,例如继续调用其他设置属性的方法等操作。 + */ + @Override + public Page setSearchCount(boolean isSearchCount) { + this.isSearchCount = isSearchCount; + return this; + } + + /** + * 重写了父类Page中获取每页显示条数的方法,返回当前对象中存储的每页显示条数(size属性),用于在分页相关的计算(如计算偏移量等)以及业务判断中使用。 + * + * @return long 返回每页显示的数据记录数量,按照默认值或之前设置的值来确定,可用于后续的分页逻辑处理。 + */ + @Override + public long getSize() { + return this.size; + } + + /** + * 重写了父类Page中设置每页显示条数的方法,对传入的每页显示条数进行了限制处理。 + * 如果传入的每页显示条数(size参数)大于预先设定的最大限制值(这里设置为100),则将当前对象的size属性设置为最大限制值(100),避免每页显示过多数据; + * 否则将传入的size值赋值给当前对象的size属性,用于在业务逻辑中根据实际需求动态设置每页显示的合理数量,然后返回当前对象(this)方便进行链式调用。 + * + * @param size 要设置的每页显示条数,通过前端传入或业务逻辑指定的每页希望展示的数据记录数量。 + * @return Page 返回当前对象本身,可用于链式调用,例如继续调用其他设置属性的方法等操作。 + */ + @Override + public Page setSize(long size) { + int maxSize = 100; + if (size > maxSize) { + this.size = maxSize; + } else { + this.size = size; + } + return this; + } + + /** + * 重写了父类Page中获取当前页的方法,返回当前对象中存储的当前页(current属性),用于在分页相关的计算(如计算偏移量等)以及业务判断中使用。 + * + * @return long 返回当前所处的页码,按照默认值或之前设置的值来确定,可用于后续的分页逻辑处理。 + */ + @Override + public long getCurrent() { + return this.current; + } + + /** + * 重写了父类Page中设置当前页的方法,将传入的当前页数值赋值给当前对象的current属性,并返回当前对象(this),方便进行链式调用, + * 用于在业务逻辑中根据实际需求动态设置当前所处的页码,例如响应前端传入的页码切换请求等操作。 + * + * @param current 要设置的当前页数值,通过前端传入或业务逻辑指定的当前希望展示的页码。 + * @return Page 返回当前对象本身,可用于链式调用,例如继续调用其他设置属性的方法等操作。 + */ + @Override + public Page setCurrent(long current) { + this.current = current; + return this; + } +} \ No newline at end of file -- 2.34.1 From 6807ab51c430fc6c250109aee7d576a62cad1ab6 Mon Sep 17 00:00:00 2001 From: lzy <1768422698@qq.com> Date: Wed, 18 Dec 2024 20:46:22 +0800 Subject: [PATCH 109/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/manager/PasswordManager.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordManager.java diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordManager.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordManager.java new file mode 100644 index 0000000..c72e8b8 --- /dev/null +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordManager.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ +package com.yami.shop.security.common.manager; + +import cn.hutool.crypto.symmetric.AES; +import com.yami.shop.common.exception.YamiShopBindException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; + +/** + * @author 菠萝凤梨 + * @date 2022/1/19 16:02 + */ +@Component +public class PasswordManager { + private static final Logger logger = LoggerFactory.getLogger(PasswordManager.class); + + /** + * 用于aes签名的key,16位 + */ + @Value("${auth.password.signKey:-mall4j-password}") + public String passwordSignKey; + + public String decryptPassword(String data) { + // 在使用oracle的JDK时,JAR包必须签署特殊的证书才能使用。 + // 解决方案 1.使用openJDK或者非oracle的JDK(建议) 2.添加证书 + // hutool的aes报错可以打开下面那段代码 + // SecureUtil.disableBouncyCastle(); + AES aes = new AES(passwordSignKey.getBytes(StandardCharsets.UTF_8)); + String decryptStr; + String decryptPassword; + try { + decryptStr = aes.decryptStr(data); + decryptPassword = decryptStr.substring(13); + } catch (Exception e) { + logger.error("Exception:", e); + throw new YamiShopBindException("AES解密错误", e); + } + return decryptPassword; + } +} -- 2.34.1 From dc558d7aaa42bc4630b1b9b61ab567268ae622b1 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:32:17 +0800 Subject: [PATCH 110/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/dao/DeliveryMapper.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/dao/DeliveryMapper.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/dao/DeliveryMapper.java b/yami-shop-service/src/main/java/com/yami/shop/dao/DeliveryMapper.java new file mode 100644 index 0000000..8d64b0f --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/dao/DeliveryMapper.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义包名 +package com.yami.shop.dao; + +// 导入所需的类 +import com.yami.shop.bean.model.Delivery; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * DeliveryMapper 接口的文档注释 + * 这里可以描述这个接口的作用,比如它是用于数据库操作的Mapper接口 + * + * @author lanhai 表示这个类的作者是lanhai + */ +public interface DeliveryMapper extends BaseMapper { + // 这个接口继承了BaseMapper接口,泛型参数为Delivery,表示这个Mapper与Delivery实体类相关联 + // 在这个接口中,可以定义与Delivery表相关的数据库操作方法 + // 由于继承了BaseMapper,已经包含了很多基础的数据库操作方法,如增删改查 + // 如果需要额外的自定义方法,可以在这个接口中声明,然后在对应的XML文件或使用MyBatis Plus的注解方式实现 +} \ No newline at end of file -- 2.34.1 From c3c95db9e04239d0f539ffbdd8220aacce16e8a7 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:32:27 +0800 Subject: [PATCH 111/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/mapper/DeliveryMapper.xml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 yami-shop-service/src/main/resources/mapper/DeliveryMapper.xml diff --git a/yami-shop-service/src/main/resources/mapper/DeliveryMapper.xml b/yami-shop-service/src/main/resources/mapper/DeliveryMapper.xml new file mode 100644 index 0000000..fd16915 --- /dev/null +++ b/yami-shop-service/src/main/resources/mapper/DeliveryMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + \ No newline at end of file -- 2.34.1 From a902a9da7a3805f8285d13f7f63f7357ac46bb84 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:33:20 +0800 Subject: [PATCH 112/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/service/DeliveryService.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/DeliveryService.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/DeliveryService.java b/yami-shop-service/src/main/java/com/yami/shop/service/DeliveryService.java new file mode 100644 index 0000000..237a40b --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/DeliveryService.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义包名,表示这个类属于com.yami.shop.service包 +package com.yami.shop.service; + +// 导入MyBatis Plus框架中的IService接口,这个接口提供了基本的CRUD操作 +import com.baomidou.mybatisplus.extension.service.IService; +// 导入项目中定义的Delivery实体类 +import com.yami.shop.bean.model.Delivery; + +/** + * DeliveryService接口的文档注释 + * 这个接口是用于定义与Delivery实体类相关的业务操作 + * + * @author lgh on 2018/11/26 表示这个接口是由lgh在2018年11月26日创建的 + */ +public interface DeliveryService extends IService { + + // 这个接口继承了IService接口,泛型参数为Delivery,表示这个服务接口与Delivery实体类相关联 + // 在这个接口中,可以定义与Delivery实体类相关的额外业务操作方法 + // 由于继承了IService,已经包含了很多基础的CRUD操作,如增删改查 +} -- 2.34.1 From 07bb375518474875912724aa95026a47d8f322a0 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:34:09 +0800 Subject: [PATCH 113/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/DeliveryServiceImpl.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/impl/DeliveryServiceImpl.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/DeliveryServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/DeliveryServiceImpl.java new file mode 100644 index 0000000..c386291 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/DeliveryServiceImpl.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义包名,表示这个类属于com.yami.shop.service.impl包 +package com.yami.shop.service.impl; + +// 导入MyBatis Plus框架中的ServiceImpl类,提供基础的CRUD操作实现 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +// 导入Spring框架的Autowired注解,用于自动注入依赖的组件 +import org.springframework.beans.factory.annotation.Autowired; +// 导入Spring框架的Service注解,用于声明这是一个服务组件 +import org.springframework.stereotype.Service; + +// 导入项目中定义的Delivery实体类 +import com.yami.shop.bean.model.Delivery; +// 导入与Delivery实体类相关的Mapper接口 +import com.yami.shop.dao.DeliveryMapper; +// 导入定义的服务接口 +import com.yami.shop.service.DeliveryService; + +/** + * DeliveryServiceImpl类的文档注释 + * 这个类是DeliveryService接口的实现类,提供了与Delivery实体类相关的业务操作实现 + * + * @author lgh on 2018/11/26 表示这个类是由lgh在2018年11月26日创建的 + */ +// 使用Spring的Service注解标注这个类为一个服务组件,这样Spring容器可以自动扫描并管理这个类的实例 +@Service +public class DeliveryServiceImpl extends ServiceImpl implements DeliveryService { + + // 使用Autowired注解自动注入DeliveryMapper的实例 + @Autowired + private DeliveryMapper deliveryMapper; + + // 这里可以添加额外的业务逻辑方法,或者覆盖ServiceImpl中的方法以提供定制化的实现 +} -- 2.34.1 From ff5d5aa8c1f7e42198602f1f9f14c1f27ffdf7aa Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:35:28 +0800 Subject: [PATCH 114/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/HotSearchServiceImpl.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/impl/HotSearchServiceImpl.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/HotSearchServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/HotSearchServiceImpl.java new file mode 100644 index 0000000..803a53b --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/HotSearchServiceImpl.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义包名,表示这个类属于com.yami.shop.service.impl包 +package com.yami.shop.service.impl; + +// 导入MyBatis Plus框架中的ServiceImpl类,提供基础的CRUD操作实现 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +// 导入Spring框架的缓存注解 +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +// 导入Spring框架的Service注解,用于声明这是一个服务组件 +import org.springframework.stereotype.Service; + +// 导入项目中定义的HotSearchDto数据传输对象 +import com.yami.shop.bean.dto.HotSearchDto; +// 导入项目中定义的HotSearch实体类 +import com.yami.shop.bean.model.HotSearch; +// 导入Spring框架的Autowired注解,用于自动注入依赖的组件 +import org.springframework.beans.factory.annotation.Autowired; +// 导入与HotSearch实体类相关的Mapper接口 +import com.yami.shop.dao.HotSearchMapper; +// 导入定义的服务接口 +import com.yami.shop.service.HotSearchService; +// 导入Java.util.List接口,用于返回列表类型的数据 +import java.util.List; + +/** + * HotSearchServiceImpl类的文档注释 + * 这个类是HotSearchService接口的实现类,提供了与HotSearch实体类相关的业务操作实现 + * + * @author lgh on 2019/03/27 表示这个类是由lgh在2019年3月27日创建的 + */ +// 使用Spring的Service注解标注这个类为一个服务组件,这样Spring容器可以自动扫描并管理这个类的实例 +@Service +public class HotSearchServiceImpl extends ServiceImpl implements HotSearchService { + + // 使用Autowired注解自动注入HotSearchMapper的实例 + @Autowired + private HotSearchMapper hotSearchMapper; + + /** + * 根据店铺ID获取热点搜索DTO列表的方法 + * 这个方法使用@Cacheable注解,表示其返回结果将被缓存,缓存名称为"HotSearchDto",键为方法参数shopId + * @param shopId 店铺ID + * @return 根据店铺ID获取的热点搜索DTO列表 + */ + @Override + @Cacheable(cacheNames = "HotSearchDto", key = "#shopId") + public List getHotSearchDtoByShopId(Long shopId) { + // 调用hotSearchMapper的同名方法获取数据 + return hotSearchMapper.getHotSearchDtoByShopId(shopId); + } + + /** + * 根据店铺ID移除热点搜索DTO缓存的方法 + * 这个方法使用@CacheEvict注解,表示将从缓存中移除指定的缓存项,缓存名称为"HotSearchDto",键为方法参数shopId + * @param shopId 店铺ID + */ + @Override + @CacheEvict(cacheNames = "HotSearchDto", key = "#shopId") + public void removeHotSearchDtoCacheByShopId(Long shopId) { + // 这里可以添加额外的逻辑,比如通知缓存管理器移除缓存 + } +} -- 2.34.1 From 193c03408d5b17c21bc3c2e214753de1d626a64b Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:36:43 +0800 Subject: [PATCH 115/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/mapper/IndexImgMapper.xml | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 yami-shop-service/src/main/resources/mapper/IndexImgMapper.xml diff --git a/yami-shop-service/src/main/resources/mapper/IndexImgMapper.xml b/yami-shop-service/src/main/resources/mapper/IndexImgMapper.xml new file mode 100644 index 0000000..bd93219 --- /dev/null +++ b/yami-shop-service/src/main/resources/mapper/IndexImgMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + delete from tz_index_img where img_id in + + + #{id} + + + + + + \ No newline at end of file -- 2.34.1 From 6d65c85869c6ea2674468a7ab6af0e013f79061d Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:37:49 +0800 Subject: [PATCH 116/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/service/IndexImgService.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/IndexImgService.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/IndexImgService.java b/yami-shop-service/src/main/java/com/yami/shop/service/IndexImgService.java new file mode 100644 index 0000000..1c938a7 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/IndexImgService.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义包名,表示这个类属于com.yami.shop.service包 +package com.yami.shop.service; + +// 导入MyBatis Plus框架中的IService接口,提供基础的CRUD操作 +import com.baomidou.mybatisplus.extension.service.IService; +// 导入项目中定义的IndexImg实体类 +import com.yami.shop.bean.model.IndexImg; +// 导入Java.util.List接口,用于返回列表类型的数据 +import java.util.List; + +/** + * IndexImgService接口的文档注释 + * 这个接口是用于定义与IndexImg实体类相关的业务操作 + * + * @author lgh on 2018/11/26 表示这个接口是由lgh在2018年11月26日创建的 + */ +public interface IndexImgService extends IService { + + /** + * 根据id列表删除图片的方法 + * 这个方法允许传入一个Long类型的数组,表示要删除的图片的ID列表 + * + * @param ids 要删除的图片ID数组 + */ + void deleteIndexImgByIds(Long[] ids); + + /** + * 获取全部图片列表的方法 + * 这个方法返回一个List类型的数据,包含了所有的图片实体 + * + * @return 包含所有图片的列表 + */ + List listIndexImg(); + + /** + * 删除图片缓存的方法 + * 这个方法用于清除与图片相关的缓存,以确保数据的一致性 + */ + void removeIndexImgCache(); +} -- 2.34.1 From 0f71af50d1702217a78842a67deb22c4543cea83 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:38:09 +0800 Subject: [PATCH 117/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/dao/IndexImgMapper.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/dao/IndexImgMapper.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/dao/IndexImgMapper.java b/yami-shop-service/src/main/java/com/yami/shop/dao/IndexImgMapper.java new file mode 100644 index 0000000..6666a6f --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/dao/IndexImgMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yami.shop.bean.model.IndexImg; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * IndexImgMapper 是一个数据访问层接口,用于定义对IndexImg实体进行数据库操作的方法。 + * 它继承自MyBatis-Plus提供的BaseMapper,从而获得了一系列默认的CRUD方法。 + * @author lanhai + */ +public interface IndexImgMapper extends BaseMapper { + + /** + * deleteIndexImgByIds 方法用于根据给定的ID列表批量删除图片记录。 + * @param ids 一个或多个要删除的图片记录的ID数组。 + */ + void deleteIndexImgByIds(@Param("ids") Long[] ids); + + /** + * listIndexImg 方法用于获取所有的图片记录列表。 + * @return 返回一个包含所有IndexImg对象的列表。 + */ + List listIndexImg(); +} -- 2.34.1 From 5ce83e1a26bb5cfcd43eb56e6d9ebcd66b51166a Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:39:07 +0800 Subject: [PATCH 118/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/IndexImgServiceImpl.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/impl/IndexImgServiceImpl.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/IndexImgServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/IndexImgServiceImpl.java new file mode 100644 index 0000000..546c705 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/IndexImgServiceImpl.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import com.yami.shop.bean.model.IndexImg; +import com.yami.shop.dao.IndexImgMapper; +import com.yami.shop.service.IndexImgService; + +import java.util.List; + +/** + * IndexImgServiceImpl 是IndexImgService接口的具体实现类。 + * 它继承自MyBatis-Plus提供的ServiceImpl,并实现了IndexImgService接口中定义的方法。 + * 该服务负责处理与首页图片(IndexImg)相关的业务逻辑。 + * + * @author lgh on 2018/11/26. + */ +@Service +public class IndexImgServiceImpl extends ServiceImpl implements IndexImgService { + + /** + * indexImgMapper是通过Spring自动装配的DAO层组件, + * 用于执行对IndexImg实体对应的数据库表的操作。 + */ + @Autowired + private IndexImgMapper indexImgMapper; + + /** + * deleteIndexImgByIds方法实现了根据给定ID列表批量删除首页图片的功能。 + * 它调用了indexImgMapper中的deleteIndexImgByIds方法来执行实际的删除操作。 + */ + @Override + public void deleteIndexImgByIds(Long[] ids) { + indexImgMapper.deleteIndexImgByIds(ids); + } + + /** + * listIndexImg方法返回所有首页图片的列表。 + * 使用了@Cacheable注解以启用Spring Cache缓存机制, + * 这样可以避免重复查询数据库,提高性能。 + * 当首次调用此方法时,结果会被缓存起来; + * 后续相同请求将直接从缓存中读取数据,直到缓存失效或被手动清除。 + */ + @Override + @Cacheable(cacheNames = "indexImg", key = "'indexImg'") + public List listIndexImg() { + return indexImgMapper.listIndexImg(); + } + + /** + * removeIndexImgCache方法用于清除首页图片的缓存。 + * 使用了@CacheEvict注解,当调用此方法时会清除指定名称的缓存, + * 确保在需要的时候缓存内容是最新的。 + */ + @Override + @CacheEvict(cacheNames = "indexImg", key = "'indexImg'") + public void removeIndexImgCache() { + // 方法体为空,因为实际的缓存清除工作由@CacheEvict注解完成。 + } +} -- 2.34.1 From d9b5d43575ddcc775d2bdcadd8ae330b611d4eca Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:39:28 +0800 Subject: [PATCH 119/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yami/shop/service/HotSearchService.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/HotSearchService.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/HotSearchService.java b/yami-shop-service/src/main/java/com/yami/shop/service/HotSearchService.java new file mode 100644 index 0000000..2705662 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/HotSearchService.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义包名,表示这个接口属于com.yami.shop.service包 +package com.yami.shop.service; + +// 导入MyBatis Plus框架中的IService接口,提供基础的CRUD操作 +import com.baomidou.mybatisplus.extension.service.IService; +// 导入项目中定义的HotSearchDto数据传输对象 +import com.yami.shop.bean.dto.HotSearchDto; +// 导入项目中定义的HotSearch实体类 +import com.yami.shop.bean.model.HotSearch; +// 导入Java.util.List接口,用于返回列表类型的数据 +import java.util.List; + +/** + * HotSearchService接口的文档注释 + * 这个接口是用于定义与HotSearch实体类相关的业务操作 + * + * @author lgh on 2019/03/27 表示这个接口是由lgh在2019年3月27日创建的 + */ +public interface HotSearchService extends IService { + + /** + * 根据店铺ID获取热搜列表的方法 + * 这个方法允许传入一个Long类型的店铺ID,返回一个包含热搜数据的HotSearchDto列表 + * + * @param shopId 店铺ID + * @return 根据店铺ID获取的热搜列表 + */ + List getHotSearchDtoByShopId(Long shopId); + + /** + * 根据店铺ID删除热搜缓存的方法 + * 这个方法允许传入一个Long类型的店铺ID,用于清除与该店铺ID相关的热搜缓存 + * + * @param shopId 店铺ID + */ + void removeHotSearchDtoCacheByShopId(Long shopId); +} -- 2.34.1 From 582414af9d53f92ab959c57019d32d96dc8ab9ea Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:40:18 +0800 Subject: [PATCH 120/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/dao/HotSearchMapper.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/dao/HotSearchMapper.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/dao/HotSearchMapper.java b/yami-shop-service/src/main/java/com/yami/shop/dao/HotSearchMapper.java new file mode 100644 index 0000000..7bd9af5 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/dao/HotSearchMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yami.shop.bean.dto.HotSearchDto; +import com.yami.shop.bean.model.HotSearch; + +import java.util.List; + +/** + * HotSearchMapper 是一个数据访问层接口,继承自MyBatis-Plus提供的BaseMapper, + * 提供对HotSearch实体的基本CRUD操作方法。 + * 此外,它还定义了一个特定的方法用于根据店铺ID获取热搜列表。 + * + * @author lanhai + */ +public interface HotSearchMapper extends BaseMapper { + + /** + * getHotSearchDtoByShopId 方法用于根据给定的店铺ID获取该店铺的热搜列表。 + * 它返回的是一个HotSearchDto对象的列表,而不是直接返回数据库中的HotSearch实体对象。 + * 这样做通常是为了能够在不改变数据库表结构的情况下调整API输出格式,或者为了减少不必要的数据传输。 + * + * @param shopId 店铺的唯一标识符,用于确定要查询的热搜信息所属的店铺。 + * @return 返回一个包含指定店铺热搜信息的HotSearchDto对象列表。 + */ + List getHotSearchDtoByShopId(Long shopId); +} \ No newline at end of file -- 2.34.1 From 0ba47f9361f96b1541080ab0816c714db629de1a Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:40:46 +0800 Subject: [PATCH 121/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/mapper/HotSearchMapper.xml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 yami-shop-service/src/main/resources/mapper/HotSearchMapper.xml diff --git a/yami-shop-service/src/main/resources/mapper/HotSearchMapper.xml b/yami-shop-service/src/main/resources/mapper/HotSearchMapper.xml new file mode 100644 index 0000000..5908e45 --- /dev/null +++ b/yami-shop-service/src/main/resources/mapper/HotSearchMapper.xml @@ -0,0 +1,26 @@ + + + + + + + + + + \ No newline at end of file -- 2.34.1 From 5c2ea01afc8611d34dfc0788421c3ca51d417846 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:41:10 +0800 Subject: [PATCH 122/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yami/shop/dao/MessageMapper.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/dao/MessageMapper.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/dao/MessageMapper.java b/yami-shop-service/src/main/java/com/yami/shop/dao/MessageMapper.java new file mode 100644 index 0000000..d6cd081 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/dao/MessageMapper.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.dao; + +import com.yami.shop.bean.model.Message; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * MessageMapper 是一个数据访问层接口,用于定义针对Message实体的操作方法。 + * 它继承自MyBatis-Plus提供的BaseMapper,因此默认实现了对Message表的基本CRUD功能。 + * + * 如果需要额外的数据库查询或修改操作,可以在本接口中添加相应的方法,并在对应的XML映射文件或使用@Select等注解实现这些方法。 + * + * @author lanhai + */ +public interface MessageMapper extends BaseMapper { + // 本接口当前没有定义额外的方法,所有操作都依赖于BaseMapper提供的默认方法。 +} \ No newline at end of file -- 2.34.1 From 69db425b2efc4c8ce4e57d8e7e48f5460c0df0cc Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:41:37 +0800 Subject: [PATCH 123/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/mapper/MessageMapper.xml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 yami-shop-service/src/main/resources/mapper/MessageMapper.xml diff --git a/yami-shop-service/src/main/resources/mapper/MessageMapper.xml b/yami-shop-service/src/main/resources/mapper/MessageMapper.xml new file mode 100644 index 0000000..6ecdda3 --- /dev/null +++ b/yami-shop-service/src/main/resources/mapper/MessageMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file -- 2.34.1 From 2ff1e41a17da44423ef2c2ac45f316d35626c7b1 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:42:06 +0800 Subject: [PATCH 124/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/service/MessageService.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/MessageService.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/MessageService.java b/yami-shop-service/src/main/java/com/yami/shop/service/MessageService.java new file mode 100644 index 0000000..d9361e2 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/MessageService.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yami.shop.bean.model.Message; + +/** + * MessageService 是一个服务层接口,用于定义针对Message实体的业务逻辑方法。 + * 它继承自MyBatis-Plus提供的IService,因此默认实现了对Message表的基本CRUD功能。 + * + * 通过实现此接口,开发者可以在相应的ServiceImpl类中添加额外的业务逻辑, + * 或者重写已有的方法以满足特定需求。 + * + * @author lgh on 2018/10/15. + */ +public interface MessageService extends IService { + + // 本接口当前没有定义额外的方法,所有操作都依赖于IService提供的默认方法。 + // 如果需要增加特定的业务逻辑方法,可以在本接口中添加相应的方法签名。 +} -- 2.34.1 From 4df9185dd8b6cbdbe03e05eef5de4577dcccce45 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:42:38 +0800 Subject: [PATCH 125/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/service/impl/MessageServiceImpl.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/impl/MessageServiceImpl.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/MessageServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/MessageServiceImpl.java new file mode 100644 index 0000000..efe1c17 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/MessageServiceImpl.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义包名,表示这个类属于com.yami.shop.service.impl包 +package com.yami.shop.service.impl; + +// 导入MyBatis Plus框架中的ServiceImpl类,提供基础的CRUD操作实现 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +// 导入Spring框架的Autowired注解,用于自动注入依赖的组件 +import org.springframework.beans.factory.annotation.Autowired; +// 导入Spring框架的Service注解,用于声明这是一个服务组件 +import org.springframework.stereotype.Service; + +// 导入项目中定义的Message实体类 +import com.yami.shop.bean.model.Message; +// 导入与Message实体类相关的Mapper接口 +import com.yami.shop.dao.MessageMapper; +// 导入定义的服务接口 +import com.yami.shop.service.MessageService; + +/** + * MessageServiceImpl类的文档注释 + * 这个类是MessageService接口的实现类,提供了与Message实体类相关的业务操作实现 + * + * @author lgh on 2018/10/15 表示这个类是由lgh在2018年10月15日创建的 + */ +// 使用Spring的Service注解标注这个类为一个服务组件,这样Spring容器可以自动扫描并管理这个类的实例 +@Service +public class MessageServiceImpl extends ServiceImpl implements MessageService { + + // 使用Autowired注解自动注入MessageMapper的实例 + @Autowired + private MessageMapper messageMapper; + + // 这里可以添加额外的业务逻辑方法,或者覆盖ServiceImpl中的方法以提供定制化的实现 +} -- 2.34.1 From ee73702d22028b3e7eca2a0ba37717a71c13f001 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:43:00 +0800 Subject: [PATCH 126/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/service/MyOrderService.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/MyOrderService.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/MyOrderService.java b/yami-shop-service/src/main/java/com/yami/shop/service/MyOrderService.java new file mode 100644 index 0000000..1513fd7 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/MyOrderService.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yami.shop.bean.app.dto.MyOrderDto; +import com.yami.shop.bean.model.Order; + +/** + * MyOrderService 是一个服务层接口,用于定义针对用户订单(Order)的业务逻辑方法。 + * 它继承自MyBatis-Plus提供的IService,因此默认实现了对Order表的基本CRUD功能。 + * 此外,它还提供了一个特定的方法来根据用户ID和订单状态分页获取订单信息。 + * + * @author lgh + */ +public interface MyOrderService extends IService { + + /** + * pageMyOrderByUserIdAndStatus 方法用于根据用户ID和订单状态分页查询订单信息。 + * + * @param page 分页参数,包含了当前页码、每页显示记录数等信息。 + * 该参数也作为返回结果的一部分,用来封装分页后的数据。 + * @param userId 用户唯一标识符,用于确定要查询哪个用户的订单。 + * @param status 订单的状态码,用于过滤符合条件的订单。 + * 如果为null,则表示不根据订单状态进行过滤。 + * @return 返回一个IPage对象,其中包含分页后的订单信息列表, + * 以及与分页相关的元数据(如总记录数、总页数等)。 + */ + IPage pageMyOrderByUserIdAndStatus(Page page, String userId, Integer status); +} -- 2.34.1 From 9922ab43a8dece438732bca57aaa64c3a464a5d0 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:44:17 +0800 Subject: [PATCH 127/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yami/shop/service/NoticeService.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/NoticeService.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/NoticeService.java b/yami-shop-service/src/main/java/com/yami/shop/service/NoticeService.java new file mode 100644 index 0000000..a37d9ac --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/NoticeService.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +// 定义包名,表示这个接口属于com.yami.shop.service包 + package com.yami.shop.service; + +// 导入MyBatis Plus框架中的分页插件的Page类 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +// 导入MyBatis Plus框架中的IService接口,提供基础的CRUD操作 +import com.baomidou.mybatisplus.extension.service.IService; +// 导入项目中定义的NoticeDto数据传输对象 +import com.yami.shop.bean.app.dto.NoticeDto; +// 导入项目中定义的Notice实体类 +import com.yami.shop.bean.model.Notice; +// 导入Java.util.List接口,用于返回列表类型的数据 +import java.util.List; + +/** + * NoticeService接口的文档注释 + * 这个接口是用于定义与Notice实体类相关的业务操作,主要用于公告管理 + * + * @author hzm 表示这个接口是由hzm创建的 + * @date 2019-04-18 21:21:40 表示这个接口是在2019年4月18日创建的 + */ +public interface NoticeService extends IService { + + /** + * 获取公告列表的方法 + * 这个方法返回一个List类型的数据,包含了所有的公告实体 + * + * @return 包含所有公告的列表 + */ + List listNotice(); + + /** + * 删除公告缓存的方法 + * 这个方法用于清除与公告相关的缓存,以确保数据的一致性 + */ + void removeNoticeList(); + + /** + * 分页获取公布的公告的方法 + * 这个方法接受一个Page类型的参数,返回一个分页的NoticeDto列表 + * + * @param page 分页参数 + * @return 分页的公告DTO列表 + */ + Page pageNotice(Page page); + + /** + * 根据公告id获取公告的方法 + * 这个方法接受一个Long类型的公告ID,返回对应的Notice实体 + * + * @param noticeId 公告ID + * @return 对应的公告实体 + */ + Notice getNoticeById(Long noticeId); + + /** + * 根据公告id删除公告的方法 + * 这个方法接受一个Long类型的公告ID,用于删除对应的公告实体 + * + * @param noticeId 公告ID + */ + void removeNoticeById(Long noticeId); +} -- 2.34.1 From 50bb228c2323228585f20b1a82ba424588c53820 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:44:46 +0800 Subject: [PATCH 128/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/service/impl/MyOrderServiceImpl.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/impl/MyOrderServiceImpl.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/MyOrderServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/MyOrderServiceImpl.java new file mode 100644 index 0000000..2f54ad0 --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/MyOrderServiceImpl.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yami.shop.bean.app.dto.MyOrderDto; +import com.yami.shop.bean.model.Order; +import com.yami.shop.common.util.PageAdapter; +import com.yami.shop.dao.OrderMapper; +import com.yami.shop.service.MyOrderService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * MyOrderServiceImpl 是 MyOrderService 接口的具体实现类。 + * 它继承自 MyBatis-Plus 提供的 ServiceImpl,并实现了 MyOrderService 中定义的方法。 + * 该服务负责处理与用户订单(Order)相关的业务逻辑。 + * + * @author lgh on 2018/09/15. + */ +@Service +public class MyOrderServiceImpl extends ServiceImpl implements MyOrderService { + + /** + * orderMapper 是通过 Spring 自动装配的 DAO 层组件, + * 用于执行对 Order 实体对应的数据库表的操作。 + */ + @Autowired + private OrderMapper orderMapper; + + /** + * pageMyOrderByUserIdAndStatus 方法实现了根据用户ID和订单状态分页查询订单信息的功能。 + * + * @param page 分页参数,包含了当前页码、每页显示记录数等信息。 + * 该参数也作为返回结果的一部分,用来封装分页后的数据。 + * @param userId 用户唯一标识符,用于确定要查询哪个用户的订单。 + * @param status 订单的状态码,用于过滤符合条件的订单。 + * 如果为 null,则表示不根据订单状态进行过滤。 + * @return 返回一个 IPage 对象,其中包含分页后的订单信息列表, + * 以及与分页相关的元数据(如总记录数、总页数等)。 + */ + @Override + public IPage pageMyOrderByUserIdAndStatus(Page page, String userId, Integer status) { + // 使用 PageAdapter 将传入的分页对象适配成适合底层查询的形式 + page.setRecords(orderMapper.listMyOrderByUserIdAndStatus(new PageAdapter(page), userId, status)); + + // 查询满足条件的订单总数,以便设置分页对象的总记录数 + page.setTotal(orderMapper.countMyOrderByUserIdAndStatus(userId, status)); + + // 返回填充了数据的分页对象 + return page; + } +} -- 2.34.1 From b5953fadbb384c09aa52a43036d5407f7f00db46 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:46:27 +0800 Subject: [PATCH 129/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/mapper/OrderItemMapper.xml | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 yami-shop-service/src/main/resources/mapper/OrderItemMapper.xml diff --git a/yami-shop-service/src/main/resources/mapper/OrderItemMapper.xml b/yami-shop-service/src/main/resources/mapper/OrderItemMapper.xml new file mode 100644 index 0000000..8309b1d --- /dev/null +++ b/yami-shop-service/src/main/resources/mapper/OrderItemMapper.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO `tz_order_item` (shop_id,order_number,prod_id,sku_id,sku_name, + prod_count,prod_name,pic,price,user_id,product_total_amount,rec_time,comm_sts, + distribution_card_no,basket_date) VALUES + + + (#{orderItem.shopId},#{orderItem.orderNumber},#{orderItem.prodId},#{orderItem.skuId},#{orderItem.skuName}, + #{orderItem.prodCount},#{orderItem.prodName},#{orderItem.pic},#{orderItem.price},#{orderItem.userId},#{orderItem.productTotalAmount},#{orderItem.recTime},#{orderItem.commSts}, + #{orderItem.distributionCardNo},#{orderItem.basketDate}) + + + + + + \ No newline at end of file -- 2.34.1 From 5e2327cce28670f3085ce4743eaf8c28037169d9 Mon Sep 17 00:00:00 2001 From: CR7 <1965214192@qq.com> Date: Wed, 18 Dec 2024 21:46:58 +0800 Subject: [PATCH 130/130] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/service/impl/NoticeServiceImpl.java | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 yami-shop-service/src/main/java/com/yami/shop/service/impl/NoticeServiceImpl.java diff --git a/yami-shop-service/src/main/java/com/yami/shop/service/impl/NoticeServiceImpl.java b/yami-shop-service/src/main/java/com/yami/shop/service/impl/NoticeServiceImpl.java new file mode 100644 index 0000000..6fd4e8d --- /dev/null +++ b/yami-shop-service/src/main/java/com/yami/shop/service/impl/NoticeServiceImpl.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved. + * + * https://www.mall4j.com/ + * + * 未经允许,不可做商业用途! + * + * 版权所有,侵权必究! + */ + +package com.yami.shop.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yami.shop.bean.app.dto.NoticeDto; +import com.yami.shop.bean.model.Notice; +import com.yami.shop.dao.NoticeMapper; +import com.yami.shop.service.NoticeService; +import lombok.AllArgsConstructor; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * NoticeServiceImpl 是 NoticeService 接口的具体实现类。 + * 它继承自 MyBatis-Plus 提供的 ServiceImpl,并实现了 NoticeService 中定义的方法。 + * 该服务负责处理与公告(Notice)相关的业务逻辑。 + * + * @author hzm + * @date 2019-04-18 21:21:40 + */ +@Service +@AllArgsConstructor // Lombok 注解,自动生成所有构造函数,简化依赖注入 +public class NoticeServiceImpl extends ServiceImpl implements NoticeService { + + private final NoticeMapper noticeMapper; // 使用构造器注入 DAO 层组件 + + /** + * listNotice 方法用于获取有效的、置顶的公告列表,并按照发布时间降序排列。 + * 使用了 @Cacheable 注解来启用缓存,以提高性能。 + * + * @return 返回一个包含符合条件的公告对象列表。 + */ + @Override + @Cacheable(cacheNames = "notices", key = "'notices'") + public List listNotice() { + return noticeMapper.selectList(new LambdaQueryWrapper() + .eq(Notice::getStatus, 1) // 状态为1表示有效 + .eq(Notice::getIsTop, 1) // 是否置顶为1表示置顶 + .orderByDesc(Notice::getPublishTime)); // 按发布时间降序排序 + } + + /** + * removeNoticeList 方法用于清除公告列表的缓存。 + * 使用了 @CacheEvict 注解,在调用此方法时会清除指定名称的缓存。 + */ + @Override + @CacheEvict(cacheNames = "notices", key = "'notices'") + public void removeNoticeList() { + // 方法体为空,因为实际的缓存清除工作由 @CacheEvict 注解完成。 + } + + /** + * pageNotice 方法用于分页查询公告信息。 + * + * @param page 分页参数,包含了当前页码、每页显示记录数等信息。 + * 该参数也作为返回结果的一部分,用来封装分页后的数据。 + * @return 返回一个 IPage 对象,其中包含分页后的公告信息列表, + * 以及与分页相关的元数据(如总记录数、总页数等)。 + */ + @Override + public Page pageNotice(Page page) { + return noticeMapper.pageNotice(page); + } + + /** + * getNoticeById 方法根据给定的公告ID获取公告详情。 + * 使用了 @Cacheable 注解来启用缓存,以提高性能。 + * + * @param noticeId 公告唯一标识符,用于确定要查询的公告。 + * @return 返回一个 Notice 对象,代表指定ID的公告详情。 + */ + @Override + @Cacheable(cacheNames = "notice", key = "#noticeId") + public Notice getNoticeById(Long noticeId) { + return noticeMapper.selectById(noticeId); + } + + /** + * removeNoticeById 方法用于根据给定的公告ID清除公告详情的缓存。 + * 使用了 @CacheEvict 注解,在调用此方法时会清除指定名称和键值的缓存。 + * + * @param noticeId 公告唯一标识符,用于确定要清除缓存的公告。 + */ + @Override + @CacheEvict(cacheNames = "notice", key = "#noticeId") + public void removeNoticeById(Long noticeId) { + // 方法体为空,因为实际的缓存清除工作由 @CacheEvict 注解完成。 + } +} -- 2.34.1