Merge pull request '合并' (#5) from develop into main

main
方建军 9 months ago
commit e946f1d7b6

@ -0,0 +1 @@
undefined

@ -0,0 +1 @@
undefined

@ -0,0 +1 @@
undefined

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

@ -0,0 +1 @@
undefined

@ -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<String> getUserPermissions(Long userId) {
List<String> permsList;
//系统管理员,拥有最高权限
if(userId == Constant.SUPER_ADMIN_ID){
List<SysMenu> 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());
}
}

@ -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<IPage<Area>> page(Area area, PageParam<Area> page) {
IPage<Area> areaPage = areaService.page(page, new LambdaQueryWrapper<Area>());
return ServerResponseEntity.success(areaPage);
}
/**
*
* @param area
* @return
*/
@GetMapping("/list")
@PreAuthorize("@pms.hasPermission('admin:area:list')") // 权限检查
public ServerResponseEntity<List<Area>> list(Area area) {
List<Area> areas = areaService.list(new LambdaQueryWrapper<Area>()
.like(area.getAreaName() != null, Area::getAreaName, area.getAreaName()));
return ServerResponseEntity.success(areas);
}
/**
* ID
* @param pid ID
* @return
*/
@GetMapping("/listByPid")
public ServerResponseEntity<List<Area>> listByPid(Long pid) {
List<Area> list = areaService.listByPid(pid);
return ServerResponseEntity.success(list);
}
/**
*
* @param id ID
* @return
*/
@GetMapping("/info/{id}")
@PreAuthorize("@pms.hasPermission('admin:area:info')") // 权限检查
public ServerResponseEntity<Area> 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<Void> 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<Void> 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<Void> 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<Area>()
.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("该地区已存在");
}
}
}

@ -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<List<Category>> table() {
List<Category> categoryMenuList = categoryService.tableCategory(SecurityUtils.getSysUser().getShopId());
return ServerResponseEntity.success(categoryMenuList);
}
/**
*
* @param categoryId ID
* @return
*/
@GetMapping("/info/{categoryId}")
public ServerResponseEntity<Category> 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<Void> save(@RequestBody Category category) {
category.setShopId(SecurityUtils.getSysUser().getShopId());
category.setRecTime(new Date());
Category categoryName = categoryService.getOne(new LambdaQueryWrapper<Category>().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<String> 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<Category>().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<Category>().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<String> delete(@PathVariable("categoryId") Long categoryId) {
if (categoryService.count(new LambdaQueryWrapper<Category>().eq(Category::getParentId, categoryId)) > 0) {
return ServerResponseEntity.showFailMsg("请删除子分类,再删除该分类");
}
categoryService.deleteCategory(categoryId);
return ServerResponseEntity.success();
}
/**
*
* @return
*/
@GetMapping("/listCategory")
public ServerResponseEntity<List<Category>> listCategory() {
return ServerResponseEntity.success(categoryService.list(new LambdaQueryWrapper<Category>()
.le(Category::getGrade, 2)
.eq(Category::getShopId, SecurityUtils.getSysUser().getShopId())
.orderByAsc(Category::getSeq)));
}
/**
*
* @return
*/
@GetMapping("/listProdCategory")
public ServerResponseEntity<List<Category>> listProdCategory() {
List<Category> categories = categoryService.treeSelect(SecurityUtils.getSysUser().getShopId(), 2);
return ServerResponseEntity.success(categories);
}
}

@ -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<List<Delivery>> page() {
List<Delivery> list = deliveryService.list(); // 获取所有配送信息
return ServerResponseEntity.success(list); // 返回配送信息列表
}
}

@ -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<IPage<Order>> page(OrderParam orderParam, PageParam<Order> page) {
Long shopId = SecurityUtils.getSysUser().getShopId();
orderParam.setShopId(shopId);
IPage<Order> orderPage = orderService.pageOrdersDetailByOrderParam(page, orderParam);
return ServerResponseEntity.success(orderPage); // 返回分页结果
}
/**
*
* @param orderNumber
* @return
*/
@GetMapping("/orderInfo/{orderNumber}")
@PreAuthorize("@pms.hasPermission('order:order:info')") // 权限检查
public ServerResponseEntity<Order> 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<OrderItem> 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<Void> 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<OrderItem> 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<Order> 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<OrderItem> 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<Order> 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<OrderItem> 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);
}
}
}

@ -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<IPage<PickAddr>> page(PickAddr pickAddr, PageParam<PickAddr> page) {
IPage<PickAddr> pickAddrs = pickAddrService.page(page, new LambdaQueryWrapper<PickAddr>()
.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<PickAddr> 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<Void> 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<Void> 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<Void> delete(@RequestBody Long[] ids) {
pickAddrService.removeByIds(Arrays.asList(ids)); // 删除自提点地址
return ServerResponseEntity.success(); // 返回成功响应
}
}

@ -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<IPage<Transport>> page(Transport transport, PageParam<Transport> page) {
Long shopId = SecurityUtils.getSysUser().getShopId();
IPage<Transport> transports = transportService.page(page,
new LambdaQueryWrapper<Transport>()
.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<Transport> 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<Void> 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<Void> update(@RequestBody Transport transport) {
transportService.updateTransportAndTransfee(transport);
return ServerResponseEntity.success();
}
/**
*
* @param ids ID
* @return
*/
@DeleteMapping
@PreAuthorize("@pms.hasPermission('shop:transport:delete')") // 权限检查
public ServerResponseEntity<Void> delete(@RequestBody Long[] ids) {
transportService.deleteTransportAndTransfeeAndTranscity(ids);
// 删除运费模板的缓存
for (Long id : ids) {
transportService.removeTransportAndAllItemsCache(id);
}
return ServerResponseEntity.success();
}
/**
*
* @return
*/
@GetMapping("/list")
public ServerResponseEntity<List<Transport>> list() {
Long shopId = SecurityUtils.getSysUser().getShopId();
List<Transport> list = transportService.list(new LambdaQueryWrapper<Transport>().eq(Transport::getShopId, shopId));
return ServerResponseEntity.success(list);
}
}

@ -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<IPage<UserAddr>> getUserAddrPage(PageParam page, UserAddr userAddr) {
return ServerResponseEntity.success(userAddrService.page(page, new LambdaQueryWrapper<UserAddr>())); // 返回分页结果
}
/**
* ID
* @param addrId ID
* @return
*/
@GetMapping("/info/{addrId}")
public ServerResponseEntity<UserAddr> getById(@PathVariable("addrId") Long addrId) {
return ServerResponseEntity.success(userAddrService.getById(addrId)); // 返回用户地址信息
}
/**
*
* @param userAddr
* @return
*/
@SysLog("新增用户地址管理") // 自定义日志注解
@PostMapping
@PreAuthorize("@pms.hasPermission('user:addr:save')") // 权限检查
public ServerResponseEntity<Boolean> save(@RequestBody @Valid UserAddr userAddr) {
return ServerResponseEntity.success(userAddrService.save(userAddr)); // 保存用户地址并返回结果
}
/**
*
* @param userAddr
* @return
*/
@SysLog("修改用户地址管理") // 自定义日志注解
@PutMapping
@PreAuthorize("@pms.hasPermission('user:addr:update')") // 权限检查
public ServerResponseEntity<Boolean> 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<Boolean> removeById(@PathVariable Long addrId) {
return ServerResponseEntity.success(userAddrService.removeById(addrId)); // 删除用户地址并返回结果
}
}

@ -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-jobjava
* @ 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<Order> orders = orderService.listOrderAndOrderItems(OrderStatus.UNPAY.value(), DateUtil.offsetMinute(now, -30));
if (CollectionUtil.isEmpty(orders)) {
return;
}
orderService.cancelOrders(orders);
for (Order order : orders) {
List<OrderItem> 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<Order> orders = orderService.listOrderAndOrderItems(OrderStatus.CONSIGNMENT.value(), DateUtil.offsetDay(now, -15));
if (CollectionUtil.isEmpty(orders)) {
return;
}
orderService.confirmOrder(orders);
for (Order order : orders) {
List<OrderItem> orderItems = order.getOrderItems();
for (OrderItem orderItem : orderItems) {
productService.removeProductCacheByProdId(orderItem.getProdId());
skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId());
}
}
}
}

@ -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注解
/**
* ApiBeanConfigSnowflakeID
* @ 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());
}
}

@ -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<List<UserAddrDto>> dvyList() {
// 获取当前用户ID
String userId = SecurityUtils.getUser().getUserId();
// 查询用户的所有地址信息,并按是否常用和更新时间倒序排序
List<UserAddr> userAddrs = userAddrService.list(
new LambdaQueryWrapper<UserAddr>()
.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<String> 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<UserAddr>().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<String> 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<String> 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<String> 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<UserAddrDto> 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));
}
}

@ -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(pid0)
* @return
*/
@GetMapping("/listByPid")
@Operation(summary = "获取省市区信息" , description = "根据省市区的pid获取地址信息")
@Parameter(name = "pid", description = "省市区的pid(pid为0获取所有省份)" , required = true)
public ServerResponseEntity<List<Area>> listByPid(Long pid) {
List<Area> list = areaService.listByPid(pid);
return ServerResponseEntity.success(list);
}
}

@ -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 ID0
* @return
*/
@GetMapping("/categoryInfo")
@Operation(summary = "分类信息列表", description = "获取所有的产品分类信息顶级分类的parentId为0,默认为顶级分类")
@Parameter(name = "parentId", description = "分类ID", required = false)
public ServerResponseEntity<List<CategoryDto>> categoryInfo(@RequestParam(value = "parentId", defaultValue = "0") Long parentId) {
List<Category> categories = categoryService.listByParentId(parentId);
List<CategoryDto> categoryDtos = BeanUtil.copyToList(categories, CategoryDto.class);
return ServerResponseEntity.success(categoryDtos);
}
}

@ -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<DeliveryDto> 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); // 返回物流信息
}
}

@ -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<OrderShopDto> 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<OrderItem> orderItems = orderItemService.getOrderItemsByOrderNumber(orderNumber);
List<OrderItemDto> 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<IPage<MyOrderDto>> myOrder(@RequestParam(value = "status") Integer status, PageParam<MyOrderDto> page) {
String userId = SecurityUtils.getUser().getUserId();
IPage<MyOrderDto> 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<String> 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<OrderItem> 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<String> 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<OrderItem> 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<String> 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<OrderCountData> getOrderCount() {
String userId = SecurityUtils.getUser().getUserId();
OrderCountData orderCountMap = orderService.getOrderCount(userId);
return ServerResponseEntity.success(orderCountMap);
}
}

@ -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<ShopCartOrderMergerDto> 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<ShopCartItemDto> shopCartItems = basketService.getShopCartItemsByOrderItems(orderParam.getBasketIds(), orderParam.getOrderItem(), userId);
if (CollectionUtil.isEmpty(shopCartItems)) {
throw new YamiShopBindException("请选择您需要的商品加入购物车");
}
// 根据店铺组装购物车中的商品信息
List<ShopCartDto> shopCarts = basketService.getShopCarts(shopCartItems);
// 生成完整的订单信息
ShopCartOrderMergerDto shopCartOrderMergerDto = new ShopCartOrderMergerDto();
shopCartOrderMergerDto.setUserAddr(userAddrDto);
List<ShopCartOrderDto> 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<ShopCartItemDiscountDto> shopCartItemDiscounts = shopCart.getShopCartItemDiscounts();
List<ShopCartItemDto> shopAllShopCartItems = new ArrayList<>();
for (ShopCartItemDiscountDto shopCartItemDiscount : shopCartItemDiscounts) {
List<ShopCartItemDto> 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<OrderNumbersDto> submitOrders(@Valid @RequestBody SubmitOrderParam submitOrderParam) {
String userId = SecurityUtils.getUser().getUserId();
ShopCartOrderMergerDto mergerOrder = orderService.getConfirmOrderCache(userId);
if (mergerOrder == null) {
throw new YamiShopBindException("订单已过期,请重新下单");
}
List<OrderShopParam> orderShopParams = submitOrderParam.getOrderShopParam();
List<ShopCartOrderDto> 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<Order> 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()));
}
}

@ -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<Void> 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<Boolean> 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); // 返回成功响应
}
}

@ -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<Void> 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();
// }
}

@ -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); // 设置订单总运费
}
}
}

@ -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<ShopCartOrderDto> shopCartOrders = mergerOrder.getShopCartOrders();
List<Long> basketIds = new ArrayList<>();
// 商品skuId为key需要更新的sku为value的map
Map<Long, Sku> skuStocksMap = new HashMap<>(16);
// 商品productId为key需要更新的product为value的map
Map<Long, Product> 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<Long> basketIds, Map<Long, Sku> skuStocksMap, Map<Long, Product> prodStocksMap, Long addrOrderId, ShopCartOrderDto shopCartOrderDto) {
// 使用雪花算法生成的订单号
String orderNumber = String.valueOf(snowflake.nextId());
shopCartOrderDto.setOrderNumber(orderNumber);
Long shopId = shopCartOrderDto.getShopId();
// 订单商品名称
StringBuilder orderProdName = new StringBuilder(100);
List<OrderItem> orderItems = new ArrayList<>();
List<ShopCartItemDiscountDto> shopCartItemDiscounts = shopCartOrderDto.getShopCartItemDiscounts();
for (ShopCartItemDiscountDto shopCartItemDiscount : shopCartItemDiscounts) {
List<ShopCartItemDto> 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<OrderItem> 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<Long, Product> 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<Long, Sku> 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;
}
}

@ -0,0 +1,75 @@
package com.yami.shop.bean.model;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* Basket
* "tz_basket"MyBatis Plus
* Serializable便使
*
* @author lanhai
*/
@Data
@TableName("tz_basket")
public class Basket implements Serializable {
/**
*
* "tz_basket"
*/
@TableId
private Long basketId;
/**
* ID
* ID
*/
private Long shopId;
/**
* ID
* ID
*/
private Long prodId;
/**
* SkuIDStock Keeping Unit
* SkuID
*/
private Long skuId;
/**
* ID
* ID
* ID
*/
private String userId;
/**
*
*
*/
private Integer basketCount;
/**
*
*
*/
private Date basketDate;
/**
* ID
* ID
*/
private Long discountId;
/**
* 广广
*
*/
private String distributionCardNo;
}

@ -0,0 +1,82 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.app.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.yami.shop.common.serializer.json.ImgJsonSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* `CategoryDto`DTO
* 使
*
* 便
*
* @author lanhai
*/
@Data
public class CategoryDto {
/**
*
*
* `categoryId`
*
*
* `@Schema` API 使 Swagger
* id`required = true`
* 使
*/
@Schema(description = "分类id", required = true)
private Long categoryId;
/**
* `parentId`
*
*
* `id` `parentId`
*
* `@Schema` API id
* 便
*/
@Schema(description = "分类父id", required = true)
private Long parentId;
/**
* `categoryName`
*
*
* 便
*
* `@Schema` API
*
*/
@Schema(description = "分类名称", required = true)
private String categoryName;
/**
* `pic`
* URL
*
*
*
* 使 `@JsonSerialize(using = ImgJsonSerializer.class)`
* `CategoryDto` JSON
* 使 `ImgJsonSerializer` `pic`
* 使
* 使 `@Schema`
* 便使
*/
@Schema(description = "分类图片", required = true)
@JsonSerialize(using = ImgJsonSerializer.class)
private String pic;
}

@ -0,0 +1,75 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.app.dto;
import lombok.Data;
import java.io.Serializable;
/**
* `ChooseDiscountItemDto`DTO
*
*
* 便使
*
* @author lanhai
*/
@Data
public class ChooseDiscountItemDto implements Serializable {
/**
*
* `discountItemId`
*
*
*/
private Long discountItemId;
/**
* `discountActivityId`
* ID
*
*
*/
private Long discountActivityId;
/**
* `discountAmount`
*
* 使
*/
private Double discountAmount;
/**
* `isActive`
* `true` `false`
* `true`
* `false`便
*
*/
private boolean isActive;
/**
* `List<Long>`
* ID `applicableProdIds`
*
*
*/
private Object applicableProdIds;
/**
* 使 `remainingUseTimes`
* 使使
* 使3使
* 使使
*/
private Integer remainingUseTimes;
}

@ -0,0 +1,66 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.app.dto;
import lombok.Data;
import java.io.Serializable;
/**
* `CouponOrderDto`DTO
* 便
* 使
*
* @author lanhai
*/
@Data
public class CouponOrderDto implements Serializable {
/**
*
* `couponId`
* 使
*
*/
private Long couponId;
/**
* `orderId`
* 使使使
* `orderId` 便
* 使使
*/
private Long orderId;
/**
* `discountAmount`
*
*
*
*/
private Double discountAmount;
/**
* 使 `isUsed` `true` 使`false` 使
* 使使
* 使
* 便
*/
private boolean isUsed;
/**
* 使 `remainingUseTimes`
* 使使
* 使使
* 使使
*/
private Integer remainingUseTimes;
}

@ -0,0 +1,110 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* @author lanhai
*//*
* * Copyright (c) 2018-2999 广 All rights reserved.
* *
* * https://www.mall4j.com/
* *
* *
* *
* *
* */
*
*package com.yami.shop.bean.app.dto;
*
*import io.swagger.v3.oas.annotations.media.Schema;
*import lombok.Data;
*import java.util.List;
*
* /**
* * `DeliveryDto`DTO
* * 便
* * 使
* *
*
* * @author lanhai
* */
*@Data
*
public class DeliveryDto {
*
* /**
* * `companyName`
* *
* * `@Schema` `required = true`
* *
* */
*
@Schema(description = "物流公司名称", required = true)
*
private String companyName;
*
* /**
* * `companyHomeUrl`访
* *
* * 便 `@Schema`
* *
* */
*
@Schema(description = "物流公司官网", required = true)
*
private String companyHomeUrl;
*
* /**
* * `dvyFlowId`
* *
* *
* * `@Schema`
* */
*
@Schema(description = "物流订单号", required = true)
*
private String dvyFlowId;
*
* /**
* * `data` `DeliveryInfoDto`
* *
* * 便
* * `@Schema`
* * 使
* */
*
@Schema(description = "查询出的物流信息", required = true)
*
private List<DeliveryInfoDto> data;
*
}
@Data
public class DeliveryDto {
@Schema(description = "物流公司名称" ,required=true)
private String companyName;
@Schema(description = "物流公司官网" ,required=true)
private String companyHomeUrl;
@Schema(description = "物流订单号" ,required=true)
private String dvyFlowId;
@Schema(description = "查询出的物流信息" ,required=true)
private List<DeliveryInfoDto> data;
}

@ -0,0 +1,68 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* `DeliveryInfoDto`DTO
* 便
*
* @author lanhai
*/
@Data
public class DeliveryInfoDto {
/**
* `context`
*
*
*
*
* `@Schema` `required = true`
* 使
*/
@Schema(description = "详细信息", required = true)
private String context;
/**
* `ftime`
* `yyyy-MM-dd HH:mm:ss`
*
* 便
*
*/
private String ftime;
/**
* `location`线
*
* XXXXXX
*
* `@Schema`
* 便
*/
@Schema(description = "快递所在区域", required = true)
private String location;
/**
* `time` `yyyy-MM-dd HH:mm:ss`
*
*
* 便
* `@Schema`
*
*/
@Schema(description = "物流更新时间", required = true)
private String time;
}

@ -0,0 +1,67 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.app.dto;
import lombok.Data;
import java.io.Serializable;
/**
* `DiscountDto`DTO
* 便
* 使
*
* @author lanhai
*/
@Data
public class DiscountDto implements Serializable {
/**
*
* `discountId`200508
*
*
*
*/
private Long discountId;
/**
* `orderId`
* 使使使
* `orderId` 便
* 使使
*/
private Long orderId;
/**
* `discountAmount`
*
*
*
*/
private Double discountAmount;
/**
* 使 `isUsed` `true` 使`false` 使
* 使
* 使
* 便
*/
private boolean isUsed;
/**
* 使 `remainingUseTimes`
* 使使
* 使使
* 使使
*/
private Integer remainingUseTimes;
}

@ -0,0 +1,81 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.app.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.yami.shop.common.serializer.json.ImgJsonSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
/**
* `IndexImgDto`DTO
* 使
* 便
* ID
* @author lanhai
*/
@Schema(description = "首页图片对象")
@Data
public class IndexImgDto {
/**
* `imgUrl`访
* 使URL
* `imgUrl`
* `@JsonSerialize(using = ImgJsonSerializer.class)` JSON
* 使使
* `@Schema` Url`required = true`
* 便
*/
@JsonSerialize(using = ImgJsonSerializer.class)
@Schema(description = "图片Url", required = true)
private String imgUrl;
/**
* `seq`
* `seq`
*
* `@Schema`
* 便
*/
@Schema(description = "图片顺序", required = true)
private Integer seq;
/**
* `uploadTime` `Date` `yyyy-MM-dd HH:mm:ss`
* 便
* `@Schema`
*
*/
@Schema(description = "上传时间", required = true)
private Date uploadTime;
/**
* `type`
* `1` `2` `3`
*
* 便
* `@Schema` 便
*/
@Schema(description = "类型", required = true)
private int type;
/**
* `relation` `id` `id`
*
* `relation`
* `@Schema` idID便
*/
@Schema(description = "关联id", required = true)
private Long relation;
}

@ -0,0 +1,67 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* `MyOrderDto`DTO
* 便
* 使便
* @author lanhai
*/
@Data
@Schema(description = "我的订单")
public class MyOrderDto {
/**
* `orderItemDtos` `MyOrderItemDto`
* SKU
* 便
* 退使
* `@Schema` `required = true`
* 便
*/
@Schema(description = "订单项", required = true)
private List<MyOrderItemDto> orderItemDtos;
/**
* `orderNumber`
*
* 退
* `@Schema` 便
*/
@Schema(description = "订单号", required = true)
private String orderNumber;
/**
* `actualTotal` `Double`
* 使
*
* `@Schema`
* 便
*/
@Schema(description = "总价", required = true)
private Double actualTotal;
/**
* `status`
* `0` `1` `2` `3`
*
*
* `@Schema` 便
*/
@Schema(description = "订单状态", required = true)
private Integer status;
}

@ -0,0 +1,76 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.app.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.yami.shop.common.serializer.json.ImgJsonSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* `MyOrderItemDto`DTO
* 便
* 使
* @author lanhai
*/
@Schema(description = "我的订单-订单项")
@Data
public class MyOrderItemDto {
/**
* `pic`访
* 使URL
* `pic`
* `@JsonSerialize(using = ImgJsonSerializer.class)` JSON
* 使使
* `@Schema` `required = true`
* 便
*/
@Schema(description = "商品图片", required = true)
@JsonSerialize(using = ImgJsonSerializer.class)
private String pic;
/**
* `prodName`P50
*
* `@Schema`
* 便
*/
@Schema(description = "商品名称", required = true)
private String prodName;
/**
* `prodCount`
* 退
* `@Schema` 便
*/
@Schema(description = "商品数量", required = true)
private Integer prodCount;
/**
* `price` `Double`
*
* `@Schema`
* 便
*/
@Schema(description = "商品价格", required = true)
private Double price;
/**
* `skuName`SKUStock Keeping Unit
* SKU8GB + 256GB +
* SKU便
* `@Schema` skuName便
*/
@Schema(description = "skuName", required = true)
private String skuName;
}

@ -0,0 +1,71 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
/**
* `NoticeDto`DTO
* 便
* 使便
*
* @author lanhai
*/
@Schema(description = "公告对象")
@Data
public class NoticeDto {
/**
* `id`IDLong
* ID便
* `@Schema` id
* ID
*
*/
@Schema(description = "公告id")
private Long id;
/**
* `shopId`ID
*
* ID
*/
@Schema(description = "店铺id")
private Long shopId;
/**
* `title`
*
*
*/
@Schema(description = "标题")
private String title;
/**
* `content`
*
*
* 便
*/
@Schema(description = "公告内容")
private String content;
/**
* `publishTime` `Date` `yyyy-MM-dd HH:mm:ss`
*
*
*/
@Schema(description = "公告发布时间")
private Date publishTime;
}

@ -0,0 +1,189 @@
package com.yami.shop.bean.app.param;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
/**
* AddrParamapp使
* 便
*
* @author lanhai
*/
@Schema(description = "地址参数")
public class AddrParam {
/**
*
* @Schema required=true
*/
@Schema(description = "地址ID", required = true)
private Long addrId;
/**
* @NotNull
* 使 @Schema
*/
@NotNull(message = "收货人不能为空")
@Schema(description = "收货人", required = true)
private String receiver;
/**
*
* @NotNull @Schema
*/
@NotNull(message = "地址不能为空")
@Schema(description = "地址", required = true)
private String addr;
/**
* required=false @Schema
*/
@Schema(description = "邮编", required = false)
private String postCode;
/**
*
* 使 @NotNull @Schema
*/
@NotNull(message = "手机不能为空")
@Schema(description = "手机", required = true)
private String mobile;
/**
*
* @NotNull @Schema
*/
@NotNull(message = "省ID不能为空")
@Schema(description = "省ID", required = true)
private Long provinceId;
/**
*
* @NotNull @Schema
*/
@NotNull(message = "城市ID不能为空")
@Schema(description = "城市ID", required = true)
private Long cityId;
/**
*
* @NotNull 使 @Schema
*/
@NotNull(message = "区ID不能为空")
@Schema(description = "区ID", required = true)
private Long areaId;
/**
*
* @NotNull @Schema
*/
@NotNull(message = "省不能为空")
@Schema(description = "省", required = true)
private String province;
/**
*
* @NotNull @Schema
*/
@NotNull(message = "城市不能为空")
@Schema(description = "城市", required = true)
private String city;
/**
*
* @NotNull @Schema
*/
@NotNull(message = "区不能为空")
@Schema(description = "区", required = true)
private String area;
// 以下是各个属性的Getter和Setter方法用于获取和设置对应属性的值
public Long getAddrId() {
return addrId;
}
public void setAddrId(Long addrId) {
this.addrId = addrId;
}
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
public String getPostCode() {
return postCode;
}
public void setPostCode(String postCode) {
this.postCode = postCode;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public Long getProvinceId() {
return provinceId;
}
public void setProvinceId(Long provinceId) {
this.provinceId = provinceId;
}
public Long getCityId() {
return cityId;
}
public void setCityId(Long cityId) {
this.cityId = cityId;
}
public Long getAreaId() {
return areaId;
}
public void setAreaId(Long areaId) {
this.areaId = areaId;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getArea() {
return area;
}
public void setArea(String area) {
this.area = area;
}
}

@ -0,0 +1,97 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.app.param;
import jakarta.validation.constraints.NotNull;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* `ChangeShopCartParam`
* 便
*
* @author LGH
*/
@Data
public class ChangeShopCartParam {
/**
* `basketId`ID
* `basketId`
* ID
*
* `@Schema` API使SwaggerID
* `required = true`
* 便
*/
@Schema(description = "购物车ID", required = true)
private Long basketId;
/**
* `prodId`ID
* `prodId`
* ID
*
* 使 `@NotNull` ID
* ID
* `@Schema` APIID
*/
@NotNull(message = "商品ID不能为空")
@Schema(description = "商品ID", required = true)
private Long prodId;
/**
* `skuId`Stock Keeping UnitSKUID
*
* SKU `skuId`
* 便
* `@NotNull` skuId
* `@Schema` APIskuId便
*/
@NotNull(message = "skuId不能为空")
@Schema(description = "skuId", required = true)
private Long skuId;
/**
* `shopId`ID
*
* `shopId`
*
* `@NotNull` ID
* `@Schema` APIID
*/
@NotNull(message = "店铺ID不能为空")
@Schema(description = "店铺ID", required = true)
private Long shopId;
/**
* `count`
* 12
*
*
* `@NotNull`
* `@Schema` API便
*/
@NotNull(message = "商品个数不能为空")
@Schema(description = "商品个数", required = true)
private Integer count;
/**
* `distributionCardNo`广
* 广
* 广便
* `@Schema` API广
*
*/
@Schema(description = "分销推广人卡号")
private String distributionCardNo;
}

@ -0,0 +1,104 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.app.param;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* `LoginParam`
* 便
*
* @author lanhai
*/
@Schema(description = "登陆参数")
public class LoginParam {
/**
* `code` `code` `code`
* `code` `code`
* `@Schema` `code`使 `code`
* `required = true` `code`
*
*/
@Schema(description = "小程序登陆时返回的code(使用code登陆必填)", required = true)
private String code;
/**
* `mobile`
* 便
* `@Schema`
* 使
*/
@Schema(description = "登陆时的用户名(账号密码登陆必填)", required = true)
private String mobile;
/**
* `password` `mobile`
*
* `@Schema`
* 使
*/
@Schema(description = "登陆时的密码(账号密码登陆必填)", required = true)
private String password;
/**
* `getCode`JavaBean访Getter `code`
* 访 `code`
* 访
*/
public String getCode() {
return code;
}
/**
* `setCode` `getCode` Setter `code`
* `code`
* JavaBean便
*/
public void setCode(String code) {
this.code = code;
}
/**
* `getMobile` `mobile` Getter `getCode`
* 使便
*/
public String getMobile() {
return mobile;
}
/**
* `setMobile` `getMobile` Setter `mobile`
*
*
*/
public void setMobile(String mobile) {
this.mobile = mobile;
}
/**
* `getPassword` `password` Getter
* 便使
*
*/
public String getPassword() {
return password;
}
/**
* `setPassword` `password` Setter
*
*/
public void setPassword(String password) {
this.password = password;
}
}

@ -0,0 +1,57 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* `HotSearchDto`DTO
* 便
* 使便
*
* @author lanhai
*/
@Schema(description = "热搜数据")
@Data
public class HotSearchDto implements Serializable {
/**
* `hotSearchId`IDLong
* ID便
* `@Schema` id
* ID
*
*/
@Schema(description = "热搜id")
private Long hotSearchId;
/**
* `title`
*
* `@Schema`
* 便API使Swagger便使
*/
@Schema(description = "标题")
private String title;
/**
* `content`
*
* 便
* `@Schema` `HotSearchDto`
* 便
*/
@Schema(description = "内容")
private String content;
}

@ -0,0 +1,85 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.enums;
/**
* AreaLevelEnum 便
* 使
*
* @author cl
*/
public enum AreaLevelEnum {
/**
* FIRST_LEVEL
* 1
*/
FIRST_LEVEL(1),
/**
* SECOND_LEVEL
* 2便
*/
SECOND_LEVEL(2),
/**
* THIRD_LEVEL
* 3
*/
THIRD_LEVEL(3)
;
/**
*
*
*/
private Integer num;
/**
*
* 便使
*
* @return
*/
public Integer value() {
return num;
}
/**
*
*
*
* @param num
*/
AreaLevelEnum(Integer num) {
this.num = num;
}
/**
* AreaLevelEnum
*
* 便
* null
*
* @param value
* @return AreaLevelEnum null
*/
public static AreaLevelEnum instance(Integer value) {
AreaLevelEnum[] enums = values();
for (AreaLevelEnum statusEnum : enums) {
if (statusEnum.value().equals(value)) {
return statusEnum;
}
}
return null;
}
}

@ -0,0 +1,62 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.enums;
/**
* `MessageStatus`
* 便
* 使
* @author lanhai
*/
public enum MessageStatus {
/**
* `CANCEL` `0`
*
* `CANCEL`
* 便
*/
CANCEL(0),
/**
* `RELEASE` `1`
*
* `RELEASE`
*
*/
RELEASE(1);
/**
* `num`
*
* 使便
*/
private Integer num;
/**
* `value`访 `num`
* `MessageStatus`
* 访
*/
public Integer value() {
return num;
}
/**
* `CANCEL(0)` `RELEASE(1)`
* `num`
* 便 `value`
*/
MessageStatus(Integer num) {
this.num = num;
}
}

@ -0,0 +1,42 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
// 包声明表明该类所在的包名这里定义在com.yami.shop.bean.event包下
// 按照Java的包结构规范通常用于对相关类进行组织和分类管理方便代码的模块化开发和维护。
package com.yami.shop.bean.event;
// 导入Order类因为在本类中会使用到Order类型的变量通过导入对应的类
// 才能在代码中正确地引用和操作该类型此处表示从com.yami.shop.bean.model包中引入Order类。
import com.yami.shop.bean.model.Order;
// Lombok注解用于自动生成包含所有参数的构造函数
// 这样在创建CancelOrderEvent类的实例时可以方便地通过传入相应参数来初始化对象。
import lombok.AllArgsConstructor;
// Lombok注解用于自动生成类的getter、setter方法以及其他一些常用的方法如toString等
// 减少了手动编写这些重复代码的工作量,提高代码的简洁性和开发效率。
import lombok.Data;
// 类的文档注释,简要描述了该类的作用,即表示取消订单的事件。
// 这种注释有助于其他开发人员快速理解该类在整个业务逻辑中的用途。
/**
*
* @author
*/
// 使用@Data注解让Lombok自动为该类生成相关的方法如getter、setter等。
// 使用@AllArgsConstructor注解让Lombok自动生成包含所有参数的构造函数。
@Data
@AllArgsConstructor
// 定义CancelOrderEvent类用于表示取消订单这一业务事件相关的信息
// 通常在基于事件驱动的架构中,此类可以作为事件对象在不同组件之间传递相关的业务数据。
public class CancelOrderEvent {
// 定义一个私有成员变量order类型为Order用于存储被取消的订单相关的详细信息
// 比如订单编号、下单用户、商品明细、订单金额等内容,方便在事件传递过程中获取和处理订单相关的数据。
private Order order;
}

@ -0,0 +1,62 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.event;
import com.yami.shop.bean.app.dto.ShopCartDto;
import com.yami.shop.bean.app.dto.ShopCartItemDto;
import com.yami.shop.bean.app.dto.ShopCartOrderDto;
import com.yami.shop.bean.app.dto.ShopCartOrderMergerDto;
import com.yami.shop.bean.app.param.OrderParam;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
/**
* `ConfirmOrderEvent`DDD
*
*
* 使
*
* @author LGH
*/
@Data
@AllArgsConstructor
public class ConfirmOrderEvent {
/**
* `shopCartOrderDto`
*
*
* `ShopCartOrderDto` ID
*
* 便
*/
private ShopCartOrderDto shopCartOrderDto;
/**
* `orderParam`
*
*
*
*
*/
private OrderParam orderParam;
/**
* `shopCartItems` `ShopCartItemDto`
* SKU
*
* 便使
*
*/
private List<ShopCartItemDto> shopCartItems;
}

@ -0,0 +1,69 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* Area"tz_area"
* 使MyBatis PlusLombok @Data GetterSetter
*
*
* @author lanhai
*/
@Data
@TableName("tz_area") // 表明该实体类对应的数据库表名称为"tz_area"方便MyBatis Plus进行数据库操作时的表关联映射
public class Area implements Serializable {
private static final long serialVersionUID = -6013320537436191451L;
/**
* @TableId
* @Schema required=true
*/
@TableId
@Schema(description = "地区id", required = true)
private Long areaId;
/**
* @Schema
*
*/
@Schema(description = "地区名称", required = true)
private String areaName;
/**
*
* @Schema 便
*/
@Schema(description = "地区上级id", required = true)
private Long parentId;
/**
* 123
* @Schema
*/
@Schema(description = "地区层级", required = true)
private Integer level;
/**
* @TableField(exist=false)
* 使便
*/
@TableField(exist = false)
private List<Area> areas;
}

@ -0,0 +1,84 @@
/*
* Copyright (c) 2018 - 2999 广 All rights reserved.
* 广20182999
*
* https://www.mall4j.com/
*
*
*
* 使
*
*
*
*/
package com.yami.shop.bean.model;
// 声明该类所属的包名,用于在项目的代码组织结构中对类进行分类管理,方便代码的组织、查找以及不同模块间的引用。
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
// 引入MyBatis Plus相关注解和Lombok的Data注解用于简化代码编写以及实现实体类与数据库表的映射等功能。
import java.io.Serializable;
import java.util.Date;
// 引入Java标准库中的接口和类Serializable接口用于标记该类对象可以被序列化方便在网络传输、存储等场景下进行对象状态的保存和恢复Date类用于处理日期相关的数据。
/**
* @author lanhai
* lanhai
*/
@Data
// Lombok的@Data注解它会自动为类生成常用的方法包括所有成员变量的getter和setter方法、toString方法、equals方法以及hashCode方法等极大地减少了手动编写这些重复代码的工作量使代码更加简洁。
@TableName("tz_attach_file")
// MyBatis Plus的@TableName注解用于将当前的实体类与数据库中的具体表进行映射关联这里表明AttachFile类对应数据库中的"tz_attach_file"表,方便后续进行数据库操作时框架能够准确地知道操作的数据来源和目标表。
public class AttachFile implements Serializable {
// 定义一个名为AttachFile的公共类它实现了Serializable接口意味着该类的实例对象能够进行序列化操作比如可以保存到文件、在网络间传输等场景中使用。
@TableId
// MyBatis Plus的@TableId注解用于标识该成员变量对应的是数据库表中的主键字段在数据库操作如查询、更新、删除等主键是用于唯一标识每条记录的关键属性。
private Long fileId;
// 定义一个名为fileId的私有成员变量类型为Long用于存储文件在系统中的唯一标识符通常对应数据库表中主键列的值通过它可以准确地定位和操作某一条具体的文件相关记录。
/**
*
* filePath
*/
private String filePath;
// 定义一个名为filePath的私有成员变量类型为String用于记录文件的存储路径。
/**
*
* fileType"txt""jpg""pdf"PDF便
*/
private String fileType;
// 定义一个名为fileType的私有成员变量类型为String用于存储文件的类型标识。
/**
*
* fileSize使
*/
private Integer fileSize;
// 定义一个名为fileSize的私有成员变量类型为Integer用于存储文件的大小数值通常以字节为单位来衡量。
/**
*
* uploadTime
*/
private Date uploadTime;
// 定义一个名为uploadTime的私有成员变量类型为Date用于保存文件上传的时间信息。
/**
* id
* fileJoinId
*/
private Long fileJoinId;
// 定义一个名为fileJoinId的私有成员变量类型为Long用于记录与文件相关联的其他表的主键值。
/**
* 1 @see FileJoinType
* fileJoinType1FileJoinType@see
*/
private Integer fileJoinType;
// 定义一个名为fileJoinType的私有成员变量类型为Integer用于标识文件关联表的类型信息。
}

@ -0,0 +1,75 @@
package com.yami.shop.bean.model;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* Basket
* "tz_basket"MyBatis Plus
* Serializable便使
*
* @author lanhai
*/
@Data
@TableName("tz_basket")
public class Basket implements Serializable {
/**
*
* "tz_basket"
*/
@TableId
private Long basketId;
/**
* ID
* ID
*/
private Long shopId;
/**
* ID
* ID
*/
private Long prodId;
/**
* SkuIDStock Keeping Unit
* SkuID
*/
private Long skuId;
/**
* ID
* ID
* ID
*/
private String userId;
/**
*
*
*/
private Integer basketCount;
/**
*
*
*/
private Date basketDate;
/**
* ID
* ID
*/
private Long discountId;
/**
* 广广
*
*/
private String distributionCardNo;
}

@ -0,0 +1,96 @@
package com.yami.shop.bean.model;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* Brand"tz_brand"
* MyBatis Plus@TableName
* Serializable便
*
* @author lanhai
*/
@Data
@TableName("tz_brand")
public class Brand implements Serializable {
/**
*
* "tz_brand"
*
*/
@TableId
private Long brandId;
/**
*
*
*/
private String brandName;
/**
*
* URL
* logo
*/
private String brandPic;
/**
* ID
* ID
* 便
*/
private String userId;
/**
*
* 沿便
*/
private String memo;
/**
*
* 便
*/
private Integer seq;
/**
* 10线
*
* 0使线
*/
private Integer status;
/**
*
*
*/
private String brief;
/**
*
*
*/
private Date recTime;
/**
*
*
*/
private Date updateTime;
/**
*
* 便
*/
private String firstChar;
/**
*
* 使
*/
private String content;
}

@ -0,0 +1,170 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
// 声明该类所在的包名按照Java的包结构组织方式将相关的类放在同一个包下方便管理和区分不同功能模块的代码
// 这里表示该类属于com.yami.shop.bean.model包。
package com.yami.shop.bean.model;
// 导入MyBatis Plus的注解TableField用于定义实体类中字段与数据库表字段之间的一些额外映射关系
// 比如指定字段是否在数据库表中有对应列等情况。
import com.baomidou.mybatisplus.annotation.TableField;
// 导入MyBatis Plus的注解TableId用于标记实体类中的主键字段使得MyBatis Plus能正确识别与数据库表主键的对应关系。
import com.baomidou.mybatisplus.annotation.TableId;
// 导入MyBatis Plus的注解TableName用于将实体类与数据库中的具体表进行映射表明该实体类对应哪个数据库表。
import com.baomidou.myatisplus.annotation.TableName;
// 导入Jackson的注解JsonSerialize用于指定在将对象转换为JSON格式时对特定字段使用自定义的序列化方式。
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
// 导入自定义的ImgJsonSerializer类它应该是用于处理图片相关字段序列化的类
// 在这里配合@JsonSerialize注解来对特定的图片字段进行定制化的JSON序列化操作。
import com.yami.shop.common.serializer.json.ImgJsonSerializer;
// 导入Lombok的Data注解使用该注解后Lombok会自动为类生成常用的方法比如getter、setter、toString等方法
// 减少了手动编写这些重复代码的工作量,提高代码的简洁性和开发效率。
import lombok.Data;
// 导入Java的相关接口和类用于实现对象的序列化功能Serializable接口以及处理日期相关操作Date类
// 还有用于操作列表数据结构List接口使得该类能更好地满足常见的业务需求比如数据存储、传输等场景。
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* lanhai
* 便
* @author lanhai
*/
// 使用@Data注解让Lombok自动生成类的getter、setter等常用方法简化代码编写。
// 使用@TableName注解将该Category类与数据库中的"tz_category"表进行映射关联,意味着该类的实例可以与数据库表中的记录相互转换操作。
@Data
@TableName("tz_category")
public class Category implements Serializable {
/**
* ID
* "tz_category"
* ID
*/
@TableId
private Long categoryId;
/**
* id
* id
*
*/
private Long shopId;
/**
* ID
* 0L
* 便
*/
private Long parentId = 0L;
/**
*
*
*/
private String categoryName;
/**
*
* URL
* 使
*/
private String icon;
/**
* @JsonSerialize使ImgJsonSerializer
* JSON
* 便
*/
@JsonSerialize(using = ImgJsonSerializer.class)
private String pic;
/**
*
* 便
*
*/
private Integer seq;
/**
* 10线
* 使
* 0使线
*/
private Integer status;
/**
*
*
*/
private Date recTime;
/**
*
* 12便
*/
private Integer grade;
/**
*
*
*
*/
private Date updateTime;
/**
* id使@TableField(exist=false)"tz_category"
* ID便
*/
@TableField(exist=false)
private List<Long> brandIds;
/**
* id使@TableField(exist=false)
* ID
*
*/
@TableField(exist=false)
private List<Long> attributeIds;
/**
* @TableField(exist=false)
* ID便
* 使
*/
@TableField(exist=false)
private List<Brand> brands;
/**
* @TableField(exist=false)
* 便
*
*/
@TableField(exist=false)
private List<ProdProp> prodProps;
/**
* 使@TableField(exist=false)
* 便
*
*/
@TableField(exist=false)
private List<Product> products;
/**
* @TableField(exist=false)
* 便
*
*/
@TableField(exist=false)
private List<Category> categories;
}

@ -0,0 +1,59 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.app.dto;
import lombok.Data;
import java.io.Serializable;
/**
* DTO
*
* 便
* 使
* 便
*
* @author lanhai
*/
@Data
public class ChooseDiscountItemDto implements Serializable {
// 此处可根据实际业务需求添加相应的属性及对应的注释,以下为示例,可按需调整替换
/**
*
* ID
* 便
*/
private Long discountItemId;
/**
* ID
* ID
* 便
*/
private Long discountActivityId;
/**
*
*
*
*/
private Double discountAmount;
/**
* truefalse
* true
* false
* 便
*/
private boolean isActive;
}

@ -0,0 +1,53 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.model;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
/**
* `CategoryProp`
* 便
*
*
* @author lanhai
*/
@Data
@TableName("tz_category_prop")
public class CategoryProp implements Serializable {
/**
* `id`
* `id`
*
*
*/
@TableId
private Long id;
/**
* `categoryId`ID
* ID
* ID
*
*/
private Long categoryId;
/**
* `propId`ID `tz_prod_prop` `prop_id`
*
* `propId` ID
*
* 便
*/
private Long propId;
}

@ -0,0 +1,73 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.model;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* `Delivery`
* 便
*
* @author lanhai
*/
@Data
@TableName("tz_delivery")
public class Delivery implements Serializable {
/**
* `dvyId`
* `dvyId`
* ID
*
*/
@TableId
private Long dvyId;
/**
* `dvyName`
*
* 便
*/
private String dvyName;
/**
* `companyHomeUrl`访
*
* 使便访
*/
private String companyHomeUrl;
/**
* `recTime`
* `Date` `yyyy-MM-dd HH:mm:ss`
*
*/
private Date recTime;
/**
* `modifyTime` `Date`
*
* 便
*/
private Date modifyTime;
/**
* `queryUrl`
*
*
*/
private String queryUrl;
}

@ -0,0 +1,68 @@
package com.yami.shop.bean.model;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
/**
* HotSearch"tz_hot_search"
* MyBatis Plus@TableName
* Serializable便
*
* @author lanhai
*/
@Data
@TableName("tz_hot_search")
public class HotSearch implements Serializable {
/**
*
* "tz_hot_search"
*
*/
@TableId
private Long hotSearchId;
/**
* id
* id
*
*/
private Long shopId;
/**
*
* 便
*/
private String title;
/**
*
*
*/
private String content;
/**
*
*
* @DateTimeFormat"yyyy-MM-dd HH:mm:ss"便
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date recDate;
/**
*
* 使
*/
private Integer seq;
/**
* 10线
*
* 0使线
*/
private Integer status;
}

@ -0,0 +1,129 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.yami.shop.common.serializer.json.ImgJsonSerializer;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* `IndexImg`
* 便
*
* @author lanhai
*/
@Data
@TableName("tz_index_img")
public class IndexImg implements Serializable {
private static final long serialVersionUID = -3468251351681518798L;
/**
* `imgId`
* `imgId`
* ID
*
*/
@TableId
private Long imgId;
/**
* `shopId`ID
*
* ID
*/
private Long shopId;
/**
* `imgUrl`访
* 使URL
*
*/
private String imgUrl;
/**
* `des`
*
*
*/
private String des;
/**
* `title`
*
*
*/
private String title;
/**
* `link`
*
*
*/
private String link;
/**
* `status` `0`
* `1` 便
*
*/
private Integer status;
/**
* `seq`
* `seq`
*
*/
private Integer seq;
/**
* `uploadTime` `Date` `yyyy-MM-dd HH:mm:ss`
*
*/
private Date uploadTime;
/**
* `type` `1` `2`
* `3`
* 便
*/
private int type;
/**
* `relation` `id` `id`
*
* 便
*/
private Long relation;
/**
* `pic` `@TableField(exist = false)`
* 使 `@JsonSerialize(using = ImgJsonSerializer.class)`
* JSON使
* 使便
*/
@TableField(exist = false)
@JsonSerialize(using = ImgJsonSerializer.class)
private String pic;
/**
* `prodName` `@TableField(exist = false)`
*
*
*/
@TableField(exist = false)
private String prodName;
}

@ -0,0 +1,104 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.app.param;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* `LoginParam`
* 便
*
* @author lanhai
*/
@Schema(description = "登陆参数")
public class LoginParam {
/**
* `code` `code` `code`
* `code` `code`
* `@Schema` `code`使 `code`
* `required = true` `code`
*
*/
@Schema(description = "小程序登陆时返回的code(使用code登陆必填)", required = true)
private String code;
/**
* `mobile`
* 便
* `@Schema`
* 使
*/
@Schema(description = "登陆时的用户名(账号密码登陆必填)", required = true)
private String mobile;
/**
* `password` `mobile`
*
* `@Schema`
* 使
*/
@Schema(description = "登陆时的密码(账号密码登陆必填)", required = true)
private String password;
/**
* `getCode`JavaBean访Getter `code`
* 访 `code`
* 访
*/
public String getCode() {
return code;
}
/**
* `setCode` `getCode` Setter `code`
* `code`
* JavaBean便
*/
public void setCode(String code) {
this.code = code;
}
/**
* `getMobile` `mobile` Getter `getCode`
* 使便
*/
public String getMobile() {
return mobile;
}
/**
* `setMobile` `getMobile` Setter `mobile`
*
*
*/
public void setMobile(String mobile) {
this.mobile = mobile;
}
/**
* `getPassword` `password` Getter
* 便使
*
*/
public String getPassword() {
return password;
}
/**
* `setPassword` `password` Setter
*
*/
public void setPassword(String password) {
this.password = password;
}
}

@ -0,0 +1,76 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.model;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
/**
*
*
* @author hzm
* @date 2019-04-18 21:21:40
*/
@Data
@TableName("tz_notice")
public class Notice implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId
private Long id;
/**
* id
*/
private Long shopId;
/**
*
*/
private String title;
/**
*
*/
private String content;
/**
* (1: 0:)
*/
private Integer status;
/**
* 1: 0
*/
private Integer isTop;
/**
*
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date publishTime;
/**
*
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

@ -0,0 +1,217 @@
package com.yami.shop.bean.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* @author lanhai
* @description Order
* 便便
*/
@Data
// 使用@TableName注解指定该实体类对应的数据库表名为"tz_order"用于MyBatis Plus框架建立实体与表的映射关系
@TableName("tz_order")
public class Order implements Serializable {
private static final long serialVersionUID = 6222259729062826852L;
/**
* @description ID使@TableId"tz_order"
*/
@TableId
private Long orderId;
/**
* @description idid
*/
private Long shopId;
/**
* @description
*
*/
private String prodName;
/**
* @description ID
* 便
*/
private String userId;
/**
* @description
*
*/
private String orderNumber;
/**
* @description
*/
private Double total;
/**
* @description
*
*/
private Double actualTotal;
/**
* @description 1 2
* 便退
*/
private Integer payType;
/**
* @description
* 便
*/
private String remarks;
/**
* @description
* - -1
* - 0
* - 1
* - 2
* - 3
*
*/
private Integer status;
/**
* @description
* 便
*/
private String dvyType;
/**
* @description ID
*
*/
private Long dvyId;
/**
* @description
* 便
*/
private String dvyFlowId;
/**
* @description
*
*/
private Double freightAmount;
/**
* @description Id
*
*/
private Long addrOrderId;
/**
* @description 便
*
*/
private Integer productNums;
/**
* @description 使@DateTimeFormat"yyyy-MM-dd HH:mm:ss"
*
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* @description 使@DateTimeFormat
* 便
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/**
* @description 使@DateTimeFormat
* 退
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date payTime;
/**
* @description 使@DateTimeFormat
*
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date dvyTime;
/**
* @description 使@DateTimeFormat
*
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date finallyTime;
/**
* @description 使@DateTimeFormat
*
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date cancelTime;
/**
* @description 10
* 退
*/
private Integer isPayed;
/**
* @description
* - 0
* - 1
* - 2
* 便
*/
private Integer deleteStatus;
/**
* @description 退退
* - 0退
* - 1退退
* - 2退便退
*/
private Integer refundSts;
/**
* @description
*
*/
private Double reduceAmount;
/**
* @description 使@TableField(exist = false)"tz_order"
*
* 便
*/
@TableField(exist = false)
private String shopName;
/**
* @description 使@TableField(exist = false)
* OrderItemID
* 便
*/
@TableField(exist = false)
private List<OrderItem> orderItems;
/**
* @description 使@TableField(exist = false)
* UserAddrOrder
* 便
*/
@TableField(exist = false)
private UserAddrOrder userAddrOrder;
}

@ -0,0 +1,54 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.order;
/**
* `ConfirmOrderOrder`
*
*
*
* @author LGH
*/
public interface ConfirmOrderOrder {
/**
* `DEFAULT`0
*
*
*
*/
int DEFAULT = 0;
/**
* `DISCOUNT`100 `DEFAULT`
*
* `DEFAULT`
*
*/
int DISCOUNT = 100;
/**
* `COUPON`200 `DISCOUNT`
* 使
* `DISCOUNT`
* 使
*/
int COUPON = 200;
/**
* `DISTRIBUTION`300 `COUPON`
*
* `COUPON`
*
*/
int DISTRIBUTION = 300;
}

@ -0,0 +1,129 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.bean.param;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
/**
* `DeliveryOrderParam`
* 便
*
* @author lanhai
*/
public class DeliveryOrderParam {
/**
* `orderNumber`
*
*
*
*
* 使 `@NotBlank`
*
* `@Schema` API 使 Swagger
* `required = true`
*
*/
@NotBlank(message = "订单号不能为空")
@Schema(description = "订单号", required = true)
private String orderNumber;
/**
* `dvyId` ID `Long`
* ID
* ID
* `dvyId`
*
* `@NotBlank` id
* ID `@Schema` API
* 便
*
*/
@NotBlank(message = "快递公司id不能为空")
@Schema(description = "快递公司", required = true)
private Long dvyId;
/**
* `dvyFlowId`
*
*
*
* 使 `@NotBlank`
* `@Schema` API
*
*/
@NotBlank(message = "物流单号不能为空")
@Schema(description = "物流单号", required = true)
private String dvyFlowId;
/**
* `getDvyId` JavaBean 访Getter `dvyId`
* `DeliveryOrderParam` ID
* 访
* 访使
*/
public Long getDvyId() {
return dvyId;
}
/**
* `setDvyId` `getDvyId` Setter `dvyId`
* ID
* `Long` `dvyId`
* ID ID
* ID
*/
public void setDvyId(Long dvyId) {
this.dvyId = dvyId;
}
/**
* `getDvyFlowId` Getter `dvyFlowId`
*
* JavaBean 使访便使
*/
public String getDvyFlowId() {
return dvyFlowId;
}
/**
* `setDvyFlowId` `getDvyFlowId` Setter `dvyFlowId`
*
* `DeliveryOrderParam`
*
*
*/
public void setDvyFlowId(String dvyFlowId) {
this.dvyFlowId = dvyFlowId;
}
/**
* `getOrderNumber` `orderNumber` Getter Getter
*
*
* JavaBean 使访
*/
public String getOrderNumber() {
return orderNumber;
}
/**
* `setOrderNumber` `getOrderNumber` Setter `orderNumber`
*
*
*
*/
public void setOrderNumber(String orderNumber) {
this.orderNumber = orderNumber;
}
}

@ -0,0 +1,34 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.common.bean;
// 导入lombok的Data注解使用该注解可以自动生成类的一些常规方法比如getter、setter、toString等方法简化代码编写
import lombok.Data;
/**
*
* 访ID访
* 便
* @author LGH
*/
@Data
// 定义名为AliDaYu的公共类用于封装阿里大鱼配置相关的属性
public class AliDaYu {
// 以下是类的私有属性,用于存储具体的配置信息
// 用于存储阿里大鱼的访问密钥ID通过该ID来标识访问阿里大鱼服务的身份凭证之一
private String accessKeyId;
// 用于存储阿里大鱼的访问密钥密码与访问密钥ID配合使用用于进行安全验证等操作确保对阿里大鱼服务的合法访问
private String accessKeySecret;
// 用于存储阿里大鱼的签名名称,在涉及到一些需要签名验证的业务场景中会用到该名称
private String signName;
}

@ -0,0 +1,44 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.common.bean;
// 引入Lombok的Data注解通过该注解编译器会自动帮我们生成类的常用方法
// 比如各属性的Getter、Setter方法以及toString、equals、hashCode方法等简化代码编写
import lombok.Data;
/**
* ImgUpload
* 便使
*
* @author lgh
*/
@Data
public class ImgUpload {
/**
*
* 便访
*/
private String imagePath;
/**
*
* 1imagePath
* 2使
*/
private Integer uploadType;
/**
* 访URL访
* resourceUrl访URL访
*/
private String resourceUrl;
}

@ -0,0 +1,133 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.common.config;
// 导入相关的自定义异常类、响应相关的枚举、响应实体类等
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ResponseEnum;
import com.yami.shop.common.response.ServerResponseEntity;
// 导入日志相关的Lombok注解用于简化日志记录代码
import lombok.extern.slf4j.Slf4j;
// 导入Spring的HTTP状态码相关枚举类
import org.springframework.http.HttpStatus;
// 导入Spring用于构建HTTP响应实体的类
import org.springframework.http.ResponseEntity;
// 导入Spring用于标记控制器类的注解此处结合异常处理相关特性使用
import org.springframework.stereotype.Controller;
// 导入Spring在数据校验场景下的异常类以及表示字段错误的类
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
// 导入Spring用于定义异常处理方法的注解以及标记全局异常处理类的注解
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
// 导入Spring用于处理资源未找到异常的类
import org.springframework.web.servlet.resource.NoResourceFoundException;
import java.util.ArrayList;
import java.util.List;
/**
*
*
* @author LGH
*/
@Slf4j
@Controller
@RestControllerAdvice
public class DefaultExceptionHandlerConfig {
/**
* MethodArgumentNotValidExceptionBindException
* Spring
*
*
* @param e MethodArgumentNotValidExceptionBindException
* @return ResponseEntity<ServerResponseEntity<List<String>>> HTTP
*
*/
@ExceptionHandler({ MethodArgumentNotValidException.class, BindException.class })
public ResponseEntity<ServerResponseEntity<List<String>>> methodArgumentNotValidExceptionHandler(Exception e) {
// 记录异常信息,方便后续查看出现异常的具体情况,用于调试和问题排查
log.error("methodArgumentNotValidExceptionHandler", e);
List<FieldError> fieldErrors = null;
// 判断异常类型是否是MethodArgumentNotValidException如果是则获取对应的字段错误列表
if (e instanceof MethodArgumentNotValidException) {
fieldErrors = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors();
}
// 判断异常类型是否是BindException如果是则获取对应的字段错误列表
if (e instanceof BindException) {
fieldErrors = ((BindException) e).getBindingResult().getFieldErrors();
}
// 如果没有获取到字段错误列表(可能出现不符合预期的异常情况等)
if (fieldErrors == null) {
// 返回一个包含特定失败响应枚举(表示参数校验不通过)的响应实体,没有具体字段错误信息
return ResponseEntity.status(HttpStatus.OK)
.body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID));
}
// 用于存放每个字段的错误信息字符串(格式为:字段名:默认错误消息)
List<String> defaultMessages = new ArrayList<>(fieldErrors.size());
// 遍历字段错误列表拼接每个字段的错误信息字符串并添加到defaultMessages列表中
for (FieldError fieldError : fieldErrors) {
defaultMessages.add(fieldError.getField() + ":" + fieldError.getDefaultMessage());
}
// 返回一个包含具体字段错误信息列表的失败响应实体,使用参数校验不通过的响应枚举标识
return ResponseEntity.status(HttpStatus.OK)
.body(ServerResponseEntity.fail(ResponseEnum.METHOD_ARGUMENT_NOT_VALID, defaultMessages));
}
/**
* YamiShopBindException
* YamiShopBindException
*
*
* @param e YamiShopBindException
* @return ResponseEntity<ServerResponseEntity<?>> HTTP
*
*/
@ExceptionHandler(YamiShopBindException.class)
public ResponseEntity<ServerResponseEntity<?>> unauthorizedExceptionHandler(YamiShopBindException e){
// 记录异常信息,方便后续排查问题,了解异常出现的具体情况
log.error("mall4jExceptionHandler", e);
ServerResponseEntity<?> serverResponseEntity = e.getServerResponseEntity();
// 如果异常中已经封装好了响应实体(在抛出异常时可能已经构建好了特定的响应内容)
if (serverResponseEntity!=null) {
// 直接将其作为响应内容返回给客户端设置状态码为OK
return ResponseEntity.status(HttpStatus.OK).body(serverResponseEntity);
}
// 失败返回消息,状态码固定为直接显示消息的状态码,根据异常的错误码和错误消息构建失败响应实体并返回
return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(e.getCode(),e.getMessage()));
}
/**
* Exception
*
*
*
* @param e Exception
* @return ResponseEntity<ServerResponseEntity<Object>> HTTP
*
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ServerResponseEntity<Object>> exceptionHandler(Exception e){
// 判断异常是否是资源未找到异常类型
if (e instanceof NoResourceFoundException) {
// 如果是则返回一个包含异常消息的失败响应实体告知客户端资源未找到相关错误信息状态码设为OK
return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.showFailMsg(e.getMessage()));
}
// 记录异常信息,方便后续排查问题,知道出现异常的具体情况
log.error("exceptionHandler", e);
// 返回一个使用默认的异常响应枚举构建的失败响应实体,告知客户端出现了未预期的通用异常情况
return ResponseEntity.status(HttpStatus.OK).body(ServerResponseEntity.fail(ResponseEnum.EXCEPTION));
}
}

@ -0,0 +1,112 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.common.config;
// 导入相关的自定义类,可能用于存储图片上传相关的配置、枚举等信息
import com.yami.shop.common.bean.ImgUpload;
import com.yami.shop.common.enums.QiniuZone;
// 导入Spring的注解相关类用于实现依赖注入和配置相关功能
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 导入七牛云相关的类,用于与七牛云存储服务进行交互,涉及区域、上传管理、认证等功能
import com.qiniu.common.Zone;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.UploadManager;
import com.qiniu.util.Auth;
import com.yami.shop.common.bean.Qiniu;
import java.util.Objects;
/**
*
*
* 便
* @author lgh
*/
@Configuration
public class FileUploadConfig {
// 通过Spring的依赖注入获取Qiniu类型的配置对象该对象应该包含了七牛云相关的配置信息如密钥、机房区域等
@Autowired
private Qiniu qiniu;
/**
* com.qiniu.storage.Configuration
* QiniuQiniuZoneZone
* 使
*
* @return com.qiniu.storage.Configuration
*/
@Bean
public com.qiniu.storage.Configuration qiniuConfig() {
Zone zone = null;
// 判断配置中的机房区域是否为华北区如果是则设置对应的七牛云机房区域为华北区Zone.huabei()
if (Objects.equals(qiniu.getZone(), QiniuZone.HUA_BEI)) {
zone = Zone.huabei();
}
// 判断是否为华东区若是则设置对应的七牛云机房区域为华东区Zone.huadong()
else if (Objects.equals(qiniu.getZone(), QiniuZone.HUA_DONG)) {
zone = Zone.huadong();
}
// 判断是否为华南区若是则设置对应的七牛云机房区域为华南区Zone.huanan()
else if (Objects.equals(qiniu.getZone(), QiniuZone.HUA_NAN)) {
zone = Zone.huanan();
}
// 判断是否为北美区若是则设置对应的七牛云机房区域为北美区Zone.beimei()
else if (Objects.equals(qiniu.getZone(), QiniuZone.BEI_MEI)) {
zone = Zone.beimei();
}
// 判断是否为新加坡区若是则设置对应的七牛云机房区域为新加坡区Zone.xinjiapo()
else if (Objects.equals(qiniu.getZone(), QiniuZone.XIN_JIA_PO)) {
zone = Zone.xinjiapo();
}
// 使用确定好的机房区域zone构建七牛云存储配置实例并返回
return new com.qiniu.storage.Configuration(zone);
}
/**
* UploadManager
* qiniuConfigcom.qiniu.storage.Configuration
*
*
* @return UploadManager
*/
@Bean
public UploadManager uploadManager() {
return new UploadManager(qiniuConfig());
}
/**
* Auth
* Qiniu访accessKeysecretKey
*
*
* @return Auth 访
*/
@Bean
public Auth auth() {
return Auth.create(qiniu.getAccessKey(), qiniu.getSecretKey());
}
/**
* BucketManager
* authAuthqiniuConfig
*
*
* @return BucketManager
*/
@Bean
public BucketManager bucketManager() {
return new BucketManager(auth(), qiniuConfig());
}
}

@ -0,0 +1,26 @@
package com.yami.shop.common.constants;
/**
* Constant
*
* 便
*
*
* @author TRACK
*/
public class Constant {
/**
* PERIOD
* 使
* 使
*/
public static final String PERIOD = ".";
/**
* COMMA
* CSV
* 便使
*/
public static final String COMMA = ",";
}

@ -0,0 +1,131 @@
package com.yami.shop.common.handler;
// 导入 Hutool 工具库中处理字符集相关的工具类,用于设置响应的字符编码
import cn.hutool.core.util.CharsetUtil;
// 导入 Jackson 库中用于将对象转换为 JSON 字符串以及反序列化等操作的核心类
import com.fasterxml.jackson.databind.ObjectMapper;
// 导入自定义的业务异常类,可能在项目中用于处理特定业务逻辑出错的情况
import com.yami.shop.common.exception.YamiShopBindException;
// 导入自定义的用于封装服务器响应信息的实体类,包含响应状态码、消息、数据等内容
import com.yami.shop.common.response.ServerResponseEntity;
// 导入 Slf4j 框架的日志记录相关类,用于创建日志记录器来记录不同情况的日志信息
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// 导入 Spring 框架用于实现依赖注入的注解,表明某个属性需要由 Spring 容器进行注入
import org.springframework.beans.factory.annotation.Autowired;
// 导入 Spring 框架中定义媒体类型的枚举类,用于设置响应的内容类型为 JSON 格式
import org.springframework.http.MediaType;
// 导入 Spring 框架用于将类标记为组件的注解,表明该类是一个 Spring 管理的组件,可被自动扫描并注入到其他需要的地方
import org.springframework.stereotype.Component;
// 导入 Spring 框架中用于获取请求上下文相关信息的类和接口
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
// 导入 Servlet 相关的用于操作 HTTP 响应的类,用于设置响应的各种属性以及向客户端输出内容
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Objects;
/**
* HttpHandler Web
* JSON HTTP
*
*
* @author
* @date 2022/3/28 14:15
*/
@Component
public class HttpHandler {
// 创建一个日志记录器,用于记录该类中不同操作阶段的日志信息,方便后续进行问题排查和调试
private static final Logger logger = LoggerFactory.getLogger(HttpHandler.class);
// 通过 Spring 的依赖注入机制,注入一个 ObjectMapper 对象,用于将对象转换为 JSON 字符串以便输出到客户端
@Autowired
private ObjectMapper objectMapper;
/**
* ServerResponseEntity JSON Web HTTP
* null
* HTTP
* I/O YamiShopBindException
*
* @param serverResponseEntity
* @param <T>
*/
public <T> void printServerResponseToWeb(ServerResponseEntity<T> serverResponseEntity) {
// 如果传入的服务器响应实体为 null记录日志提示信息并直接返回不进行后续操作
if (serverResponseEntity == null) {
logger.info("print obj is null");
return;
}
// 从 Spring 的请求上下文中获取 ServletRequestAttributes 对象,它包含了与当前请求相关的信息,如请求和响应对象等
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
// 如果获取到的 ServletRequestAttributes 对象为 null说明无法获取到请求上下文相关信息记录错误日志并返回无法进行响应输出操作
if (requestAttributes == null) {
logger.error("requestAttributes is null, can not print to web");
return;
}
// 从 ServletRequestAttributes 对象中获取 HttpServletResponse 对象,用于后续设置响应属性和向客户端输出内容
HttpServletResponse response = requestAttributes.getResponse();
// 如果获取到的 HttpServletResponse 对象为 null说明无法获取到有效的 HTTP 响应对象,记录错误日志并返回,无法进行响应输出操作
if (response == null) {
logger.error("httpServletResponse is null, can not print to web");
return;
}
// 记录响应的错误消息(这里假设 getMsg 方法获取的是错误相关信息,实际情况可能根据 ServerResponseEntity 的具体实现而定)到日志中,方便排查问题
logger.error("response error: " + serverResponseEntity.getMsg());
// 设置 HTTP 响应的字符编码为 UTF-8确保输出的内容能够正确地被客户端解析尤其是包含中文等多字节字符的情况
response.setCharacterEncoding(CharsetUtil.UTF_8);
// 设置 HTTP 响应的内容类型为 application/json表明响应的内容是 JSON 格式的数据,让客户端能够正确识别并解析
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
// 用于向 HTTP 响应中写入字符数据的对象,初始化为 null后续通过获取响应的输出流来实例化
PrintWriter printWriter = null;
try {
// 获取 HttpServletResponse 的输出流对象,用于向客户端写入响应内容(这里是将服务器响应实体转换后的 JSON 字符串写入)
printWriter = response.getWriter();
// 使用注入的 ObjectMapper 对象将服务器响应实体转换为 JSON 字符串,并写入到 HTTP 响应的输出流中,从而输出到客户端
printWriter.write(objectMapper.writeValueAsString(serverResponseEntity));
} catch (IOException e) {
// 如果在写入过程中出现 I/O 异常抛出自定义的业务异常YamiShopBindException并将原始的 I/O 异常作为原因传递,方便上层进行统一的异常处理和日志记录
throw new YamiShopBindException("io 异常", e);
}
}
/**
* YamiShopBindException JSON Web HTTP
* null
* ServerResponseEntity printServerResponseToWeb
* printServerResponseToWeb
*
* @param yamiShopBindException YamiShopBindException
* @param <T>
*/
public <T> void printServerResponseToWeb(YamiShopBindException yamiShopBindException) {
// 如果传入的 YamiShopBindException 异常对象为 null记录日志提示信息并直接返回不进行后续操作
if (yamiShopBindException == null) {
logger.info("print obj is null");
return;
}
// 判断异常对象中是否包含了服务器响应实体ServerResponseEntity如果包含则直接调用 printServerResponseToWeb 方法输出该实体内容到客户端
if (Objects.nonNull(yamiShopBindException.getServerResponseEntity())) {
printServerResponseToWeb(yamiShopBindException.getServerResponseEntity());
return;
}
// 如果异常对象中没有包含服务器响应实体,则创建一个新的 ServerResponseEntity 对象,用于封装异常中的错误码和错误消息等信息
ServerResponseEntity<T> serverResponseEntity = new ServerResponseEntity<>();
serverResponseEntity.setCode(yamiShopBindException.getCode());
serverResponseEntity.setMsg(yamiShopBindException.getMessage());
// 调用 printServerResponseToWeb 方法将构建好的服务器响应实体输出到客户端,完成响应信息的输出操作
printServerResponseToWeb(serverResponseEntity);
}
}

@ -0,0 +1,104 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.common.serializer.json;
// 导入Hutool工具库中用于字符串操作的工具类例如判断字符串是否为空、处理字符串拼接等操作
import cn.hutool.core.util.StrUtil;
// 导入Jackson库中用于自定义JSON序列化相关的核心类用于定义如何将Java对象序列化为JSON格式
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
// 导入自定义的类Qiniu类可能包含了七牛云相关的配置信息如资源访问地址等
import com.yami.shop.common.bean.Qiniu;
// 导入自定义的图片上传工具类,可能用于获取图片上传相关的配置信息,比如上传类型、资源地址等
import com.yami.shop.common.util.ImgUploadUtil;
// 导入Spring框架用于实现依赖注入的注解以及将类标记为组件的注解表明该类是受Spring管理的组件可被自动注入到需要的地方
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* ImgJsonSerializerJSONJSON
* httphttps访
* JSONJSON
*
* @author lanhai
*/
@Component
public class ImgJsonSerializer extends JsonSerializer<String> {
// 通过Spring的依赖注入机制注入一个Qiniu对象该对象可能包含了七牛云存储相关的配置信息比如七牛云资源的访问地址等
@Autowired
private Qiniu qiniu;
// 注入一个ImgUploadUtil对象用于获取图片上传相关的配置信息例如图片上传类型等辅助对图片路径进行处理
@Autowired
private ImgUploadUtil imgUploadUtil;
/**
* JacksonserializeJSON
*
* @param value
* @param gen JacksonJsonGeneratorJSONJSON
* @param serializers JacksonSerializerProvider
* @throws IOException JsonGeneratorI/O
*/
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 判断传入的要序列化的字符串是否为空空白字符串包括null、空字符串以及只包含空格等不可见字符的字符串
if (StrUtil.isBlank(value)) {
// 如果为空则向JsonGenerator写入一个空字符串保持JSON格式的一致性然后直接返回不再进行后续处理
gen.writeString(StrUtil.EMPTY);
return;
}
// 将传入的字符串按照逗号进行分割,得到一个字符串数组,假设这里的字符串表示多个图片路径,以逗号分隔开,每个元素就是一个单独的图片路径
String[] imgs = value.split(StrUtil.COMMA);
// 创建一个可变的字符串构建器,用于拼接处理后的图片路径,方便后续构建最终的序列化字符串
StringBuilder sb = new StringBuilder();
// 用于存储资源访问地址,根据不同的上传类型来确定具体的值,后续会将其添加到图片路径前面(如果图片路径本身不符合要求的话)
String resourceUrl = "";
// 定义一个正则表达式字符串,用于匹配以"http"或"https"开头的字符串,目的是判断图片路径是否已经是完整的网络访问地址形式
String rule = "^((http[s]{0,1})://)";
// 使用定义好的正则表达式创建一个Pattern对象用于后续进行正则匹配操作
Pattern pattern = Pattern.compile(rule);
// 根据ImgUploadUtil对象获取的上传类型来确定资源访问地址resourceUrl的值
if (Objects.equals(imgUploadUtil.getUploadType(), 2)) {
// 如果上传类型为2从注入的Qiniu对象中获取资源访问地址可能是七牛云存储资源的访问地址用于构建完整的图片访问路径
resourceUrl = qiniu.getResourcesUrl();
} else if (Objects.equals(imgUploadUtil.getUploadType(), 1)) {
// 如果上传类型为1从ImgUploadUtil对象中获取资源访问地址可能是其他存储方式对应的资源访问地址
resourceUrl = imgUploadUtil.getResourceUrl();
}
// 遍历分割后的每个图片路径字符串
for (String img : imgs) {
// 使用创建的Pattern对象对当前图片路径字符串进行正则匹配得到一个Matcher对象用于查看是否匹配成功
Matcher matcher = pattern.matcher(img);
// 如果匹配成功,说明图片路径已经是以"http"或"https"开头的完整网络访问地址形式,直接将其添加到字符串构建器中,并添加逗号(保持与传入格式的一致性,后续会处理末尾多余的逗号)
if (matcher.find()) {
sb.append(img).append(StrUtil.COMMA);
} else {
// 如果图片路径不是完整的网络访问地址形式则将前面确定的资源访问地址resourceUrl、当前图片路径以及逗号依次添加到字符串构建器中构建完整的图片访问路径格式
sb.append(resourceUrl).append(img).append(StrUtil.COMMA);
}
}
// 删除字符串构建器中最后一个字符(末尾多余的逗号),得到最终处理好的图片路径字符串
sb.deleteCharAt(sb.length() - 1);
// 将处理好的图片路径字符串通过JsonGenerator写入到最终的JSON输出中完成序列化操作
gen.writeString(sb.toString());
}
}

@ -0,0 +1,168 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.common.util;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
*
* doubleBigDecimal
* @author lanhai
*/
public class Arith {
/**
* div2
* HALF_EVEN
*/
private static final int DEF_DIV_SCALE = 2;
/**
*
*
*/
private Arith() {
}
/**
*
* doubleBigDecimal
* double使double
*
* @param v1
* @param v2
* @return double
*/
public static double add(double v1, double v2) {
// 必须转换成String因为BigDecimal的构造函数建议使用基于字符串的构造方式来避免精度问题
// 如果直接使用double类型传入构造函数在某些情况下可能会出现精度丢失。
String s1 = Double.toString(v1);
String s2 = Double.toString(v2);
BigDecimal b1 = new BigDecimal(s1);
BigDecimal b2 = new BigDecimal(s2);
return b1.add(b2).doubleValue();
}
/**
*
* doubleBigDecimal
* double
*
* @param v1
* @param v2
* @return double
*/
public static double sub(double v1, double v2) {
String s1 = Double.toString(v1);
String s2 = Double.toString(v2);
BigDecimal b1 = new BigDecimal(s1);
BigDecimal b2 = new BigDecimal(s2);
return b1.subtract(b2).doubleValue();
}
/**
*
* doubleBigDecimal
* double
*
* @param v1
* @param v2
* @return double
*/
public static double mul(double v1, double v2) {
String s1 = Double.toString(v1);
String s2 = Double.toString(v2);
BigDecimal b1 = new BigDecimal(s1);
BigDecimal b2 = new BigDecimal(s2);
return b1.multiply(b2).doubleValue();
}
/**
* 10
* divDEF_DIV_SCALE
*
*
* @param v1
* @param v2
* @return double
*/
public static double div(double v1, double v2) {
return div(v1, v2, DEF_DIV_SCALE);
}
/**
* scale
* scale0
* 0doubleBigDecimal
* 使HALF_EVENdouble
*
* @param v1
* @param v2
* @param scale
* @return double
*/
public static double div(double v1, double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
String s1 = Double.toString(v1);
String s2 = Double.toString(v2);
BigDecimal b1 = new BigDecimal(s1);
BigDecimal b2 = new BigDecimal(s2);
return b1.divide(b2, scale, RoundingMode.HALF_EVEN).doubleValue();
}
/**
*
* scale0
* BigDecimal1BigDecimal
* scaleHALF_EVENdouble
*
* @param v double
* @param scale
* @return double
*/
public static double round(double v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
String s = Double.toString(v);
BigDecimal b = new BigDecimal(s);
BigDecimal one = new BigDecimal("1");
return b.divide(one, scale, RoundingMode.HALF_EVEN).doubleValue();
}
/**
* BigDecimal
* double便BigDecimal使
*
* @param bigDecimal BigDecimal
* @param bigDecimal2 BigDecimal
* @param bigDecimal3 BigDecimal
* @return double
*/
public static double add(BigDecimal bigDecimal, BigDecimal bigDecimal2, BigDecimal bigDecimal3) {
return bigDecimal.add(bigDecimal2).add(bigDecimal3).doubleValue();
}
/**
* BigDecimal
* doubleBigDecimal
*
* @param preDepositPrice BigDecimal
* @param finalPrice BigDecimal
* @return double
*/
public static double add(BigDecimal preDepositPrice, BigDecimal finalPrice) {
return preDepositPrice.add(finalPrice).doubleValue();
}
}

@ -0,0 +1,87 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.common.util;
import lombok.AllArgsConstructor;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
/**
* CacheManagerUtil便Spring
* SpringCacheManager
* 便
* Spring@Component使
*
* @author lanhai
*/
@Component
@AllArgsConstructor
public class CacheManagerUtil {
// Spring的缓存管理器用于获取具体的缓存对象等操作通过构造注入的方式获取
private CacheManager cacheManager;
/**
*
*
* @param <T>
* @param cacheName CacheManager
* @param key
* @return Tnull
*/
@SuppressWarnings({"unchecked"})
public <T> T getCache(String cacheName, String key) {
// 通过缓存管理器获取指定名称的缓存对象
Cache cache = cacheManager.getCache(cacheName);
if (cache == null) {
return null;
}
// 从缓存对象中尝试获取对应键的值的包装对象
Cache.ValueWrapper valueWrapper = cache.get(key);
if (valueWrapper == null) {
return null;
}
// 从包装对象中获取实际的值并转换为指定的泛型类型T返回
return (T) valueWrapper.get();
}
/**
*
*
* @param cacheName CacheManager
* @param key
* @param value Java
*/
public void putCache(String cacheName, String key, Object value) {
// 通过缓存管理器获取指定名称的缓存对象
Cache cache = cacheManager.getCache(cacheName);
if (cache!= null) {
// 如果缓存对象存在,则将键值对存入该缓存对象中
cache.put(key, value);
}
}
/**
*
*
* @param cacheName CacheManager
* @param key
*/
public void evictCache(String cacheName, String key) {
// 通过缓存管理器获取指定名称的缓存对象
Cache cache = cacheManager.getCache(cacheName);
if (cache!= null) {
// 如果缓存对象存在,则清除对应键的缓存数据
cache.evict(key);
}
}
}

@ -0,0 +1,66 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.common.util;
// 导入Spring相关的类用于获取请求相关的上下文信息基于请求上下文来获取HttpServletRequest对象
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
// 导入Servlet相关的类用于操作HTTP请求这里主要是HttpServletRequest它包含了请求相关的各种信息
import jakarta.servlet.http.HttpServletRequest;
/**
* HttpContextUtilsHTTP便
* SpringHttpServletRequest
* 便使
*
* @author lanhai
*/
public class HttpContextUtils {
/**
* 线HttpServletRequest
* SpringRequestContextHolderServletRequestAttributes
* HttpServletRequest
*
* @return HttpServletRequest HttpServletRequest
*
*/
public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
/**
*
* getHttpServletRequestHttpServletRequest
* getRequestURLURL
* getRequestURIURL
*
* @return String http://example.com:8080具体取决于实际请求情况
*/
public static String getDomain() {
HttpServletRequest request = getHttpServletRequest();
StringBuffer url = request.getRequestURL();
return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
}
/**
* Origin
* HttpServletRequestgetHeader"Origin"
*
*
* @return String Originhttp://example.com如果有Origin请求头的话否则返回null等情况
*/
public static String getOrigin() {
HttpServletRequest request = getHttpServletRequest();
return request.getHeader("Origin");
}
}

@ -0,0 +1,127 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.common.util;
// 导入 Hutool 库中用于生成分布式唯一ID的 Snowflake 类,通常基于雪花算法实现
import cn.hutool.core.lang.Snowflake;
// 导入 Spring 框架用于实现依赖注入的注解以及将类标记为组件的注解,表明该类是受 Spring 管理的组件
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* IdUtil
* 1. DICT DICT.length()
* 2. ID便使 ID
* 3. ID ID
* 4. Snowflake ID ID
*
* @author xuliugen
* @date 2018/04/23
*/
@Component
public class IdUtil {
// 通过 Spring 的依赖注入机制,注入一个 Snowflake 实例,用于生成分布式唯一 ID可能用于生成短 ID 的基础)
@Autowired
private Snowflake snowflake;
// 定义了一个包含数字和大小写字母(去除了容易混淆的部分字母)的字符串,作为自定义进制的字符集,用于进制转换操作
private static final String DICT = "0123456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
// 计算出基于 DICT 字符集的进制数,即字符集的长度,用于进制转换过程中的计算
private static final int SEED = DICT.length();
// 定义了短网址编码的最小长度,用于在生成短网址编码时,如果长度不足则进行补位操作,保证编码长度符合一定要求
private static final int ID_MIN_LENGTH = 6;
/**
* DICT 便
*/
private static final char[] CHARS = DICT.toCharArray();
/**
* Map
*/
private static final Map<Character, Integer> NUMBERS = new HashMap<>();
// 静态代码块,用于初始化 NUMBERS 这个字符到数字的映射 Map遍历 CHARS 字符数组,将每个字符与其对应的索引(在自定义进制下的数字)存入 Map 中
static {
int len = CHARS.length;
for (int i = 0; i < len; i++) {
NUMBERS.put(CHARS[i], i);
}
}
/**
* ID DICT
* SEED DICT
* ID_MIN_LENGTH DICT
*
* @param id ID (1 - 56.8 billion)
* @return "RwTji8""GijT7Y"
*/
public static String encode(long id) {
// 创建一个可变的字符串构建器,用于逐步构建短网址编码字符串
StringBuilder shortUrl = new StringBuilder();
// 当传入的十进制数字大于 0 时,进行进制转换操作,采用除基取余法
while (id > 0) {
// 计算当前十进制数除以自定义进制数SEED的余数将其转换为整数类型该余数将作为在 DICT 中查找对应字符的索引
int r = (int) (id % SEED);
// 将根据余数获取到的对应字符插入到短网址编码字符串的开头(逆序构建编码字符串)
shortUrl.insert(0, CHARS[r]);
// 更新十进制数,将其除以自定义进制数,得到下一轮循环要处理的数字
id = id / SEED;
}
// 获取当前已经构建好的短网址编码字符串的长度
int len = shortUrl.length();
// 如果长度小于最小长度要求ID_MIN_LENGTH进行补位操作
while (len < ID_MIN_LENGTH) {
// 在短网址编码字符串的开头插入 DICT 中的第一个字符(通常是 '0')进行补位
shortUrl.insert(0, CHARS[0]);
// 更新长度
len++;
}
// 返回最终构建好的短网址编码字符串
return shortUrl.toString();
}
/**
* ID
*
*
* @param key "RwTji8""GijT7Y"
* @return ID
*/
public static long decode(String key) {
// 将传入的短网址编码字符串转换为字符数组,方便逐个字符进行处理
char[] shorts = key.toCharArray();
// 获取字符数组的长度,即短网址编码的长度
int len = shorts.length;
// 初始化用于累加计算的十进制数字为 0
long id = 0L;
// 遍历短网址编码的每个字符,从左到右(按照权重从高到低)进行解析计算
for (int i = 0; i < len; i++) {
// 根据当前字符在 NUMBERS 映射 Map 中获取其对应的数字在自定义进制下的数字表示并乘以当前位置对应的权重SEED 的幂次方),然后累加到结果中
id = id + (long) (NUMBERS.get(shorts[i]) * Math.pow(SEED, len - i - 1));
}
// 返回解析得到的十进制数字(数据库记录 ID
return id;
}
/**
* Snowflake ID ID
* ID
*
* @return ID ID
*/
public String nextShortId() {
return encode(snowflake.nextId());
}
}

@ -0,0 +1,127 @@
package com.yami.shop.common.util;
// 导入Hutool工具库中用于字符串操作的工具类可用于判断字符串是否为空等操作
import cn.hutool.core.util.StrUtil;
// 导入自定义的用于封装图片上传相关配置信息的类包含存储路径、上传类型、资源访问URL等属性
import com.yami.shop.common.bean.ImgUpload;
// 导入自定义的业务异常类,用于在特定业务逻辑出现问题时抛出相应的异常信息,方便统一处理
import com.yami.shop.common.exception.YamiShopBindException;
// 导入Spring框架用于实现依赖注入的注解以及将类标记为组件的注解表明该类是受Spring管理的组件可在项目中被自动注入和使用
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
// 导入Spring用于处理文件上传的核心类代表上传的文件对象包含文件内容、文件名等信息
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
/**
* ImgUploadUtil
* 访URL
* 便
*
* @author TRACK
*/
@Component
public class ImgUploadUtil {
// 通过Spring的依赖注入机制注入一个ImgUpload对象该对象包含了本地文件上传相关的配置信息如存储路径、上传方式等
@Autowired
private ImgUpload imgUpload;
/**
* ImgUpload
* nullYamiShopBindException
*
*
* @return Integer 12使
*/
public Integer getUploadType() {
Integer uploadType = imgUpload.getUploadType();
if (Objects.isNull(uploadType)) {
throw new YamiShopBindException("请配置图片存储方式");
}
return uploadType;
}
/**
* ImgUploadimagePath
* nullYamiShopBindException
*
*
* @return String
*/
public String getUploadPath() {
String imagePath = imgUpload.getImagePath();
if (Objects.isNull(imagePath) || StrUtil.isBlank(imagePath)) {
throw new YamiShopBindException("请配置图片存储路径");
}
return imagePath;
}
/**
* 访URLImgUpload访URLresourceUrl
* null访URLYamiShopBindException
* 访URLURL访
*
* @return String 访URL访
*/
public String getResourceUrl() {
String resourceUrl = imgUpload.getResourceUrl();
if (Objects.isNull(resourceUrl) || StrUtil.isBlank(resourceUrl)) {
throw new YamiShopBindException("请配置图片路径");
}
return resourceUrl;
}
/**
* MultipartFile
*
* I/O
*
*
* @param img Spring
* @param fileName
* @return String 访
*/
public String upload(MultipartFile img, String fileName) {
// 获取配置的本地文件上传路径(存储文件夹路径)
String filePath = imgUpload.getImagePath();
// 根据文件上传路径和传入的文件名构建一个File对象代表要保存的目标文件
File file = new File(filePath + fileName);
// 判断目标文件所在的目录是否存在,如果不存在则尝试创建目录
if (!file.exists()) {
boolean result = file.mkdirs();
// 如果目录创建失败返回false抛出自定义的业务异常提示创建目录失败的具体路径信息
if (!result) {
throw new YamiShopBindException("创建目录:" + filePath + "失败");
}
}
try {
// 将上传的文件内容转移到目标文件中即将MultipartFile中的文件数据写入到本地创建好的目标文件里
img.transferTo(file);
} catch (IOException e) {
// 如果在文件转移写入过程中出现I/O异常抛出自定义的业务异常提示图片上传失败
throw new YamiShopBindException("图片上传失败");
}
// 文件上传成功后,返回上传后的文件名,方便后续业务使用
return fileName;
}
/**
*
* deleteOnExitJVM退
* JVM退
*
* @param fileName
*/
public void delete(String fileName) {
// 获取配置的本地文件上传路径(存储文件夹路径)
String filePath = imgUpload.getImagePath();
// 根据文件上传路径和传入的文件名构建一个File对象代表要删除的目标文件
File file = new File(filePath + fileName);
// 标记文件在JVM退出时删除注意这并不一定会立即删除文件而是在JVM正常退出时执行删除操作如果文件存在且可删除的话
file.deleteOnExit();
}
}

@ -0,0 +1,67 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.common.util;
// 导入Servlet相关的用于操作HTTP请求的类后续用于获取请求头以及客户端IP地址等信息
import jakarta.servlet.http.HttpServletRequest;
/**
* IpHelperIP
* IP
* IP
* IPIP
*
* @author lanhai
*/
public class IpHelper {
// 定义一个表示未知IP地址的常量字符串用于后续判断请求头中获取到的IP地址是否有效
private static final String UNKNOWN = "unknown";
/**
* IPIP
* "x-forwarded-for"IPIP
* IP0"unknown""Proxy-Client-IP"
* "WL-Proxy-Client-IP"
* IPRemoteAddrIP
* IPIPIPIP
*
* @return String IPnull
*/
public static String getIpAddr() {
// 通过HttpContextUtils工具类获取当前线程绑定的HttpServletRequest对象该对象包含了请求相关的各种信息
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
// 如果获取到的HttpServletRequest对象为null说明无法获取到请求上下文相关信息直接返回null无法获取IP地址
if (request == null) {
return null;
}
// 首先尝试从"x-forwarded-for"请求头获取IP地址该请求头在经过代理服务器转发时可能包含客户端真实IP
String ip = request.getHeader("x-forwarded-for");
// 如果获取到的IP地址为空、长度为0或者等于表示未知的字符串不区分大小写比较则继续尝试从其他请求头获取
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
// 如果从"Proxy-Client-IP"请求头获取到的IP地址仍不符合要求为空、长度为0或者是未知字符串则再尝试从"WL-Proxy-Client-IP"请求头获取
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
// 如果经过前面的尝试还是没有获取到有效IP地址则直接获取请求的远程地址RemoteAddr作为IP地址这是最基本的获取客户端IP的方式
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 将获取到的IP地址字符串按照逗号进行分割因为可能存在经过多层代理IP地址有多个的情况以逗号分隔
String[] ips = ip.split(",");
// 返回分割后的第一个IP地址去除两端的空白字符作为客户端的真实IP地址如果只有一个IP则就是该IP本身
return ips[0].trim();
}
}

@ -0,0 +1,148 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.common.util;
// 导入Jackson相关的注解和类用于配置JSON序列化和反序列化过程中的一些行为比如包含哪些属性、如何处理未知属性等
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.json.JsonWriteFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
// 导入Lombok的日志记录相关注解用于简化日志记录代码自动生成名为log的日志记录对象
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* JsonJackson便JSON
* JavaJSONJSONJavaJSON
* JacksonObjectMapperJSON
*
* @author lanhai
*/
@Slf4j
public class Json {
// 创建一个静态的ObjectMapper实例ObjectMapper是Jackson库中用于进行JSON序列化和反序列化的核心类后续所有的JSON操作都基于它来实现
private static ObjectMapper objectMapper = new ObjectMapper();
// 静态代码块用于对ObjectMapper实例进行一系列的配置这些配置会影响JSON序列化和反序列化的行为。
static {
// 设置序列化时的包含规则这里配置为JsonInclude.Include.NON_EMPTY表示如果属性值为空比如null、空字符串、空集合等则不输出该属性到JSON字符串中减少不必要的JSON数据量。
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
// 配置在序列化时对于空的Java对象没有任何属性值的对象转JSON的时候不抛出错误而是正常返回一个空的JSON对象如 {}),增强程序的健壮性,避免因空对象序列化失败导致整个操作中断。
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// 禁用将日期类型属性序列化为时间戳的功能,这样在处理日期类型数据时可以按照更符合业务需求的日期格式(比如特定的字符串格式)进行序列化,而不是默认的时间戳形式。
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 禁用在反序列化时遇到未知属性即JSON字符串中的属性在对应的Java类中不存在定义抛出异常的功能这样即使JSON数据有额外的属性也能尽量解析出已知的属性值避免因未知属性导致整个反序列化失败。
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// 取消对非ASCII字符的转码操作使得JSON字符串中可以直接包含如中文等非ASCII字符而不需要进行转义编码方便查看和处理更符合实际业务场景中对中文等字符的使用需求。
objectMapper.configure(JsonWriteFeature.ESCAPE_NON_ASCII.mappedFeature(), false);
}
/**
* JavaJSON
* ObjectMapperwriteValueAsStringJSON
* null
*
* @param object JSONJavaJacksonPOJO
* @return String JSONnull
*/
public static String toJsonString(Object object) {
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
// 记录对象转JSON时出现的错误日志方便后续排查问题这里记录了异常的详细信息通过传入e作为参数
log.error("对象转json错误", e);
}
return null;
}
/**
* JSONJava
* ObjectMapperreadValueclazzJSONJava
* JSONnull
*
*
* @param json JSONJavaJackson
* @param clazz JavaObjectMapperJSON
* @return <T> Javaclazznull
*/
public static <T> T parseObject(String json, Class<T> clazz) {
T result = null;
try {
result = objectMapper.readValue(json, clazz);
} catch (Exception e) {
// 记录JSON转对象时出现的错误日志方便后续排查问题这里记录了异常的详细信息通过传入e作为参数
log.error("对象转json错误", e);
}
return result;
}
/**
* ObjectMapperJSON
* JSON使JSON
*
* @return ObjectMapper ObjectMapperJSON
*/
public static ObjectMapper getObjectMapper() {
return objectMapper;
}
/**
* JSONJavaList
* ObjectMapperreadValueJSONclazz
* ListnullList
* 便
* 使TypeReference10
*
* @param json JSONJavaJSON
* @param clazz JavaObjectMapperJSONMyClass[].class
* @return <T> JavaclazzList
*/
public static <T> List<T> parseArray(String json, Class<T[]> clazz) {
T[] result = null;
try {
result = objectMapper.readValue(json, clazz);
} catch (Exception e) {
// 记录JSON转换时出现的错误日志方便后续排查问题这里记录了异常的详细信息通过传入e作为参数
log.error("Json转换错误", e);
}
if (result == null) {
return Collections.emptyList();
}
return Arrays.asList(result);
}
/**
* JSONJsonNodeJsonNodeJSON便JSON
* JSONJSONMap
* null
*
* @param jsonStr JsonNodeJSONJSON
* @return JsonNode JsonNodenullJSON
*/
public static JsonNode parseJson(String jsonStr) {
JsonNode jsonNode = null;
try {
jsonNode = objectMapper.readTree(jsonStr);
} catch (Exception e) {
// 记录JSON转换时出现的错误日志方便后续排查问题这里记录了异常的详细信息通过传入e作为参数
log.error("Json转换错误", e);
}
return jsonNode;
}
}

@ -0,0 +1,51 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.common.util;
// 导入Hutool工具库中用于分页相关操作的工具类这里主要用于将页码、每页数量等信息转换为数据库查询中起始位置和结束位置的相关操作
import cn.hutool.core.util.PageUtil;
// 导入MyBatis Plus框架中用于表示分页信息的核心类包含了当前页码、每页显示数量等分页相关的属性和方法
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
// 引入Lombok的Data注解通过该注解编译器会自动帮我们生成类的常用方法比如各属性的Getter、Setter方法以及toString、equals、hashCode方法等简化代码编写
import lombok.Data;
/**
* PageAdapterMyBatis PlusPage
*
* beginsize便使
* Hutool
*
* @author lh
*/
@Data
public class PageAdapter {
// 用于表示分页查询时的起始位置(通常对应数据库查询中的偏移量,从第几条记录开始查询)
private int begin;
// 用于表示分页查询时每页的记录数量,即每页显示多少条数据
private int size;
/**
* MyBatis PlusPage
* HutoolPageUtiltransToStartEnd
* Pagebeginsize
*
* @param page MyBatis PlusPagebeginsize
*/
public PageAdapter(Page page) {
// 调用Hutool的PageUtil工具类的transToStartEnd方法将当前页码需要减1因为数据库查询中页码通常从0开始计数和每页数量转换为起始位置和结束位置的数组这里取数组的第一个元素作为起始位置begin
int[] startEnd = PageUtil.transToStartEnd((int) page.getCurrent() - 1, (int) page.getSize());
this.begin = startEnd[0];
// 将传入的Page对象中的每页数量赋值给size属性作为每页的记录数量
this.size = (int) page.getSize();
}
}

@ -0,0 +1,213 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.common.util;
// 导入MyBatis Plus框架中用于表示分页信息的核心类包含了分页相关的属性如当前页、每页大小等以及操作方法如获取记录列表、设置总数等
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
// 导入Jackson相关的注解用于在JSON序列化和反序列化过程中忽略指定的属性这里用于标记某些属性在转换为JSON时不进行处理
import com.fasterxml.jackson.annotation.JsonIgnore;
// 导入Swagger相关的注解用于在生成API文档时隐藏某些属性使得这些属性不在文档中展示出来一般用于内部使用或不需要对外暴露的属性
import io.swagger.v3.oas.annotations.Hidden;
// 导入Swagger相关的注解用于在生成API文档时为类、属性等添加描述信息方便前端开发人员等查看其含义和作用
import io.swagger.v3.oas.annotations.media.Schema;
// 导入SpringDoc相关的注解用于标记该类作为参数对象在生成API文档以及进行参数校验等场景下可以被识别和处理
import org.springdoc.core.annotations.ParameterObject;
import java.util.List;
/**
* PageParamMyBatis PlusPage
* PageJSON
* Page
* count
*
* @author lanhai
*/
@Schema
@ParameterObject
public class PageParam<T> extends Page<T> {
/**
* 10
* @SchemaAPI便使
*/
@Schema(description = "每页大小默认10")
private long size = 10;
/**
* 1@Schema便API
*/
@Schema(description = "当前页默认1")
private long current = 1;
/**
* @HiddenAPI使
*/
@Hidden
private List<T> records;
/**
* @HiddenAPI
*/
@Hidden
private long total = 0;
/**
* counttruecount
* @JsonIgnoreJSON
*/
@JsonIgnore
private boolean isSearchCount = true;
/**
* @JsonIgnoreJSON使
*/
@JsonIgnore
private String countId;
/**
* 100@JsonIgnoreJSON
*/
@JsonIgnore
private Long maxLimit;
/**
* countSQL@JsonIgnoreJSON使
*/
@JsonIgnore
private boolean optimizeCountSql;
/**
* Pagerecords
*
* @return List<T> T
*/
@Override
public List<T> getRecords() {
return this.records;
}
/**
* Pagerecordsthis便
* 便
*
* @param records T
* @return Page<T>
*/
@Override
public Page<T> setRecords(List<T> records) {
this.records = records;
return this;
}
/**
* Pagetotal使
*
* @return long
*/
@Override
public long getTotal() {
return this.total;
}
/**
* Pagetotalthis便
* count
*
* @param total
* @return Page<T>
*/
@Override
public Page<T> setTotal(long total) {
this.total = total;
return this;
}
/**
* countisSearchCount
* total0falsecount
* isSearchCountcount
*
* @return boolean counttruecountfalse
*/
@JsonIgnore
public boolean getSearchCount() {
if (total < 0) {
return false;
}
return isSearchCount;
}
/**
* PagecountisSearchCountthis便
* count
*
* @param isSearchCount counttruefalse
* @return Page<T>
*/
@Override
public Page<T> setSearchCount(boolean isSearchCount) {
this.isSearchCount = isSearchCount;
return this;
}
/**
* Pagesize使
*
* @return long
*/
@Override
public long getSize() {
return this.size;
}
/**
* Page
* size100size100
* sizesizethis便
*
* @param size
* @return Page<T>
*/
@Override
public Page<T> setSize(long size) {
int maxSize = 100;
if (size > maxSize) {
this.size = maxSize;
} else {
this.size = size;
}
return this;
}
/**
* Pagecurrent使
*
* @return long
*/
@Override
public long getCurrent() {
return this.current;
}
/**
* Pagecurrentthis便
*
*
* @param current
* @return Page<T>
*/
@Override
public Page<T> setCurrent(long current) {
this.current = current;
return this;
}
}

@ -0,0 +1,27 @@
package com.yami.shop.security.admin.dto;
import com.yami.shop.security.common.dto.AuthenticationDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* CaptchaAuthenticationDTODTO
* AuthenticationDTO
*
*
*
* @author
* @date 2022/3/28 14:57
*/
@Data
public class CaptchaAuthenticationDTO extends AuthenticationDTO {
/**
* captchaVerification
* @SchemaSwagger
* required = true
* 便
*/
@Schema(description = "验证码", required = true)
private String captchaVerification;
}

@ -0,0 +1,136 @@
package com.yami.shop.security.api.controller;
// 导入MyBatis Plus框架中用于构建条件查询的核心类方便编写数据库查询条件此处用于查询用户信息
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
// 导入项目中自定义的用户实体类,代表数据库中存储的用户相关信息,包含各种用户属性,如用户名、手机号、密码等
import com.yami.shop.bean.model.User;
// 导入项目自定义的业务异常类,用于在业务逻辑出现特定错误情况时抛出异常,方便统一处理和向客户端返回错误信息
import com.yami.shop.common.exception.YamiShopBindException;
// 导入项目自定义的工具类,可能包含一些通用的业务逻辑处理方法,此处具体功能需看其内部实现(从名字推测和主体逻辑相关)
import com.yami.shop.common.util.PrincipalUtil;
// 导入项目中定义的用于操作数据库中用户表的Mapper接口通过它可以调用数据库相关的查询、插入等操作方法由MyBatis框架生成实现类来具体执行SQL操作
import com.yami.shop.dao.UserMapper;
// 导入与安全相关的业务对象类用于封装在Token中存储的用户信息方便在不同模块间传递和使用用户相关数据
import com.yami.shop.security.common.bo.UserInfoInTokenBO;
// 导入与安全相关的用于接收登录认证信息的数据传输对象DTO包含用户名、密码以及用户所属系统类型等信息用于接收前端传入的登录数据
import com.yami.shop.security.common.dto.AuthenticationDTO;
// 导入与安全相关的枚举类用于区分不同的系统类型此处有个普通用户类型ORDINARY可能还有其他类型用于不同业务场景下的用户分类
import com.yami.shop.security.common.enums.SysTypeEnum;
// 导入与安全相关的用于密码检查的管理类,可能包含验证密码是否符合规则、检查密码错误次数等逻辑,保障登录密码的安全性
import com.yami.shop.security.common.manager.PasswordCheckManager;
// 导入与安全相关的用于密码管理的类,可能包含密码加密、解密等相关操作方法,确保密码在存储和传输过程中的安全性
import com.yami.shop.security.common.manager.PasswordManager;
// 导入与安全相关的用于管理Token存储和操作的类负责生成、存储Token以及基于用户信息获取相应的Token相关信息等功能
import com.yami.shop.security.common.manager.TokenStore;
// 导入与安全相关的用于返回给前端包含Token信息的视图对象VO将后端处理后的Token相关数据以合适的格式返回给前端展示和后续使用
import com.yami.shop.security.common.vo.TokenInfoVO;
// 导入Swagger相关的注解用于生成API文档@Tag注解用于给接口分类添加标签方便文档中进行分组展示和说明
import io.swagger.v3.oas.annotations.tags.Tag;
// 导入Swagger相关的注解用于在API文档中描述接口的功能摘要和详细信息方便前端开发人员等查看接口的作用和使用方式
import io.swagger.v3.oas.annotations.Operation;
// 导入Spring框架用于实现依赖注入的注解表明后续的属性需要由Spring容器进行注入实例化
import org.springframework.beans.factory.annotation.Autowired;
// 导入项目自定义的用于封装服务器响应信息的实体类,包含响应状态码、消息、数据等内容,用于统一向客户端返回响应结果
import com.yami.shop.common.response.ServerResponseEntity;
// 导入Spring框架用于定义处理POST请求的注解将下面的方法映射为一个接收POST请求的接口处理对应的业务逻辑
import org.springframework.web.bind.annotation.PostMapping;
// 导入Spring框架用于接收请求体数据并将其绑定到方法参数的注解确保前端传入的JSON等格式的数据能正确映射到对应的参数对象上
import org.springframework.web.bind.annotation.RequestBody;
// 导入Spring框架用于将类标记为RESTful风格的控制器的注解表明该类中的方法主要用于处理HTTP请求并返回JSON等格式的响应数据
import org.springframework.web.bind.annotation.RestController;
// 导入Jakarta验证相关的注解用于对传入的参数对象进行数据校验确保参数符合一定的规则比如非空等要求
import jakarta.validation.Valid;
/**
* LoginControllerSpring RESTful
*
* Token
* 便使SwaggerAPI便使
*
* @author
* @date 2022/3/28 15:20
*/
@RestController
@Tag(name = "登录")
public class LoginController {
// 通过Spring的依赖注入机制注入一个TokenStore实例用于管理Token的存储、生成以及获取相关信息等操作
@Autowired
private TokenStore tokenStore;
// 注入一个UserMapper实例用于调用数据库中用户表相关的查询、操作方法以便获取用户信息进行登录验证等操作
@Autowired
private UserMapper userMapper;
// 注入一个PasswordCheckManager实例用于对用户输入的密码进行各种检查如密码错误次数限制等安全相关的验证操作
@Autowired
private PasswordCheckManager passwordCheckManager;
// 注入一个PasswordManager实例用于对密码进行加密、解密等管理操作此处主要用于解密前端传入的密码进行后续验证
@Autowired
private PasswordManager passwordManager;
/**
* @Valid
*
* Token
* TokenStoreTokenTokenTokenInfoVOToken
*
* @param authenticationDTO @RequestBody
* @return ServerResponseEntity<TokenInfoVO> TokenTokenInfoVOToken
*/
@PostMapping("/login")
@Operation(summary = "账号密码(用于前端登录)", description = "通过账号/手机号/用户名密码登录,还要携带用户的类型,也就是用户所在的系统")
public ServerResponseEntity<TokenInfoVO> login(
@Valid @RequestBody AuthenticationDTO authenticationDTO) {
// 从登录认证信息对象中获取用户名(可能是手机号或者普通用户名形式),用于后续查找用户信息
String mobileOrUserName = authenticationDTO.getUserName();
// 根据获取到的用户名查找对应的用户信息,若找不到则会抛出异常告知账号或密码不正确
User user = getUser(mobileOrUserName);
// 通过PasswordManager对前端传入的密码进行解密操作获取解密后的密码以便后续和数据库中存储的密码进行比对验证
String decryptPassword = passwordManager.decryptPassword(authenticationDTO.getPassWord());
// 通过PasswordCheckManager检查密码是否正确以及是否达到密码错误次数限制半小时内密码输入错误十次已限制登录30分钟若不符合要求会抛出相应异常进行处理
passwordCheckManager.checkPassword(SysTypeEnum.ORDINARY, authenticationDTO.getUserName(), decryptPassword, user.getLoginPassword());
// 创建一个用于在Token中存储用户信息的业务对象将用户的ID、所属系统类型以及是否启用等信息设置进去用于后续生成Token以及存储相关用户标识信息
UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO();
userInfoInToken.setUserId(user.getUserId());
userInfoInToken.setSysType(SysTypeEnum.ORDINARY.value());
userInfoInToken.setEnabled(user.getStatus() == 1);
// 通过TokenStore存储用户信息并生成Token同时获取包含Token相关信息的视图对象TokenInfoVO用于返回给前端告知登录成功以及提供后续鉴权等所需的Token信息
TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
return ServerResponseEntity.success(tokenInfoVO);
}
/**
*
*
*
* YamiShopBindException
*
* @param mobileOrUserName
* @return User null
*/
private User getUser(String mobileOrUserName) {
User user = null;
// 通过PrincipalUtil工具类判断传入的用户名是否符合手机号格式如果是则进行下一步操作通过手机号查找用户
if (PrincipalUtil.isMobile(mobileOrUserName)) {
// 使用MyBatis Plus构建的条件查询对象LambdaQueryWrapper根据手机号在数据库中查找对应的用户信息通过UserMapper调用数据库查询方法
user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserMobile, mobileOrUserName));
}
// 如果通过手机号没找到用户即user为null则尝试通过用户名在数据库中查找用户
if (user == null) {
user = userMapper.selectOneByUserName(mobileOrUserName);
}
// 如果最终还是没找到对应的用户,则抛出自定义的业务异常,告知账号或密码不正确
if (user == null) {
throw new YamiShopBindException("账号或密码不正确");
}
return user;
}
}

@ -0,0 +1,37 @@
package com.yami.shop.security.common.adapter;
import java.util.List;
/**
* 便
* 使
*
* @author
* @date 2022/3/25 17:31
*/
public interface AuthConfigAdapter {
/**
* URL
* "/**/ma/**" "ma" 访
*
*/
String MAYBE_AUTH_URI = "/**/ma/**";
/**
*
* 访
*
*
* @return
*/
List<String> pathPatterns();
/**
*
* 访
*
* @return
*/
List<String> excludePathPatterns();
}

@ -0,0 +1,79 @@
package com.yami.shop.security.common.adapter;
import com.anji.captcha.service.CaptchaCacheService;
import com.yami.shop.common.util.RedisUtil;
/**
* CaptchaCacheServiceRedisImplRedis
* CaptchaCacheService
* Redis
* 使Redis
*
* @author
* @date 2022/3/25 17:33
*/
public class CaptchaCacheServiceRedisImpl implements CaptchaCacheService {
/**
* Redis
* RedisUtilsetRedis
* keyvalueexpiresInSecondsRedisUtil
*
* @param key Redis
* @param value Redis
* @param expiresInSeconds RedisRedis
*/
@Override
public void set(String key, String value, long expiresInSeconds) {
RedisUtil.set(key, value, expiresInSeconds);
}
/**
* Redis
* RedisUtilhasKey
* Redistruefalse
*
* @param key Redis
* @return Redistruefalse
*/
@Override
public boolean exists(String key) {
return RedisUtil.hasKey(key);
}
/**
* Redis
* RedisUtildelRedis
* 使Redis
*
* @param key RedisRedis
*/
@Override
public void delete(String key) {
RedisUtil.del(key);
}
/**
* Redis
* RedisUtilgetRedis
* RedisUtil
*
* @param key RedisRedis
* @return RedisRedisUtil
*/
@Override
public String get(String key) {
return RedisUtil.get(key);
}
/**
* 使redis
* Redis便
*
* @return redisRedis
*/
@Override
public String type() {
return "redis";
}
}

@ -0,0 +1,53 @@
package com.yami.shop.security.common.adapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.List;
/**
* DefaultAuthConfigAdapterAuthConfigAdapter
* pathPatternsexcludePathPatterns
*
*
* @author
* @date 2022/3/25 17:33
*/
public class DefaultAuthConfigAdapter implements AuthConfigAdapter {
// 创建一个日志记录器用于记录与该类相关的日志信息方便在运行时排查问题、了解类的执行情况等这里记录器的名称为DefaultAuthConfigAdapter类的全限定名
private static final Logger logger = LoggerFactory.getLogger(DefaultAuthConfigAdapter.class);
/**
* DefaultAuthConfigAdapter
* AuthConfigAdapter使DefaultAuthConfigAdapter
* URL
*/
public DefaultAuthConfigAdapter() {
logger.info("not implement other AuthConfigAdapter, use DefaultAuthConfigAdapter... all url need auth...");
}
/**
* pathPatternsAuthConfigAdapter
* /*
*
*
* @return List<String>/*
*/
@Override
public List<String> pathPatterns() {
return Collections.singletonList("/*");
}
/**
* excludePathPatternsAuthConfigAdapter
* pathPatterns
*
*
* @return List<String>
*/
@Override
public List<String> excludePathPatterns() {
return Collections.emptyList();
}
}

@ -0,0 +1,59 @@
package com.yami.shop.security.common.adapter;
// 导入Spring框架中用于将方法标记为创建Bean的注解通过该注解定义的方法返回的对象会被Spring容器管理可在其他地方进行注入使用
import org.springframework.context.annotation.Bean;
// 导入Spring Security框架中用于配置基于Web的安全相关设置的类通过链式调用的方式可以配置如认证、授权、跨域、会话管理等多个方面的安全策略
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
// 导入Spring Security框架中用于启用Web安全功能的注解表明在当前应用中要开启Spring Security相关的Web安全配置机制
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
// 导入Spring Security框架中用于定义会话创建策略的枚举类此处配置为无状态STATELESS会话策略适用于基于Token的认证方式
import org.springframework.security.config.http.SessionCreationPolicy;
// 导入Spring Security框架中用于构建安全过滤链的核心接口配置好的安全规则最终会形成这样一个过滤链来处理进入应用的请求进行安全相关的检查和过滤操作
import org.springframework.security.web.SecurityFilterChain;
// 导入Spring框架中用于将类标记为组件的注解表明该类是一个Spring管理的组件会被自动扫描并实例化可在其他地方进行依赖注入使用
import org.springframework.stereotype.Component;
// 导入Spring框架中用于处理跨域相关的工具类此处用于判断请求是否是跨域预检请求OPTIONS请求以便对不同类型请求做不同的授权处理
import org.springframework.web.cors.CorsUtils;
/**
* MallWebSecurityConfigurerAdapterSpring SecurityWeb
* Spring Security使Spring Security
* Token
* SecurityFilterChain使
*
* @author
* @date 2022/3/25 17:33
*/
@Component
@EnableWebSecurity
public class MallWebSecurityConfigurerAdapter {
/**
* filterChain@BeanSecurityFilterChainSpring使Bean
*
*
* @param http HttpSecurityHTTP
* @return SecurityFilterChain Web
* @throws Exception HttpSecurity
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// 关闭跨站请求伪造CSRF防护功能因为基于Token的认证方式下通常不需要CSRF防护Token本身就用于身份验证可避免此类安全风险
return http.csrf().disable().cors()
// 配置跨域相关设置,启用跨域支持(后续可能还需要配置更具体的跨域规则等,这里只是开启了基本的支持),
// 接着配置会话管理相关内容将会话创建策略设置为无状态STATELESS意味着服务器不会为客户端创建和维护会话状态
// 适用于基于Token的认证场景每次请求都通过携带Token来验证身份服务器不依赖会话来识别客户端
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 配置请求授权相关规则对于跨域预检请求OPTIONS请求通过CorsUtils::isPreFlightRequest判断允许所有的此类请求通过
// 因为跨域预检请求主要是浏览器在正式发起跨域请求前进行的一种询问服务器是否允许跨域的请求,通常需要放行
.and().authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
// 继续配置请求授权规则,对于所有其他请求(通过"/**"表示任意路径的请求也都允许通过这意味着在这个配置下没有进行严格的基于Spring Security自带认证授权的访问限制
// 可能后续会通过其他自定义的中间件或者业务逻辑来对请求进行更细致的权限判断等操作,此处只是简单放开了所有请求的访问权限
.and().authorizeRequests().requestMatchers(
"/**").permitAll().and().build();
}
}

@ -0,0 +1,84 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.security.common.config;
import cn.hutool.core.util.ArrayUtil;
// 导入自定义的授权配置适配器类,用于适配不同的授权配置相关逻辑,可根据具体业务需求进行扩展或定制
import com.yami.shop.security.common.adapter.AuthConfigAdapter;
// 导入默认的授权配置适配器实现类,当没有自定义的授权配置适配器时,会使用这个默认的实现类来处理相关逻辑
import com.yami.shop.security.common.adapter.DefaultAuthConfigAdapter;
// 导入自定义的授权过滤器类,用于对请求进行授权相关的过滤操作,比如验证用户权限等
import com.yami.shop.security.common.filter.AuthFilter;
import jakarta.servlet.DispatcherType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
/**
* Spring
*
*
* @author
* @date 2022/3/25 17:33
*/
@Configuration
// 启用方法级别的安全配置,允许在方法上使用诸如 @PreAuthorize、@PostAuthorize 等注解来进行权限控制
@EnableMethodSecurity
public class AuthConfig {
// 通过自动注入获取授权过滤器实例该过滤器将在后续的配置中被注册到Servlet容器中用于对请求进行过滤处理
@Autowired
private AuthFilter authFilter;
/**
* Bean
* SpringAuthConfigAdapterBean
* DefaultAuthConfigAdapter
* 便
*
* @return AuthConfigAdapter
*/
@Bean
@ConditionalOnMissingBean
public AuthConfigAdapter authConfigAdapter() {
return new DefaultAuthConfigAdapter();
}
/**
* FilterRegistrationBean
* FilterRegistrationBeanAuthFilterServlet
*
* 使 @Lazy Bean使
*
* @param authConfigAdapter
* @return FilterRegistrationBean<AuthFilter>Servlet
*/
@Bean
@Lazy
public FilterRegistrationBean<AuthFilter> filterRegistration(AuthConfigAdapter authConfigAdapter) {
FilterRegistrationBean<AuthFilter> registration = new FilterRegistrationBean<>();
// 将之前注入的授权过滤器添加到FilterRegistrationBean中这样Servlet容器就能识别并使用该过滤器来处理请求了
registration.setFilter(authFilter);
// 设置过滤路径,通过授权配置适配器获取需要过滤的路径列表,并转换为字符串数组格式。
// 这里使用ArrayUtil工具类将获取到的路径列表转换为String数组以便设置给FilterRegistrationBean
// 表示该过滤器将会对这些指定的路径下的请求进行过滤处理,此处的 /* 表示所有路径,具体路径配置通常由授权配置适配器决定
registration.addUrlPatterns(ArrayUtil.toArray(authConfigAdapter.pathPatterns(), String.class));
registration.setName("authFilter");
// 设置过滤器的优先级数值越小优先级越高这里设置为0表示较高的优先级在多个过滤器存在的情况下会先执行该过滤器
registration.setOrder(0);
registration.setDispatcherTypes(DispatcherType.REQUEST);
return registration;
}
}

@ -0,0 +1,116 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.security.common.config;
import com.anji.captcha.model.common.CaptchaTypeEnum;
import com.anji.captcha.model.common.Const;
import com.anji.captcha.service.CaptchaService;
import com.anji.captcha.service.impl.CaptchaServiceFactory;
import com.anji.captcha.util.ImageUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Base64Utils;
import org.springframework.util.FileCopyUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* CaptchaConfigSpring@Configuration
* Redis
* Redis
* Redis
*
* @author
* @date 2022/3/25 17:33
*/
@Configuration
public class CaptchaConfig {
/**
* captchaServiceSpringBean@Bean
* CaptchaService
* PropertiesCaptchaService
* 使CaptchaServiceFactoryCaptchaService
*
* @return CaptchaServiceSpring使
*/
@Bean
public CaptchaService captchaService() {
Properties config = new Properties();
// 设置验证码的缓存类型为Redis意味着验证码相关的数据如底图等将存储在Redis中
config.put(Const.CAPTCHA_CACHETYPE, "redis");
// 设置验证码的水印为空字符串,即不添加水印(具体根据验证码实现逻辑来定是否生效)
config.put(Const.CAPTCHA_WATER_MARK, "");
// 设置验证码的类型为滑动验证这里使用了CaptchaTypeEnum枚举中BLOCKPUZZLE对应的代码值来指定
config.put(Const.CAPTCHA_TYPE, CaptchaTypeEnum.BLOCKPUZZLE.getCodeValue());
// 设置初始化原始图片相关的标志为true具体含义取决于验证码服务内部对该参数的使用逻辑
config.put(Const.CAPTCHA_INIT_ORIGINAL, "true");
// 调用initializeBaseMap方法进行一些初始化操作可能与验证码底图相关比如预加载等
initializeBaseMap();
return CaptchaServiceFactory.getInstance(config);
}
/**
* initializeBaseMap
* ImageUtilscacheBootImage
* Map
* ImageUtils
* 便便使
*/
private static void initializeBaseMap() {
ImageUtils.cacheBootImage(getResourcesImagesFile("classpath:captcha" + "/original/*.png"),
getResourcesImagesFile("classpath:captcha" + "/slidingBlock/*.png"),
Collections.emptyMap());
}
/**
* getResourcesImagesFileBase64Map
* MapBase64便使
*
*
* @param path "classpath:captcha" + "/original/*.png"png
* @return MapBase64
*/
public static Map<String, String> getResourcesImagesFile(String path) {
Map<String, String> imgMap = new HashMap<>(16);
// 创建一个PathMatchingResourcePatternResolver实例用于解析类路径下的资源模式能够匹配符合指定模式的多个资源文件
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
// 根据传入的路径表达式获取对应的资源数组,即匹配该模式的所有图片资源文件
Resource[] resources = resolver.getResources(path);
// 遍历获取到的所有资源文件
Resource[] var4 = resources;
int var5 = resources.length;
for (int var6 = 0; var6 < var5; ++var6) {
Resource resource = var4[var6];
// 将资源文件的内容读取为字节数组以便后续进行Base64编码操作
byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream());
// 将字节数组转换为Base64编码的字符串使得图片数据可以以文本形式方便地在内存中存储和传输等
String string = Base64Utils.encodeToString(bytes);
// 获取资源文件的文件名用于作为Map中的键来标识对应的Base64编码后的图片数据
String filename = resource.getFilename();
imgMap.put(filename, string);
}
} catch (Exception var11) {
// 如果在获取资源文件或处理过程中出现异常,打印异常堆栈信息,方便排查问题
var11.printStackTrace();
}
return imgMap;
}
}

@ -0,0 +1,61 @@
package com.yami.shop.security.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
/**
* CorsConfigSpring@Configuration
* CORS便
* 使访
*
* @author yami
*/
@Configuration
public class CorsConfig {
/**
* corsConfigurationSourceSpringBean@Bean
* CorsConfigurationSourceSpring WebCORS
* origins访HTTP
*
*
* - *访
* 便访
* - addAllowedOrigin访
* configuration.addAllowedOrigin("http://localhost:8080");
* configuration.addAllowedOrigin("http://192.168.1.6:8080");
*
* @return CorsConfigurationSourceSpring Web使
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
// 创建一个CorsConfiguration对象用于配置跨域相关的各种规则
CorsConfiguration configuration = new CorsConfiguration();
// 使用addAllowedOriginPattern方法添加允许跨域访问的来源模式这里使用通配符*)表示允许所有来源访问,
// 但需注意在生产环境应按实际情况修改为具体域名,可参考上面的注释说明。
configuration.addAllowedOriginPattern("*");
// 修改为添加add而不是设置set允许的HTTP方法使用通配符*表示允许所有的HTTP方法如GET、POST、PUT等
// 这样前端可以使用各种类型的HTTP请求来访问后端接口。
configuration.addAllowedMethod("*");
// 这里配置允许的请求头,同样使用通配符(*)表示允许所有的请求头信息,
// 特别重要的是起码需要允许 Access-Control-Allow-Origin这个请求头它与跨域访问的合法性验证密切相关。
configuration.addAllowedHeader("*");
// 设置是否允许发送Cookie等凭证信息设置为true表示允许
// 若前后端交互需要携带用户认证等相关凭证如Cookie中的登录信息则需要开启此项。
configuration.setAllowCredentials(true);
// 设置预检请求OPTIONS请求的缓存时间单位为秒这里设置为一天3600 * 24秒
// 在缓存有效期内,对于相同来源、方法和请求头的请求,浏览器不会再次发送预检请求,提高性能。
configuration.setMaxAge(3600 * 24L);
// 创建一个基于URL的CorsConfigurationSource对象它可以根据不同的URL路径应用不同的CorsConfiguration配置。
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 将上面配置好的CorsConfiguration应用到所有路径/**表示匹配所有的请求路径),
// 意味着所有的接口请求都会按照这个配置来处理跨域相关的规则。
source.registerCorsConfiguration("/**", configuration);
return source;
}
}

@ -0,0 +1,79 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.security.common.controller;
import com.anji.captcha.model.common.RepCodeEnum;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import io.swagger.v3.oas.annotations.tags.Tag;
import com.yami.shop.common.response.ServerResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* CaptchaControllerSpring RESTful@RestController
* HTTP
* "/captcha"@RequestMappingSwagger"验证码"@Tag便
*
* @author
* @date 2022/3/25 17:33
*/
@RestController
@RequestMapping("/captcha")
@Tag(name = "验证码")
public class CaptchaController {
// 通过构造函数注入的方式引入CaptchaService用于后续调用验证码相关的业务逻辑方法比如生成验证码、验证验证码有效性等操作
private final CaptchaService captchaService;
public CaptchaController(CaptchaService captchaService) {
this.captchaService = captchaService;
}
/**
* getHTTP POST
* CaptchaVO@RequestBodyCaptchaVO
* captchaServicegetServerResponseEntity
*
*
* @param captchaVO
* @return ServerResponseEntityResponseModel
*/
@PostMapping({ "/get" })
public ServerResponseEntity<ResponseModel> get(@RequestBody CaptchaVO captchaVO) {
return ServerResponseEntity.success(captchaService.get(captchaVO));
}
/**
* checkHTTP POST
* CaptchaVOCaptchaVO
* captchaServicecheckServerResponseEntity
*
* ResponseModel.errorMsgResponseModel
* ServerResponseEntity
*
* @param captchaVO
* @return ServerResponseEntityResponseModel
*/
@PostMapping({ "/check" })
public ServerResponseEntity<ResponseModel> check(@RequestBody CaptchaVO captchaVO) {
ResponseModel responseModel;
try {
responseModel = captchaService.check(captchaVO);
} catch (Exception e) {
return ServerResponseEntity.success(ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR));
}
return ServerResponseEntity.success(responseModel);
}
}

@ -0,0 +1,71 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.security.common.controller;
// 导入Hutool工具库中用于字符串操作的工具类此处用于判断字符串是否为空空白字符串方便进行逻辑判断
import cn.hutool.core.util.StrUtil;
// 导入项目自定义的用于封装服务器响应信息的实体类包含响应状态码、消息、数据等内容用于统一向客户端返回响应结果此处响应数据为Void表示无具体数据返回
import com.yami.shop.common.response.ServerResponseEntity;
// 导入与安全相关的用于管理Token存储和操作的类负责对Token进行删除等相关管理操作此处用于删除用户的登录Token
import com.yami.shop.security.common.manager.TokenStore;
// 导入Swagger相关的注解用于生成API文档@Operation注解用于描述接口的功能摘要和详细信息方便前端开发人员等查看接口的作用和使用方式
import io.swagger.v3.oas.annotations.Operation;
// 导入Swagger相关的注解用于给接口分类添加标签方便文档中进行分组展示和说明此处标记为"注销"类别
import io.swagger.v3.oas.annotations.tags.Tag;
// 导入Spring框架用于实现依赖注入的注解表明后续的属性需要由Spring容器进行注入实例化此处用于注入TokenStore实例
import org.springframework.beans.factory.annotation.Autowired;
// 导入Spring框架用于定义处理POST请求的注解将下面的方法映射为一个接收POST请求的接口处理对应的业务逻辑即处理用户退出登录的请求
import org.springframework.web.bind.annotation.PostMapping;
// 导入Spring框架用于将类标记为RESTful风格的控制器的注解表明该类中的方法主要用于处理HTTP请求并返回JSON等格式的响应数据
import org.springframework.web.bind.annotation.RestController;
// 导入Servlet相关的用于操作HTTP请求的类用于获取请求头中的信息此处主要用于获取包含Token的"Authorization"请求头内容
import jakarta.servlet.http.HttpServletRequest;
/**
* LogoutControllerSpring RESTful退
* 退TokenTokenTokenStoreToken
* 使SwaggerAPI便使
*
* @author
* @date 2022/3/25 17:33
*/
@RestController
@Tag(name = "注销")
public class LogoutController {
// 通过Spring的依赖注入机制注入一个TokenStore实例用于管理Token的删除等相关操作以实现清除用户登录Token的功能
@Autowired
private TokenStore tokenStore;
/**
* 退HTTPHttpServletRequest
* "Authorization"TokenToken
* 退
* TokenTokenStoreToken退
*
* @param request 退HttpServletRequestToken
* @return ServerResponseEntity<Void> Void退
*/
@PostMapping("/logOut")
@Operation(summary = "退出登陆", description = "点击退出登陆清除token清除菜单缓存")
public ServerResponseEntity<Void> logOut(HttpServletRequest request) {
// 从请求头中获取名为"Authorization"的Token信息通常在登录成功后客户端会在后续请求的这个请求头中携带Token用于身份验证等操作
String accessToken = request.getHeader("Authorization");
// 通过Hutool工具类判断获取到的Token信息是否为空空白字符串如果为空则直接返回成功的服务器响应实体表示可视为已退出登录可能本身就没登录
if (StrUtil.isBlank(accessToken)) {
return ServerResponseEntity.success();
}
// 如果获取到了有效的Token信息则调用注入的TokenStore实例的方法删除该用户在当前系统对应的Token实现清除登录状态的功能
tokenStore.deleteCurrentToken(accessToken);
return ServerResponseEntity.success();
}
}

@ -0,0 +1,47 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.security.common.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
/**
* DTO
* 便
*
* @author
* @date 2022/3/25 17:33
*/
@Data
public class AuthenticationDTO {
/**
*
*
* @NotBlank "userName不能为空"
* 使 @Schema API Swagger API //
*/
@NotBlank(message = "userName不能为空")
@Schema(description = "用户名/邮箱/手机号", required = true)
protected String userName;
/**
*
*
* @NotBlank "passWord不能为空"
* @Schema API
*/
@NotBlank(message = "passWord不能为空")
@Schema(description = "一般用作密码", required = true)
protected String passWord;
}

@ -0,0 +1,163 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.security.common.filter;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.handler.HttpHandler;
import com.yami.shop.common.response.ResponseEnum;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.security.common.adapter.AuthConfigAdapter;
import com.yami.shop.security.common.bo.UserInfoInTokenBO;
import com.yami.shop.security.common.manager.TokenStore;
import com.yami.shop.security.common.util.AuthUserContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
*
* AuthConfigAdapter访
* 访线
*
* @author
* @date 2022/3/25 17:33
*/
@Component
public class AuthFilter implements Filter {
// 用于记录日志,方便在运行过程中输出相关的调试、错误等信息,便于排查问题
private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
// 注入AuthConfigAdapter接口的实现类通过它可以获取到配置的需要授权和不需要授权的路径信息
@Autowired
private AuthConfigAdapter authConfigAdapter;
// 注入HttpHandler用于处理向Web端输出响应相关的操作比如将服务器响应信息正确地返回给前端页面
@Autowired
private HttpHandler httpHandler;
// 注入TokenStore主要用于管理和操作与令牌Token相关的存储及查询等功能例如从存储中获取用户信息等
@Autowired
private TokenStore tokenStore;
// 通过配置文件注入令牌名称,用于后续从请求头中获取对应的令牌信息
@Value("${sa-token.token-name}")
private String tokenName;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
/**
*
*
* @param request ServletHttpServletRequestHTTP
* @param response ServletHttpServletResponse
* @param chain ServletJSP
* @throws IOException
* @throws ServletException Servlet
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 将ServletRequest转换为HttpServletRequest方便获取HTTP请求相关的属性和信息比如请求路径、请求头信息等
HttpServletRequest req = (HttpServletRequest) request;
// 将ServletResponse转换为HttpServletResponse便于后续进行HTTP响应相关的操作比如设置响应状态码、响应头、输出响应内容等
HttpServletResponse resp = (HttpServletResponse) response;
// 获取当前请求的URI统一资源标识符用于后续与配置的授权路径进行匹配判断
String requestUri = req.getRequestURI();
// 通过AuthConfigAdapter获取不需要授权的路径列表用于判断当前请求是否命中这些无需授权的路径
List<String> excludePathPatterns = authConfigAdapter.excludePathPatterns();
// 创建AntPathMatcher对象用于进行路径的匹配操作它支持通配符等方式来灵活匹配路径
AntPathMatcher pathMatcher = new AntPathMatcher();
// 如果不需要授权的路径列表不为空就遍历这些路径检查当前请求的URI是否匹配其中某个无需授权的路径模式
if (CollectionUtil.isNotEmpty(excludePathPatterns)) {
for (String excludePathPattern : excludePathPatterns) {
// 使用AntPathMatcher进行路径匹配如果匹配成功说明当前请求是不需要授权的直接将请求传递给下一个过滤器或目标资源
if (pathMatcher.match(excludePathPattern, requestUri)) {
chain.doFilter(req, resp);
return;
}
}
}
// 从请求头中获取名为tokenName的令牌信息这个令牌通常用于验证用户的登录状态和权限等
String accessToken = req.getHeader(tokenName);
// 判断当前请求的URI是否匹配可能需要登录但不登录也能用的路径模式通过AuthConfigAdapter中定义的常量路径进行匹配
boolean mayAuth = pathMatcher.match(AuthConfigAdapter.MAYBE_AUTH_URI, requestUri);
// 用于存储从令牌中解析出来的用户信息,如果后续成功获取到用户信息则会赋值
UserInfoInTokenBO userInfoInToken = null;
try {
// 如果获取到的令牌信息不为空(即存在令牌),说明用户可能已经登录,需要进一步验证并获取用户信息
if (StrUtil.isNotBlank(accessToken)) {
// 使用StpUtil工具类来校验用户是否已经登录若登录验证失败会抛出异常在这里捕获异常并进行相应处理
try {
StpUtil.checkLogin();
} catch (Exception e) {
// 如果登录校验失败通过HttpHandler将表示未授权的服务器响应信息输出到Web端通常是返回给前端页面显示相应的错误提示
httpHandler.printServerResponseToWeb(ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED));
return;
}
// 如果登录校验通过从TokenStore中根据令牌获取对应的用户信息从缓存等存储中查询并解析用户信息
userInfoInToken = tokenStore.getUserInfoByAccessToken(accessToken, true);
} else if (!mayAuth) {
// 如果没有令牌且当前请求也不属于可能不需要登录就能访问的路径,那么说明该请求需要授权但未提供有效令牌,返回表示未授权的响应给前端
httpHandler.printServerResponseToWeb(ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED));
return;
}
// 将获取到的用户信息保存到AuthUserContext中方便在后续的请求处理过程中比如在其他地方需要获取当前登录用户信息时可以直接获取
AuthUserContext.set(userInfoInToken);
// 如果前面的授权校验等操作都通过了将请求传递给下一个过滤器或者最终的目标资源如业务处理的Servlet等继续处理
chain.doFilter(req, resp);
} catch (Exception e) {
// 手动捕获非controller层抛出的异常进行统一处理
if (e instanceof YamiShopBindException) {
// 如果是YamiShopBindException类型的异常通过HttpHandler将该异常对应的响应信息输出到Web端
httpHandler.printServerResponseToWeb((YamiShopBindException) e);
} else {
// 如果是其他类型的异常,直接抛出,让上层的异常处理机制(比如容器的异常处理)去进一步处理
throw e;
}
} finally {
// 无论请求处理过程是否出现异常最终都要清理AuthUserContext中的用户信息避免数据残留影响下一次请求处理
AuthUserContext.clean();
}
}
@Override
public void destroy() {
Filter.super.destroy();
}
}

@ -0,0 +1,52 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.security.common.manager;
import cn.hutool.crypto.symmetric.AES;
import com.yami.shop.common.exception.YamiShopBindException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
/**
* @author
* @date 2022/1/19 16:02
*/
@Component
public class PasswordManager {
private static final Logger logger = LoggerFactory.getLogger(PasswordManager.class);
/**
* aeskey16
*/
@Value("${auth.password.signKey:-mall4j-password}")
public String passwordSignKey;
public String decryptPassword(String data) {
// 在使用oracle的JDK时JAR包必须签署特殊的证书才能使用。
// 解决方案 1.使用openJDK或者非oracle的JDK建议 2.添加证书
// hutool的aes报错可以打开下面那段代码
// SecureUtil.disableBouncyCastle();
AES aes = new AES(passwordSignKey.getBytes(StandardCharsets.UTF_8));
String decryptStr;
String decryptPassword;
try {
decryptStr = aes.decryptStr(data);
decryptPassword = decryptStr.substring(13);
} catch (Exception e) {
logger.error("Exception:", e);
throw new YamiShopBindException("AES解密错误", e);
}
return decryptPassword;
}
}

@ -0,0 +1,63 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.security.common.util;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.yami.shop.security.common.bo.UserInfoInTokenBO;
/**
* 线
* `TransmittableThreadLocal` 线便
*
*
* @author FrozenWatermelon
* @date 2020/7/16
*/
public class AuthUserContext {
// 使用 `TransmittableThreadLocal` 来创建一个线程本地变量,用于存储 `UserInfoInTokenBO` 类型的用户信息对象。
// `TransmittableThreadLocal` 相较于普通的 `ThreadLocal` 具有能在线程间传递数据的优势,
// 适合在多线程环境下(比如异步调用、线程池场景等)保证用户信息的正确传递和共享。
private static final ThreadLocal<UserInfoInTokenBO> USER_INFO_IN_TOKEN_HOLDER = new TransmittableThreadLocal<>();
/**
* 线
* `USER_INFO_IN_TOKEN_HOLDER` `get` `UserInfoInTokenBO`
* 线 `null`
*
* @return 线 `UserInfoInTokenBO` `null`
*/
public static UserInfoInTokenBO get() {
return USER_INFO_IN_TOKEN_HOLDER.get();
}
/**
* 线线
* `USER_INFO_IN_TOKEN_HOLDER` `set` `UserInfoInTokenBO`
* 线 `get`
*
* @param userInfoInTokenBo `UserInfoInTokenBO`
*/
public static void set(UserInfoInTokenBO userInfoInTokenBo) {
USER_INFO_IN_TOKEN_HOLDER.set(userInfoInTokenBo);
}
/**
* 线线
* 线 `USER_INFO_IN_TOKEN_HOLDER.get()` `null`
* `null` `USER_INFO_IN_TOKEN_HOLDER` `remove`
* 线
*/
public static void clean() {
if (USER_INFO_IN_TOKEN_HOLDER.get()!= null) {
USER_INFO_IN_TOKEN_HOLDER.remove();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

@ -0,0 +1,26 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
// 定义了一个名为com.yami.shop.dao的包用于组织代码
package com.yami.shop.dao;
// 导入了MyBatis Plus框架中的BaseMapper接口用于提供基础的CRUD操作
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// 导入了Area实体类这个类代表了数据库中的一个表
import com.yami.shop.bean.model.Area;
/**
* MapperArea
*
* @author lanhai lanhai
*/
public interface AreaMapper extends BaseMapper<Area> {
// 这个接口继承了BaseMapper接口因此已经包含了基础的CRUD操作不需要额外定义方法
}

@ -0,0 +1,26 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
// 定义了一个名为com.yami.shop.dao的包用于组织代码
package com.yami.shop.dao;
// 导入了MyBatis Plus框架中的BaseMapper接口用于提供基础的CRUD操作
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// 导入了AttachFile实体类这个类代表了数据库中的一个表
import com.yami.shop.bean.model.AttachFile;
/**
* MapperAttachFile
*
* @author lanhai lanhai
*/
public interface AttachFileMapper extends BaseMapper<AttachFile> {
// 这个接口继承了BaseMapper接口因此已经包含了基础的CRUD操作不需要额外定义方法
}

@ -0,0 +1,88 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
// 定义了一个名为com.yami.shop.dao的包用于组织代码
package com.yami.shop.dao;
// 导入了MyBatis Plus框架中的BaseMapper接口用于提供基础的CRUD操作
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// 导入了项目中定义的DTO和参数类
import com.yami.shop.bean.app.dto.ShopCartItemDto;
import com.yami.shop.bean.app.param.ShopCartParam;
// 导入了项目中的实体类
import com.yami.shop.bean.model.Basket;
// 导入了MyBatis的@Param注解用于在XML或注解中传递参数
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* MapperBasket
*
* @author lanhai lanhai
*/
public interface BasketMapper extends BaseMapper<Basket> {
/**
*
*
* @param userId id
* @return DTO
*/
List<ShopCartItemDto> getShopCartItems(@Param("userId") String userId);
/**
* idid
*
* @param userId id
* @param basketIds id
*/
void deleteShopCartItemsByBasketIds(@Param("userId") String userId, @Param("basketIds") List<Long> basketIds);
/**
*
*
* @param userId id
*/
void deleteAllShopCartItems(@Param("userId") String userId);
/**
*
*
* @param userId id
* @return DTO
*/
List<ShopCartItemDto> getShopCartExpiryItems(@Param("userId") String userId);
/**
*
*
* @param userId id
*/
void cleanExpiryProdList(@Param("userId") String userId);
/**
* id
*
* @param userId id
* @param basketIdShopCartParamMap id
*/
void updateDiscountItemId(@Param("userId")String userId, @Param("basketIdShopCartParamMap") Map<Long, ShopCartParam> basketIdShopCartParamMap);
/**
* idid
*
* @param prodId id
* @return id
*/
List<String> listUserIdByProdId(@Param("prodId")Long prodId);
}

@ -0,0 +1,37 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yami.shop.bean.model.Brand;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @author lanhai
*/
public interface BrandMapper extends BaseMapper<Brand> {
/**
*
* @param brandName
* @return
*/
Brand getByBrandName(String brandName);
/**
* id
* @param categoryId id
* @return
*/
List<Brand> listByCategoryId(@Param("categoryId")Long categoryId);
}

@ -0,0 +1,44 @@
/*
* Copyright (c) 2018-2999 广 All rights reserved.
*
* https://www.mall4j.com/
*
*
*
*
*/
package com.yami.shop.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yami.shop.bean.model.CategoryBrand;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* Mapper
*
* @author lanhai
*/
public interface CategoryBrandMapper extends BaseMapper<CategoryBrand> {
/**
*
* @param categoryId ID
* @param brandIds ID
*/
void insertCategoryBrand(@Param("categoryId") Long categoryId, @Param("brandIds") List<Long> brandIds);
/**
* ID
* @param categoryId ID
*/
void deleteByCategoryId(Long categoryId);
/**
* ID
* @param brandId ID
*/
void deleteByBrandId(Long brandId);
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save