From c897470532a6b8769b3fda63e9dd5e5645d3691f Mon Sep 17 00:00:00 2001 From: pzh69r42v Date: Thu, 12 Dec 2024 16:03:41 +0800 Subject: [PATCH 01/22] 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 From d81ac3f01cfb350a3e058ff730dcf7d5fe6a75e0 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Thu, 12 Dec 2024 16:10:32 +0800 Subject: [PATCH 02/22] =?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 From dae45cc1bcc7cfe1701768a7c4e0f71481be613f Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 19:50:01 +0800 Subject: [PATCH 03/22] =?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)); + } + +} From 02778a84e0b1181f8fd4a9f78ff44f024b9dda5d Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 20:09:40 +0800 Subject: [PATCH 04/22] =?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()); + } +} From 4432efe70c5be488bf8eff11c9a43e8bf166e1b7 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 20:14:44 +0800 Subject: [PATCH 05/22] =?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()); + } +} From 5cb982a58d3c5ba62b9628883b851be49aec70d6 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 20:24:14 +0800 Subject: [PATCH 06/22] =?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 From 5b8e019a4761eb3a30a36abe8657cc47d40b10c5 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 20:31:26 +0800 Subject: [PATCH 07/22] =?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); + } +} From 751d5ffabcecd3eefe6ddf60f406914b30ce8c5b Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 20:49:27 +0800 Subject: [PATCH 08/22] =?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); // 设置订单总运费 + } + } +} From dde746d4d6177618136c4013c69e43b8817c700b Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 22:59:28 +0800 Subject: [PATCH 09/22] =?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); // 返回配送信息列表 + } +} From e08bba041433ee6f005b772ad352a363431b4cf9 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Mon, 16 Dec 2024 23:05:43 +0800 Subject: [PATCH 10/22] =?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); // 返回物流信息 + } +} From bee0c0721031fd1f10b8e8c514c558ab128dd5a5 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 10:21:53 +0800 Subject: [PATCH 11/22] =?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); + } +} From a59783546236a98350f400e3594ddac298869c52 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 10:34:18 +0800 Subject: [PATCH 12/22] =?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 From 3d62bf54e669a47b8e55e12bcb7b9309863cdb0c Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 10:41:47 +0800 Subject: [PATCH 13/22] =?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())); + } + +} From 432dce51084055839b47af150e269f3935263d38 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 10:44:54 +0800 Subject: [PATCH 14/22] =?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 From d707c6d800f56cac97e25e11b8cd1410ede77eb3 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 10:48:35 +0800 Subject: [PATCH 15/22] =?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); // 返回成功响应 + } +} From e85cddfa37ad01582d17b38b13a4ca99163b8d9a Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 10:51:01 +0800 Subject: [PATCH 16/22] =?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(); +// } +} From 4b385e8eb9028e90a7392617481ea0b3530a12eb Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 10:54:09 +0800 Subject: [PATCH 17/22] =?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(); // 返回成功响应 + } +} From 0e4dd837129eda64a793cce3153b93ec140a3319 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 11:15:30 +0800 Subject: [PATCH 18/22] =?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; + } +} From cda43bd9b436e0f6ff4addf5b1cb86524e51b8e1 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 11:18:23 +0800 Subject: [PATCH 19/22] =?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 From 29790ef1e4c566f54599e4bdb9b99c18191323d8 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 11:22:25 +0800 Subject: [PATCH 20/22] =?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)); // 删除用户地址并返回结果 + } +} From c78536267de19c5b33d9f383af4bc3dbfe0177d3 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 12:52:51 +0800 Subject: [PATCH 21/22] =?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); + } +} From 756ccbd6b316a45df5ccd5c899aaa479bbb57b61 Mon Sep 17 00:00:00 2001 From: wangjinhao Date: Tue, 17 Dec 2024 12:55:10 +0800 Subject: [PATCH 22/22] =?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