From 991b6fe52988466fd905fddecdc4300b9e686471 Mon Sep 17 00:00:00 2001 From: xm <1350250847@qq.com> Date: Mon, 11 May 2026 17:55:48 +0800 Subject: [PATCH] =?UTF-8?q?1.=E8=B0=83=E6=95=B4=E8=AE=A2=E5=8D=95=E5=88=86?= =?UTF-8?q?=E9=94=80=E3=80=81=E5=88=86=E6=B6=A6=E3=80=81=E5=88=86=E7=BA=A2?= =?UTF-8?q?=E7=BB=93=E7=AE=97=E7=AE=97=E6=B3=95=E5=8A=9F=E8=83=BD=202.?= =?UTF-8?q?=E5=95=86=E5=93=81=E8=AE=A2=E5=8D=95=E6=94=AF=E4=BB=98=E6=88=90?= =?UTF-8?q?=E5=8A=9F=E5=A2=9E=E5=8A=A0=E6=89=A7=E8=A1=8C=E5=88=86=E9=94=80?= =?UTF-8?q?=E5=91=98=E5=88=86=E9=94=80=E3=80=81=E7=BB=9F=E8=AE=A1=E9=97=A8?= =?UTF-8?q?=E5=BA=97/=E6=9C=8D=E5=8A=A1=E5=95=86=E5=88=86=E9=94=80?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E3=80=81=E6=89=A7=E8=A1=8C=E6=80=BB=E5=88=86?= =?UTF-8?q?=E7=BA=A2=E4=B8=9A=E5=8A=A1=E5=8A=9F=E8=83=BD=203.=E9=85=8D?= =?UTF-8?q?=E9=80=81=E5=91=98=E5=AE=8C=E6=88=90=E9=85=8D=E9=80=81=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E8=A7=A3=E5=86=BB=E5=95=86=E5=93=81=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E5=8A=9F=E8=83=BD=204.=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E5=88=86=E9=94=80=E8=AE=B0=E5=BD=95=E5=A2=9E=E5=8A=A0=E5=85=B3?= =?UTF-8?q?=E8=81=94=E7=BB=93=E7=AE=97=E5=8D=95=E5=8F=B7=E3=80=81=E7=BB=93?= =?UTF-8?q?=E7=AE=97=E7=8A=B6=E6=80=81=E5=AD=97=E6=AE=B5=EF=BC=8C=E6=96=B9?= =?UTF-8?q?=E4=BE=BF=E9=97=A8=E5=BA=97/=E6=9C=8D=E5=8A=A1=E5=95=86?= =?UTF-8?q?=E5=81=9A=E8=AE=A1=E7=AE=97=E5=81=9A=E5=87=86=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/controller/WxLoginController.java | 1700 ++++++++--------- .../glt/service/GltTicketIssueService.java | 10 +- .../impl/GltTicketOrderServiceImpl.java | 23 +- .../task/DealerOrderSettlement10584Task.java | 467 ++++- .../shop/entity/ShopDealerOrder.java | 55 +- .../shop/mapper/ShopDealerRefereeMapper.java | 7 + .../shop/mapper/ShopOrderMapper.java | 9 +- .../mapper/xml/ShopDealerRefereeMapper.xml | 11 + .../shop/mapper/xml/ShopOrderMapper.xml | 22 + .../gxwebsoft/shop/vo/ShopOrderGoodsVO.java | 47 + 10 files changed, 1457 insertions(+), 894 deletions(-) create mode 100644 src/main/java/com/gxwebsoft/shop/vo/ShopOrderGoodsVO.java diff --git a/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java b/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java index 533bb03..3c3942d 100644 --- a/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java +++ b/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java @@ -1,851 +1,851 @@ -package com.gxwebsoft.common.system.controller; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpUtil; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.gxwebsoft.cms.entity.CmsWebsite; -import com.gxwebsoft.cms.service.CmsWebsiteService; -import com.gxwebsoft.common.core.config.ConfigProperties; -import com.gxwebsoft.common.core.exception.BusinessException; -import com.gxwebsoft.common.core.security.JwtSubject; -import com.gxwebsoft.common.core.security.JwtUtil; -import com.gxwebsoft.common.core.utils.CommonUtil; -import com.gxwebsoft.common.core.utils.RedisUtil; -import com.gxwebsoft.common.core.utils.RequestUtil; -import com.gxwebsoft.common.core.web.ApiResult; -import com.gxwebsoft.common.core.web.BaseController; -import com.gxwebsoft.common.system.entity.*; -import com.gxwebsoft.common.system.param.UserParam; -import com.gxwebsoft.common.system.result.LoginResult; -import com.gxwebsoft.common.system.service.*; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.swagger.v3.oas.annotations.Operation; -import okhttp3.*; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.bind.annotation.RequestBody; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.File; -import java.io.IOException; -import java.time.Instant; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeUnit; - -import static com.gxwebsoft.common.core.constants.PlatformConstants.MP_WEIXIN; -import static com.gxwebsoft.common.core.constants.RedisConstants.ACCESS_TOKEN_KEY; -import static com.gxwebsoft.common.core.constants.RedisConstants.MP_WX_KEY; - -@RestController -@RequestMapping("/api/wx-login") -@Tag(name = "微信小程序登录API") -public class WxLoginController extends BaseController { - private final StringRedisTemplate redisTemplate; - private final OkHttpClient http = new OkHttpClient(); - private final ObjectMapper om; - private volatile long tokenExpireEpoch = 0L; // 过期的 epoch 秒 - @Resource - private SettingService settingService; - @Resource - private UserService userService; - @Resource - private ConfigProperties configProperties; - @Resource - private UserRoleService userRoleService; - @Resource - private LoginRecordService loginRecordService; - @Resource - private RoleService roleService; - @Resource - private RedisUtil redisUtil; - @Resource - private RequestUtil requestUtil; - @Resource - private ConfigProperties config; - @Resource - private UserRefereeService userRefereeService; - @Resource - private CmsWebsiteService cmsWebsiteService; - - - public WxLoginController(StringRedisTemplate redisTemplate, ObjectMapper objectMapper) { - this.redisTemplate = redisTemplate; - this.om = objectMapper; - } - - @Operation(summary = "获取微信AccessToken") - @Transactional(rollbackFor = {Exception.class}) - @PostMapping("/getAccessToken") - public ApiResult getMpAccessToken() { - return success("操作成功", getAccessToken()); - } - - @Operation(summary = "获取微信openId") - @Transactional(rollbackFor = {Exception.class}) - @PostMapping("/getOpenId") - public ApiResult getOpenId(@RequestBody UserParam userParam, HttpServletRequest request) { - // 1.获取openid - JSONObject result = getOpenIdByCode(userParam); - String openid = result.getString("openid"); - String unionid = result.getString("unionid"); - if (openid == null) { - return fail("获取openid失败", null); - } - // 2.通过openid查询用户是否已存在 - User user = userService.getByOauthId(userParam); - // 3.存在则签发token并返回登录成功,不存在则注册新用户 - if (user == null) { - user = addUser(userParam); - } - // 4.签发token - loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request); - String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()), - configProperties.getTokenExpireTime(), configProperties.getTokenKey()); - return success("登录成功", new LoginResult(access_token, user)); - } - - @Operation(summary = "微信授权手机号码并登录") - @Transactional(rollbackFor = {Exception.class}) - @PostMapping("/loginByMpWxPhone") - public ApiResult loginByMpWxPhone(@RequestBody UserParam userParam, HttpServletRequest request) { - // 获取手机号码 - String phone = getPhoneByCode(userParam); - if (phone == null) { - String key = ACCESS_TOKEN_KEY.concat(":").concat(getTenantId().toString()); - redisTemplate.delete(key); - throw new BusinessException("授权失败,请重试"); - } - // 查询是否存在 - User user = userService.getByPhone(phone); - // 不存在则注册 - if (user == null) { - if ((userParam.getOpenid() == null || userParam.getOpenid().isEmpty()) && userParam.getAuthCode() != null) { - UserParam userParam2 = new UserParam(); - userParam2.setCode(userParam.getAuthCode()); - JSONObject result = getOpenIdByCode(userParam2); - String openid = result.getString("openid"); -// String unionid = result.getString("unionid"); - userParam.setOpenid(openid); - } - userParam.setPhone(phone); - user = addUser(userParam); - user.setRecommend(1); - } else { - // 存在则检查绑定上级 - if (userParam.getSceneType() != null && userParam.getSceneType().equals("save_referee") && userParam.getRefereeId() != null && userParam.getRefereeId() != 0) { - UserReferee check = userRefereeService.check(user.getUserId(), userParam.getRefereeId()); - if (check == null) { - UserReferee userReferee = new UserReferee(); - userReferee.setDealerId(userParam.getRefereeId()); - userReferee.setUserId(user.getUserId()); - userRefereeService.save(userReferee); - } - } - } - // 签发token - String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()), - configProperties.getTokenExpireTime(), configProperties.getTokenKey()); - loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_REGISTER, null, user.getTenantId(), request); - // 附加体育中心项目用户信息 -// user.setBookingUser(); - return success("登录成功", new LoginResult(access_token, user)); - } - - @Operation(summary = "微信授权手机号码并更新") - @Transactional(rollbackFor = {Exception.class}) - @PostMapping("/updatePhoneByMpWx") - public ApiResult updatePhoneByMpWx(@RequestBody UserParam userParam) { - // 获取微信授权手机号 - String phone = getPhoneByCode(userParam); - // 查询当前用户 - User user = userService.getById(userParam.getUserId()); - if (user != null && phone != null) { - user.setPhone(phone); - userService.updateUser(user); - return success("更新成功", phone); - } - return fail("更新失败"); - } - - /** - * 新用户注册 - */ - private User addUser(UserParam userParam) { - User addUser = new User(); - // 注册用户 - addUser.setStatus(0); - addUser.setUsername(createUsername("wx_")); - addUser.setNickname("微信用户"); - addUser.setPlatform(MP_WEIXIN); - addUser.setGradeId(2); - if (userParam.getGradeId() != null) { - addUser.setGradeId(userParam.getGradeId()); - } - if (userParam.getPhone() != null) { - addUser.setPhone(userParam.getPhone()); - } - if (StrUtil.isNotBlank(userParam.getOpenid())) { - addUser.setOpenid(userParam.getOpenid()); - } - if (StrUtil.isNotBlank(userParam.getUnionid())) { - addUser.setUnionid(userParam.getUnionid()); - } - addUser.setPassword(userService.encodePassword(CommonUtil.randomUUID16())); - addUser.setTenantId(getTenantId()); - addUser.setRecommend(1); - Role role = roleService.getOne(new QueryWrapper().eq("role_code", "user"), false); - addUser.setRoleId(role.getRoleId()); - if (userService.saveUser(addUser)) { - // 添加用户角色 - final UserRole userRole = new UserRole(); - userRole.setUserId(addUser.getUserId()); - userRole.setTenantId(addUser.getTenantId()); - userRole.setRoleId(addUser.getRoleId()); - userRoleService.save(userRole); - } - // 绑定关系 - if (userParam.getSceneType() != null && userParam.getSceneType().equals("save_referee") && userParam.getRefereeId() != null && userParam.getRefereeId() != 0) { - UserReferee check = userRefereeService.check(addUser.getUserId(), userParam.getRefereeId()); - if (check == null) { - UserReferee userReferee = new UserReferee(); - userReferee.setDealerId(userParam.getRefereeId()); - userReferee.setUserId(addUser.getUserId()); - userRefereeService.save(userReferee); - } - } - return addUser; - } - - // 获取openid - private JSONObject getOpenIdByCode(UserParam userParam) { - // 从缓存获取微信小程序配置信息 - JSONObject setting = getWxConfigFromCache(getTenantId()); - // 获取openId - String apiUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + setting.getString("appId") + "&secret=" + setting.getString("appSecret") + "&js_code=" + userParam.getCode() + "&grant_type=authorization_code"; - // 执行get请求 - String result = HttpUtil.get(apiUrl); - // 解析access_token - return JSON.parseObject(result); - } - - /** - * 获取微信手机号码 - * - * @param userParam 需要传微信凭证code - */ - private String getPhoneByCode(UserParam userParam) { - // 获取手机号码 - String apiUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken(); - HashMap paramMap = new HashMap<>(); - if (StrUtil.isBlank(userParam.getCode())) { - throw new BusinessException("code不能为空"); - } - paramMap.put("code", userParam.getCode()); - // 执行post请求 - String post = HttpUtil.post(apiUrl, JSON.toJSONString(paramMap)); - JSONObject json = JSON.parseObject(post); - if (json.get("errcode").equals(0)) { - JSONObject phoneInfo = JSON.parseObject(json.getString("phone_info")); - // 微信用户的手机号码 - final String phoneNumber = phoneInfo.getString("phoneNumber"); - // 验证手机号码 -// if (userParam.getNotVerifyPhone() == null && !Validator.isMobile(phoneNumber)) { -// String key = ACCESS_TOKEN_KEY.concat(":").concat(getTenantId().toString()); -// redisTemplate.delete(key); -// throw new BusinessException("手机号码格式不正确"); +//package com.gxwebsoft.common.system.controller; +// +//import cn.hutool.core.io.FileUtil; +//import cn.hutool.core.util.RandomUtil; +//import cn.hutool.core.util.StrUtil; +//import cn.hutool.http.HttpRequest; +//import cn.hutool.http.HttpUtil; +//import com.alibaba.fastjson.JSON; +//import com.alibaba.fastjson.JSONObject; +//import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +//import com.fasterxml.jackson.databind.JsonNode; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import com.gxwebsoft.cms.entity.CmsWebsite; +//import com.gxwebsoft.cms.service.CmsWebsiteService; +//import com.gxwebsoft.common.core.config.ConfigProperties; +//import com.gxwebsoft.common.core.exception.BusinessException; +//import com.gxwebsoft.common.core.security.JwtSubject; +//import com.gxwebsoft.common.core.security.JwtUtil; +//import com.gxwebsoft.common.core.utils.CommonUtil; +//import com.gxwebsoft.common.core.utils.RedisUtil; +//import com.gxwebsoft.common.core.utils.RequestUtil; +//import com.gxwebsoft.common.core.web.ApiResult; +//import com.gxwebsoft.common.core.web.BaseController; +//import com.gxwebsoft.common.system.entity.*; +//import com.gxwebsoft.common.system.param.UserParam; +//import com.gxwebsoft.common.system.result.LoginResult; +//import com.gxwebsoft.common.system.service.*; +//import io.swagger.v3.oas.annotations.tags.Tag; +//import io.swagger.v3.oas.annotations.Operation; +//import okhttp3.*; +//import org.springframework.data.redis.core.StringRedisTemplate; +//import org.springframework.transaction.annotation.Transactional; +//import org.springframework.web.bind.annotation.*; +//import org.springframework.web.bind.annotation.RequestBody; +// +//import javax.annotation.Resource; +//import javax.servlet.http.HttpServletRequest; +//import javax.servlet.http.HttpServletResponse; +//import java.io.File; +//import java.io.IOException; +//import java.time.Instant; +//import java.util.HashMap; +//import java.util.List; +//import java.util.Map; +//import java.util.concurrent.TimeUnit; +//import java.util.concurrent.TimeUnit; +// +//import static com.gxwebsoft.common.core.constants.PlatformConstants.MP_WEIXIN; +//import static com.gxwebsoft.common.core.constants.RedisConstants.ACCESS_TOKEN_KEY; +//import static com.gxwebsoft.common.core.constants.RedisConstants.MP_WX_KEY; +// +//@RestController +//@RequestMapping("/api/wx-login") +//@Tag(name = "微信小程序登录API") +//public class WxLoginController extends BaseController { +// private final StringRedisTemplate redisTemplate; +// private final OkHttpClient http = new OkHttpClient(); +// private final ObjectMapper om; +// private volatile long tokenExpireEpoch = 0L; // 过期的 epoch 秒 +// @Resource +// private SettingService settingService; +// @Resource +// private UserService userService; +// @Resource +// private ConfigProperties configProperties; +// @Resource +// private UserRoleService userRoleService; +// @Resource +// private LoginRecordService loginRecordService; +// @Resource +// private RoleService roleService; +// @Resource +// private RedisUtil redisUtil; +// @Resource +// private RequestUtil requestUtil; +// @Resource +// private ConfigProperties config; +// @Resource +// private UserRefereeService userRefereeService; +// @Resource +// private CmsWebsiteService cmsWebsiteService; +// +// +// public WxLoginController(StringRedisTemplate redisTemplate, ObjectMapper objectMapper) { +// this.redisTemplate = redisTemplate; +// this.om = objectMapper; +// } +// +// @Operation(summary = "获取微信AccessToken") +// @Transactional(rollbackFor = {Exception.class}) +// @PostMapping("/getAccessToken") +// public ApiResult getMpAccessToken() { +// return success("操作成功", getAccessToken()); +// } +// +// @Operation(summary = "获取微信openId") +// @Transactional(rollbackFor = {Exception.class}) +// @PostMapping("/getOpenId") +// public ApiResult getOpenId(@RequestBody UserParam userParam, HttpServletRequest request) { +// // 1.获取openid +// JSONObject result = getOpenIdByCode(userParam); +// String openid = result.getString("openid"); +// String unionid = result.getString("unionid"); +// if (openid == null) { +// return fail("获取openid失败", null); +// } +// // 2.通过openid查询用户是否已存在 +// User user = userService.getByOauthId(userParam); +// // 3.存在则签发token并返回登录成功,不存在则注册新用户 +// if (user == null) { +// user = addUser(userParam); +// } +// // 4.签发token +// loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request); +// String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()), +// configProperties.getTokenExpireTime(), configProperties.getTokenKey()); +// return success("登录成功", new LoginResult(access_token, user)); +// } +// +// @Operation(summary = "微信授权手机号码并登录") +// @Transactional(rollbackFor = {Exception.class}) +// @PostMapping("/loginByMpWxPhone") +// public ApiResult loginByMpWxPhone(@RequestBody UserParam userParam, HttpServletRequest request) { +// // 获取手机号码 +// String phone = getPhoneByCode(userParam); +// if (phone == null) { +// String key = ACCESS_TOKEN_KEY.concat(":").concat(getTenantId().toString()); +// redisTemplate.delete(key); +// throw new BusinessException("授权失败,请重试"); +// } +// // 查询是否存在 +// User user = userService.getByPhone(phone); +// // 不存在则注册 +// if (user == null) { +// if ((userParam.getOpenid() == null || userParam.getOpenid().isEmpty()) && userParam.getAuthCode() != null) { +// UserParam userParam2 = new UserParam(); +// userParam2.setCode(userParam.getAuthCode()); +// JSONObject result = getOpenIdByCode(userParam2); +// String openid = result.getString("openid"); +//// String unionid = result.getString("unionid"); +// userParam.setOpenid(openid); +// } +// userParam.setPhone(phone); +// user = addUser(userParam); +// user.setRecommend(1); +// } else { +// // 存在则检查绑定上级 +// if (userParam.getSceneType() != null && userParam.getSceneType().equals("save_referee") && userParam.getRefereeId() != null && userParam.getRefereeId() != 0) { +// UserReferee check = userRefereeService.check(user.getUserId(), userParam.getRefereeId()); +// if (check == null) { +// UserReferee userReferee = new UserReferee(); +// userReferee.setDealerId(userParam.getRefereeId()); +// userReferee.setUserId(user.getUserId()); +// userRefereeService.save(userReferee); +// } +// } +// } +// // 签发token +// String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()), +// configProperties.getTokenExpireTime(), configProperties.getTokenKey()); +// loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_REGISTER, null, user.getTenantId(), request); +// // 附加体育中心项目用户信息 +//// user.setBookingUser(); +// return success("登录成功", new LoginResult(access_token, user)); +// } +// +// @Operation(summary = "微信授权手机号码并更新") +// @Transactional(rollbackFor = {Exception.class}) +// @PostMapping("/updatePhoneByMpWx") +// public ApiResult updatePhoneByMpWx(@RequestBody UserParam userParam) { +// // 获取微信授权手机号 +// String phone = getPhoneByCode(userParam); +// // 查询当前用户 +// User user = userService.getById(userParam.getUserId()); +// if (user != null && phone != null) { +// user.setPhone(phone); +// userService.updateUser(user); +// return success("更新成功", phone); +// } +// return fail("更新失败"); +// } +// +// /** +// * 新用户注册 +// */ +// private User addUser(UserParam userParam) { +// User addUser = new User(); +// // 注册用户 +// addUser.setStatus(0); +// addUser.setUsername(createUsername("wx_")); +// addUser.setNickname("微信用户"); +// addUser.setPlatform(MP_WEIXIN); +// addUser.setGradeId(2); +// if (userParam.getGradeId() != null) { +// addUser.setGradeId(userParam.getGradeId()); +// } +// if (userParam.getPhone() != null) { +// addUser.setPhone(userParam.getPhone()); +// } +// if (StrUtil.isNotBlank(userParam.getOpenid())) { +// addUser.setOpenid(userParam.getOpenid()); +// } +// if (StrUtil.isNotBlank(userParam.getUnionid())) { +// addUser.setUnionid(userParam.getUnionid()); +// } +// addUser.setPassword(userService.encodePassword(CommonUtil.randomUUID16())); +// addUser.setTenantId(getTenantId()); +// addUser.setRecommend(1); +// Role role = roleService.getOne(new QueryWrapper().eq("role_code", "user"), false); +// addUser.setRoleId(role.getRoleId()); +// if (userService.saveUser(addUser)) { +// // 添加用户角色 +// final UserRole userRole = new UserRole(); +// userRole.setUserId(addUser.getUserId()); +// userRole.setTenantId(addUser.getTenantId()); +// userRole.setRoleId(addUser.getRoleId()); +// userRoleService.save(userRole); +// } +// // 绑定关系 +// if (userParam.getSceneType() != null && userParam.getSceneType().equals("save_referee") && userParam.getRefereeId() != null && userParam.getRefereeId() != 0) { +// UserReferee check = userRefereeService.check(addUser.getUserId(), userParam.getRefereeId()); +// if (check == null) { +// UserReferee userReferee = new UserReferee(); +// userReferee.setDealerId(userParam.getRefereeId()); +// userReferee.setUserId(addUser.getUserId()); +// userRefereeService.save(userReferee); +// } +// } +// return addUser; +// } +// +// // 获取openid +// private JSONObject getOpenIdByCode(UserParam userParam) { +// // 从缓存获取微信小程序配置信息 +// JSONObject setting = getWxConfigFromCache(getTenantId()); +// // 获取openId +// String apiUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + setting.getString("appId") + "&secret=" + setting.getString("appSecret") + "&js_code=" + userParam.getCode() + "&grant_type=authorization_code"; +// // 执行get请求 +// String result = HttpUtil.get(apiUrl); +// // 解析access_token +// return JSON.parseObject(result); +// } +// +// /** +// * 获取微信手机号码 +// * +// * @param userParam 需要传微信凭证code +// */ +// private String getPhoneByCode(UserParam userParam) { +// // 获取手机号码 +// String apiUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken(); +// HashMap paramMap = new HashMap<>(); +// if (StrUtil.isBlank(userParam.getCode())) { +// throw new BusinessException("code不能为空"); +// } +// paramMap.put("code", userParam.getCode()); +// // 执行post请求 +// String post = HttpUtil.post(apiUrl, JSON.toJSONString(paramMap)); +// JSONObject json = JSON.parseObject(post); +// if (json.get("errcode").equals(0)) { +// JSONObject phoneInfo = JSON.parseObject(json.getString("phone_info")); +// // 微信用户的手机号码 +// final String phoneNumber = phoneInfo.getString("phoneNumber"); +// // 验证手机号码 +//// if (userParam.getNotVerifyPhone() == null && !Validator.isMobile(phoneNumber)) { +//// String key = ACCESS_TOKEN_KEY.concat(":").concat(getTenantId().toString()); +//// redisTemplate.delete(key); +//// throw new BusinessException("手机号码格式不正确"); +//// } +// return phoneNumber; +// } +// return null; +// } +// +// /** +// * 生成随机账号 +// * +// * @return username +// */ +// private String createUsername(String type) { +// return type.concat(RandomUtil.randomString(12)); +// } +// +// /** +// * 获取接口调用凭据AccessToken +// * ... +// */ +// public String getAccessToken() { +// Integer tenantId = getTenantId(); +// String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId.toString()); +// +// // 从缓存获取微信小程序配置信息 +// JSONObject setting = getWxConfigFromCache(tenantId); +// if (setting == null) { +// throw new BusinessException("请先配置小程序"); +// } +// +// // 从缓存获取access_token +// String value = redisTemplate.opsForValue().get(key); +// if (value != null) { +// // 解析access_token +// JSONObject response = JSON.parseObject(value); +// String accessToken = response.getString("access_token"); +// if (accessToken != null) { +// return accessToken; +// } +// } +// +// // 微信获取凭证接口 +// String apiUrl = "https://api.weixin.qq.com/cgi-bin/token"; +// // 组装url参数 +// String url = apiUrl.concat("?grant_type=client_credential") +// .concat("&appid=").concat(setting.getString("appId")) +// .concat("&secret=").concat(setting.getString("appSecret")); +// +// // 执行get请求 +// String result = HttpUtil.get(url); +// // 解析access_token +// JSONObject response = JSON.parseObject(result); +// if (response.getString("access_token") != null) { +// // 存入缓存 +// redisTemplate.opsForValue().set(key, result, 7000L, TimeUnit.SECONDS); +// return response.getString("access_token"); +// } +// throw new BusinessException("小程序配置不正确"); +// } +// +// @Operation(summary = "获取微信openId并更新") +// @PostMapping("/getWxOpenId") +// public ApiResult getWxOpenId(@RequestBody UserParam userParam) { +// final User loginUser = getLoginUser(); +// if (loginUser == null) { +// return fail("请先登录"); +// } +// // 已存在直接返回 +// if (StrUtil.isNotBlank(loginUser.getOpenid())) { +// return success(loginUser); +// } +// // 请求微信接口获取openid +// String apiUrl = "https://api.weixin.qq.com/sns/jscode2session"; +// final HashMap map = new HashMap<>(); +// final JSONObject setting = getWxConfigFromCache(getTenantId()); +// final String appId = setting.getString("appId"); +// final String appSecret = setting.getString("appSecret"); +// map.put("appid", appId); +// map.put("secret", appSecret); +// map.put("js_code", userParam.getCode()); +// map.put("grant_type", "authorization_code"); +// final String response = HttpUtil.get(apiUrl, map); +// final JSONObject jsonObject = JSONObject.parseObject(response); +// String openid = jsonObject.getString("openid"); +// String sessionKey = jsonObject.getString("session_key"); +// String unionid = jsonObject.getString("unionid"); +// // 保存openID +// if (loginUser.getOpenid() == null || StrUtil.isBlank(loginUser.getOpenid())) { +// loginUser.setOpenid(openid); +// loginUser.setUnionid(unionid); +// requestUtil.updateUser(loginUser); +//// userService.updateById(loginUser); +// } +// return success("获取成功", jsonObject); +// } +// +// @Operation(summary = "仅获取微信openId") +// @PostMapping("/getWxOpenIdOnly") +// public ApiResult getWxOpenIdOnly(@RequestBody UserParam userParam) { +// +// String apiUrl = "https://api.weixin.qq.com/sns/jscode2session"; +// final HashMap map = new HashMap<>(); +// final JSONObject setting = getWxConfigFromCache(getTenantId()); +// final String appId = setting.getString("appId"); +// final String appSecret = setting.getString("appSecret"); +// map.put("appid", appId); +// map.put("secret", appSecret); +// map.put("js_code", userParam.getCode()); +// map.put("grant_type", "authorization_code"); +// final String response = HttpUtil.get(apiUrl, map); +// final JSONObject jsonObject = JSONObject.parseObject(response); +// return success("获取成功", jsonObject); +// } +// +// @Operation(summary = "获取微信小程序码-用户ID") +// @GetMapping("/getUserQRCode") +// public ApiResult getQRCode() { +// String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + getAccessToken(); +// final HashMap map = new HashMap<>(); +// map.put("path", "/package/user/qrcode?user_id=" + getLoginUserId()); +//// map.put("env_version","trial"); +// // 获取图片 Buffer +// byte[] qrCode = HttpRequest.post(apiUrl) +// .body(JSON.toJSONString(map)) +// .execute().bodyBytes(); +// +// // 保存的文件名称 +// final String fileName = CommonUtil.randomUUID8().concat(".png"); +// // 保存路径 +// String filePath = getUploadDir().concat("qrcode/") + fileName; +// File file = FileUtil.writeBytes(qrCode, filePath); +// if (file != null) { +// return success(config.getFileServer().concat("/qrcode/").concat(fileName)); +// } +// return fail("获取失败", null); +// } +// +// @Operation(summary = "获取微信小程序码-订单核销码") +// @GetMapping("/getOrderQRCode/{orderNo}") +// public ApiResult getOrderQRCode(@PathVariable("orderNo") String orderNo) { +// String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + getAccessToken(); +// final HashMap map = new HashMap<>(); +// map.put("path", "/package/admin/order-scan?orderNo=".concat(orderNo)); +// map.put("env_version", "release"); +// // 获取图片 Buffer +// byte[] qrCode = HttpRequest.post(apiUrl) +// .body(JSON.toJSONString(map)) +// .execute().bodyBytes(); +// +// // 保存的文件名称 +// final String fileName = CommonUtil.randomUUID8().concat(".png"); +// // 保存路径 +// String filePath = getUploadDir().concat("qrcode/") + fileName; +// File file = FileUtil.writeBytes(qrCode, filePath); +// if (file != null) { +// return success(config.getFileServer().concat("/qrcode/").concat(fileName)); +// } +// return fail("获取失败", null); +// } +// +// @Operation(summary = "获取微信小程序码-订单核销码-数量极多的业务场景") +// @GetMapping("/getOrderQRCodeUnlimited/{scene}") +// public void getOrderQRCodeUnlimited(@PathVariable("scene") String scene, HttpServletResponse response) throws IOException { +// try { +// // 从scene参数中解析租户ID +// System.out.println("scene = " + scene); +// Integer tenantId = extractTenantIdFromScene(scene); +// System.out.println("从scene参数中解析租户ID = " + tenantId); +// if (tenantId == null) { +// response.setStatus(HttpServletResponse.SC_BAD_REQUEST); +// response.getWriter().write("{\"error\":\"无法从scene参数中获取租户信息\"}"); +// return; +// } +// +// // 使用指定租户ID获取 access_token +// String accessToken = getAccessTokenForTenant(tenantId); +// String apiUrl = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + accessToken; +// +// final HashMap map = new HashMap<>(); +// map.put("scene", scene); +// map.put("page", "pages/index/index"); +// map.put("env_version", "release"); +// +// // 判断应用运行状态 +// final CmsWebsite website = cmsWebsiteService.getByTenantId(tenantId); +// if(website != null && Integer.valueOf(2).equals(website.getRunning())){ +// map.put("check_path",false); +// map.put("env_version","trial"); +// } +// +// String jsonBody = JSON.toJSONString(map); +// System.out.println("请求的 JSON body = " + jsonBody); +// +// // 获取微信 API 响应 +// cn.hutool.http.HttpResponse httpResponse = HttpRequest.post(apiUrl) +// .body(jsonBody) +// .execute(); +// +// byte[] responseBytes = httpResponse.bodyBytes(); +// String contentType = httpResponse.header("Content-Type"); +// +// // 检查响应内容类型,判断是否为错误响应 +// if (contentType != null && contentType.contains("application/json")) { +// // 微信返回了错误信息(JSON格式) +// String errorResponse = new String(responseBytes, "UTF-8"); +// System.err.println("微信 API 错误响应: " + errorResponse); +// +// // 返回错误信息给前端 +// response.setContentType("application/json;charset=UTF-8"); +// response.setStatus(HttpServletResponse.SC_BAD_REQUEST); +// response.getWriter().write(errorResponse); +// return; +// } +// +// // 成功获取二维码图片 +// response.setContentType("image/png"); +// response.setHeader("Cache-Control", "no-cache"); +// response.setHeader("Content-Disposition", "inline; filename=qrcode.png"); +// +// // 输出图片 +// response.getOutputStream().write(responseBytes); +// System.out.println("二维码生成成功,大小: " + responseBytes.length + " bytes"); +// +// } catch (Exception e) { +// System.err.println("生成二维码失败: " + e.getMessage()); +// e.printStackTrace(); +// +// // 返回错误信息 +// response.setContentType("application/json;charset=UTF-8"); +// response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); +// response.getWriter().write("{\"error\":\"生成二维码失败: " + e.getMessage() + "\"}"); +// } +// } +// +// @Operation(summary = "获取微信小程序码-用户ID") +// @GetMapping("/getQRCodeText") +// public byte[] getQRCodeText(String scene, String page, Integer width, +// Boolean isHyaline, String envVersion) throws IOException { +// HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/wxa/getwxacodeunlimit") +// .newBuilder() +// .addQueryParameter("access_token", getLocalAccessToken()) +// .build(); +// +// System.out.println("page = " + page); +// // 构造请求 JSON +// // 注意:scene 仅支持可见字符,长度上限 32,尽量 URL-safe(字母数字下划线等) +// // page 必须是已发布小程序内的路径(不带开头斜杠也可) +// var root = om.createObjectNode(); +// root.put("scene", scene); +// if (page != null) root.put("page", page); +// if (width != null) root.put("width", width); // 默认 430,建议 280~1280 +// if (isHyaline != null) root.put("is_hyaline", isHyaline); +// if (envVersion != null) root.put("env_version", envVersion); // release/trial/develop +// +// okhttp3.RequestBody reqBody = okhttp3.RequestBody.create( +// root.toString(), MediaType.parse("application/json; charset=utf-8")); +// Request req = new Request.Builder().url(url).post(reqBody).build(); +// +// try (Response resp = http.newCall(req).execute()) { +// if (!resp.isSuccessful()) { +// throw new IOException("HTTP " + resp.code() + " calling getwxacodeunlimit"); +// } +// MediaType ct = resp.body().contentType(); +// byte[] bytes = resp.body().bytes(); +// // 微信出错时返回 JSON,需要识别一下 +// if (ct != null && ct.subtype() != null && ct.subtype().contains("json")) { +// String err = new String(bytes); +// throw new IOException("WeChat error: " + err); +// } +// return bytes; // 成功就是图片二进制(PNG) +// } +// } +// +// /** +// * 获取/刷新 access_token +// */ +// public String getLocalAccessToken() throws IOException { +// long now = Instant.now().getEpochSecond(); +// String key = "AccessToken:Local:" + getTenantId(); +// +// // 从缓存获取access_token,使用JSON格式 +// String value = redisUtil.get(key); +// if (value != null && now < tokenExpireEpoch - 60) { +// try { +// // 尝试解析为JSON格式 +// JSONObject response = JSON.parseObject(value); +// String accessToken = response.getString("access_token"); +// if (accessToken != null) { +// System.out.println("从缓存获取到access_token(Local): " + accessToken.substring(0, Math.min(10, accessToken.length())) + "..."); +// return accessToken; +// } +// } catch (Exception e) { +// // 如果解析失败,可能是旧格式的纯字符串token +// System.out.println("本地缓存token格式异常,使用原值: " + e.getMessage()); +// return value; +// } +// } +// +// // 从缓存获取微信小程序配置信息 +// Integer tenantId = getTenantId(); +// JSONObject setting = getWxConfigFromCache(tenantId); +// if (setting == null) { +// throw new IOException("请先配置小程序"); +// } +// +// String appId = setting.getString("appId"); +// String appSecret = setting.getString("appSecret"); +// +// if (appId == null || appSecret == null) { +// throw new IOException("小程序配置不完整,缺少 appId 或 appSecret"); +// } +// +// HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/cgi-bin/token") +// .newBuilder() +// .addQueryParameter("grant_type", "client_credential") +// .addQueryParameter("appid", appId) +// .addQueryParameter("secret", appSecret) +// .build(); +// +// Request req = new Request.Builder().url(url).get().build(); +// try (Response resp = http.newCall(req).execute()) { +// String body = resp.body().string(); +// JsonNode json = om.readTree(body); +// if (json.has("access_token")) { +// String token = json.get("access_token").asText(); +// long expiresIn = json.get("expires_in").asInt(7200); +// +// // 缓存完整的JSON响应,与其他方法保持一致 +// redisUtil.set(key, body, expiresIn, TimeUnit.SECONDS); +// tokenExpireEpoch = now + expiresIn; +// System.out.println("获取新的access_token成功(Local),租户ID: " + tenantId); +// return token; +// } else { +// throw new IOException("Get access_token failed: " + body); +// } +// } +// } +// +// /** +// * 文件上传位置(服务器) +// */ +// private String getUploadDir() { +// return config.getUploadPath(); +// } +// +// @Operation(summary = "调试:检查微信小程序配置") +// @GetMapping("/debug/checkWxConfig") +// public ApiResult debugCheckWxConfig() { +// Integer tenantId = getTenantId(); +// Map result = new HashMap<>(); +// result.put("tenantId", tenantId); +// +// try { +// // 尝试从缓存获取配置 +// JSONObject setting = getWxConfigFromCache(tenantId); +// result.put("hasConfig", true); +// result.put("config", setting); +// result.put("cacheKey", MP_WX_KEY + tenantId); +// } catch (Exception e) { +// result.put("hasConfig", false); +// result.put("error", e.getMessage()); +// result.put("cacheKey", MP_WX_KEY + tenantId); +// +// // 提供创建配置的建议 +// Map suggestion = new HashMap<>(); +// suggestion.put("message", "请在Redis中创建微信小程序配置"); +// suggestion.put("cacheKey", MP_WX_KEY + tenantId); +// suggestion.put("tenantId", tenantId); +// suggestion.put("sampleConfig", createSampleWxConfig()); +// result.put("suggestion", suggestion); +// } +// +// return success("配置检查完成", result); +// } +// +// @Operation(summary = "调试:创建示例微信小程序配置") +// @PostMapping("/debug/createSampleWxConfig") +// public ApiResult debugCreateSampleWxConfig(@RequestBody Map params) { +// Integer tenantId = getTenantId(); +// +// String appId = params.get("appId"); +// String appSecret = params.get("appSecret"); +// +// if (appId == null || appSecret == null) { +// return fail("请提供 appId 和 appSecret", null); +// } +// +// try { +// // 直接在Redis中创建配置 +// String key = MP_WX_KEY + tenantId; +// +// // 创建配置内容 +// Map config = new HashMap<>(); +// config.put("appId", appId); +// config.put("appSecret", appSecret); +// config.put("tenantId", tenantId.toString()); +// config.put("settingKey", "mp-weixin"); +// config.put("settingId", "301"); +// +// // 保存到Redis缓存 +// redisUtil.set(key, JSON.toJSONString(config)); +// +// return success("微信小程序配置创建成功", config); +// } catch (Exception e) { +// return fail("创建配置失败: " + e.getMessage(), null); +// } +// } +// +// private Map createSampleWxConfig() { +// Map sample = new HashMap<>(); +// sample.put("appId", "wx_your_app_id_here"); +// sample.put("appSecret", "your_app_secret_here"); +// return sample; +// } +// +// @Operation(summary = "调试:获取AccessToken") +// @GetMapping("/debug/getAccessToken") +// public ApiResult debugGetAccessToken() { +// try { +// // 获取当前线程的租户ID +// Integer tenantId = getTenantId(); +// if (tenantId == null) { +// tenantId = 10550; // 默认租户 +// } +// +// System.out.println("=== 开始调试获取AccessToken,租户ID: " + tenantId + " ==="); +// +// // 手动调用获取AccessToken +// String accessToken = getAccessTokenForTenant(tenantId); +// +// String result = "获取AccessToken成功: " + (accessToken != null ? accessToken.substring(0, Math.min(10, accessToken.length())) + "..." : "null"); +// System.out.println("调试结果: " + result); +// +// return success(result); +// } catch (Exception e) { +// System.err.println("调试获取AccessToken异常: " + e.getMessage()); +// e.printStackTrace(); +// return fail("获取AccessToken失败: " + e.getMessage()); +// } +// } +// +// /** +// * 从Redis缓存中获取微信小程序配置 +// * @param tenantId 租户ID +// * @return 微信配置信息 +// */ +// private JSONObject getWxConfigFromCache(Integer tenantId) { +// String key = MP_WX_KEY + tenantId; +// String cacheValue = redisUtil.get(key); +// if (StrUtil.isBlank(cacheValue)) { +// throw new BusinessException("未找到微信小程序配置,请检查缓存key: " + key); +// } +// try { +// return JSON.parseObject(cacheValue); +// } catch (Exception e) { +// throw new BusinessException("微信小程序配置格式错误: " + e.getMessage()); +// } +// } +// +// /** +// * 从scene参数中提取租户ID +// * scene格式: uid_userId_tenantId(优先)或 uid_userId(兼容旧格式) +// */ +// private Integer extractTenantIdFromScene(String scene) { +// try { +// System.out.println("解析scene参数: " + scene); +// +// if (scene != null && scene.startsWith("uid_")) { +// String content = scene.substring(4); // 去掉"uid_"前缀 +// +// // 优先解析 uid_userId_tenantId 格式 +// String[] parts = content.split("_"); +// if (parts.length >= 2) { +// try { +// Integer tenantId = Integer.parseInt(parts[1]); +// System.out.println("从scene直接解析到tenantId = " + tenantId); +// return tenantId; +// } catch (NumberFormatException e) { +// System.err.println("scene中tenantId格式异常: " + parts[1]); +// } +// } +// +// // 兼容旧格式 uid_userId:根据用户ID查询租户ID +// if (parts.length == 1) { +// Integer userId = Integer.parseInt(parts[0]); +// System.out.println("userId = " + userId); +// try { +// List users = userService.listByIdIgnoreTenant(userId); +// System.out.println("查询到用户数量 = " + (users != null ? users.size() : 0)); +// if (users != null && !users.isEmpty()) { +// System.out.println("从用户ID " + userId + " 获取到租户ID: " + users.get(0).getTenantId()); +// return users.get(0).getTenantId(); +// } else { +// System.err.println("未找到用户ID: " + userId); // } - return phoneNumber; - } - return null; - } - - /** - * 生成随机账号 - * - * @return username - */ - private String createUsername(String type) { - return type.concat(RandomUtil.randomString(12)); - } - - /** - * 获取接口调用凭据AccessToken - * ... - */ - public String getAccessToken() { - Integer tenantId = getTenantId(); - String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId.toString()); - - // 从缓存获取微信小程序配置信息 - JSONObject setting = getWxConfigFromCache(tenantId); - if (setting == null) { - throw new BusinessException("请先配置小程序"); - } - - // 从缓存获取access_token - String value = redisTemplate.opsForValue().get(key); - if (value != null) { - // 解析access_token - JSONObject response = JSON.parseObject(value); - String accessToken = response.getString("access_token"); - if (accessToken != null) { - return accessToken; - } - } - - // 微信获取凭证接口 - String apiUrl = "https://api.weixin.qq.com/cgi-bin/token"; - // 组装url参数 - String url = apiUrl.concat("?grant_type=client_credential") - .concat("&appid=").concat(setting.getString("appId")) - .concat("&secret=").concat(setting.getString("appSecret")); - - // 执行get请求 - String result = HttpUtil.get(url); - // 解析access_token - JSONObject response = JSON.parseObject(result); - if (response.getString("access_token") != null) { - // 存入缓存 - redisTemplate.opsForValue().set(key, result, 7000L, TimeUnit.SECONDS); - return response.getString("access_token"); - } - throw new BusinessException("小程序配置不正确"); - } - - @Operation(summary = "获取微信openId并更新") - @PostMapping("/getWxOpenId") - public ApiResult getWxOpenId(@RequestBody UserParam userParam) { - final User loginUser = getLoginUser(); - if (loginUser == null) { - return fail("请先登录"); - } - // 已存在直接返回 - if (StrUtil.isNotBlank(loginUser.getOpenid())) { - return success(loginUser); - } - // 请求微信接口获取openid - String apiUrl = "https://api.weixin.qq.com/sns/jscode2session"; - final HashMap map = new HashMap<>(); - final JSONObject setting = getWxConfigFromCache(getTenantId()); - final String appId = setting.getString("appId"); - final String appSecret = setting.getString("appSecret"); - map.put("appid", appId); - map.put("secret", appSecret); - map.put("js_code", userParam.getCode()); - map.put("grant_type", "authorization_code"); - final String response = HttpUtil.get(apiUrl, map); - final JSONObject jsonObject = JSONObject.parseObject(response); - String openid = jsonObject.getString("openid"); - String sessionKey = jsonObject.getString("session_key"); - String unionid = jsonObject.getString("unionid"); - // 保存openID - if (loginUser.getOpenid() == null || StrUtil.isBlank(loginUser.getOpenid())) { - loginUser.setOpenid(openid); - loginUser.setUnionid(unionid); - requestUtil.updateUser(loginUser); -// userService.updateById(loginUser); - } - return success("获取成功", jsonObject); - } - - @Operation(summary = "仅获取微信openId") - @PostMapping("/getWxOpenIdOnly") - public ApiResult getWxOpenIdOnly(@RequestBody UserParam userParam) { - - String apiUrl = "https://api.weixin.qq.com/sns/jscode2session"; - final HashMap map = new HashMap<>(); - final JSONObject setting = getWxConfigFromCache(getTenantId()); - final String appId = setting.getString("appId"); - final String appSecret = setting.getString("appSecret"); - map.put("appid", appId); - map.put("secret", appSecret); - map.put("js_code", userParam.getCode()); - map.put("grant_type", "authorization_code"); - final String response = HttpUtil.get(apiUrl, map); - final JSONObject jsonObject = JSONObject.parseObject(response); - return success("获取成功", jsonObject); - } - - @Operation(summary = "获取微信小程序码-用户ID") - @GetMapping("/getUserQRCode") - public ApiResult getQRCode() { - String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + getAccessToken(); - final HashMap map = new HashMap<>(); - map.put("path", "/package/user/qrcode?user_id=" + getLoginUserId()); -// map.put("env_version","trial"); - // 获取图片 Buffer - byte[] qrCode = HttpRequest.post(apiUrl) - .body(JSON.toJSONString(map)) - .execute().bodyBytes(); - - // 保存的文件名称 - final String fileName = CommonUtil.randomUUID8().concat(".png"); - // 保存路径 - String filePath = getUploadDir().concat("qrcode/") + fileName; - File file = FileUtil.writeBytes(qrCode, filePath); - if (file != null) { - return success(config.getFileServer().concat("/qrcode/").concat(fileName)); - } - return fail("获取失败", null); - } - - @Operation(summary = "获取微信小程序码-订单核销码") - @GetMapping("/getOrderQRCode/{orderNo}") - public ApiResult getOrderQRCode(@PathVariable("orderNo") String orderNo) { - String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + getAccessToken(); - final HashMap map = new HashMap<>(); - map.put("path", "/package/admin/order-scan?orderNo=".concat(orderNo)); - map.put("env_version", "release"); - // 获取图片 Buffer - byte[] qrCode = HttpRequest.post(apiUrl) - .body(JSON.toJSONString(map)) - .execute().bodyBytes(); - - // 保存的文件名称 - final String fileName = CommonUtil.randomUUID8().concat(".png"); - // 保存路径 - String filePath = getUploadDir().concat("qrcode/") + fileName; - File file = FileUtil.writeBytes(qrCode, filePath); - if (file != null) { - return success(config.getFileServer().concat("/qrcode/").concat(fileName)); - } - return fail("获取失败", null); - } - - @Operation(summary = "获取微信小程序码-订单核销码-数量极多的业务场景") - @GetMapping("/getOrderQRCodeUnlimited/{scene}") - public void getOrderQRCodeUnlimited(@PathVariable("scene") String scene, HttpServletResponse response) throws IOException { - try { - // 从scene参数中解析租户ID - System.out.println("scene = " + scene); - Integer tenantId = extractTenantIdFromScene(scene); - System.out.println("从scene参数中解析租户ID = " + tenantId); - if (tenantId == null) { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - response.getWriter().write("{\"error\":\"无法从scene参数中获取租户信息\"}"); - return; - } - - // 使用指定租户ID获取 access_token - String accessToken = getAccessTokenForTenant(tenantId); - String apiUrl = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + accessToken; - - final HashMap map = new HashMap<>(); - map.put("scene", scene); - map.put("page", "pages/index/index"); - map.put("env_version", "release"); - - // 判断应用运行状态 - final CmsWebsite website = cmsWebsiteService.getByTenantId(tenantId); - if(website != null && Integer.valueOf(2).equals(website.getRunning())){ - map.put("check_path",false); - map.put("env_version","trial"); - } - - String jsonBody = JSON.toJSONString(map); - System.out.println("请求的 JSON body = " + jsonBody); - - // 获取微信 API 响应 - cn.hutool.http.HttpResponse httpResponse = HttpRequest.post(apiUrl) - .body(jsonBody) - .execute(); - - byte[] responseBytes = httpResponse.bodyBytes(); - String contentType = httpResponse.header("Content-Type"); - - // 检查响应内容类型,判断是否为错误响应 - if (contentType != null && contentType.contains("application/json")) { - // 微信返回了错误信息(JSON格式) - String errorResponse = new String(responseBytes, "UTF-8"); - System.err.println("微信 API 错误响应: " + errorResponse); - - // 返回错误信息给前端 - response.setContentType("application/json;charset=UTF-8"); - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - response.getWriter().write(errorResponse); - return; - } - - // 成功获取二维码图片 - response.setContentType("image/png"); - response.setHeader("Cache-Control", "no-cache"); - response.setHeader("Content-Disposition", "inline; filename=qrcode.png"); - - // 输出图片 - response.getOutputStream().write(responseBytes); - System.out.println("二维码生成成功,大小: " + responseBytes.length + " bytes"); - - } catch (Exception e) { - System.err.println("生成二维码失败: " + e.getMessage()); - e.printStackTrace(); - - // 返回错误信息 - response.setContentType("application/json;charset=UTF-8"); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - response.getWriter().write("{\"error\":\"生成二维码失败: " + e.getMessage() + "\"}"); - } - } - - @Operation(summary = "获取微信小程序码-用户ID") - @GetMapping("/getQRCodeText") - public byte[] getQRCodeText(String scene, String page, Integer width, - Boolean isHyaline, String envVersion) throws IOException { - HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/wxa/getwxacodeunlimit") - .newBuilder() - .addQueryParameter("access_token", getLocalAccessToken()) - .build(); - - System.out.println("page = " + page); - // 构造请求 JSON - // 注意:scene 仅支持可见字符,长度上限 32,尽量 URL-safe(字母数字下划线等) - // page 必须是已发布小程序内的路径(不带开头斜杠也可) - var root = om.createObjectNode(); - root.put("scene", scene); - if (page != null) root.put("page", page); - if (width != null) root.put("width", width); // 默认 430,建议 280~1280 - if (isHyaline != null) root.put("is_hyaline", isHyaline); - if (envVersion != null) root.put("env_version", envVersion); // release/trial/develop - - okhttp3.RequestBody reqBody = okhttp3.RequestBody.create( - root.toString(), MediaType.parse("application/json; charset=utf-8")); - Request req = new Request.Builder().url(url).post(reqBody).build(); - - try (Response resp = http.newCall(req).execute()) { - if (!resp.isSuccessful()) { - throw new IOException("HTTP " + resp.code() + " calling getwxacodeunlimit"); - } - MediaType ct = resp.body().contentType(); - byte[] bytes = resp.body().bytes(); - // 微信出错时返回 JSON,需要识别一下 - if (ct != null && ct.subtype() != null && ct.subtype().contains("json")) { - String err = new String(bytes); - throw new IOException("WeChat error: " + err); - } - return bytes; // 成功就是图片二进制(PNG) - } - } - - /** - * 获取/刷新 access_token - */ - public String getLocalAccessToken() throws IOException { - long now = Instant.now().getEpochSecond(); - String key = "AccessToken:Local:" + getTenantId(); - - // 从缓存获取access_token,使用JSON格式 - String value = redisUtil.get(key); - if (value != null && now < tokenExpireEpoch - 60) { - try { - // 尝试解析为JSON格式 - JSONObject response = JSON.parseObject(value); - String accessToken = response.getString("access_token"); - if (accessToken != null) { - System.out.println("从缓存获取到access_token(Local): " + accessToken.substring(0, Math.min(10, accessToken.length())) + "..."); - return accessToken; - } - } catch (Exception e) { - // 如果解析失败,可能是旧格式的纯字符串token - System.out.println("本地缓存token格式异常,使用原值: " + e.getMessage()); - return value; - } - } - - // 从缓存获取微信小程序配置信息 - Integer tenantId = getTenantId(); - JSONObject setting = getWxConfigFromCache(tenantId); - if (setting == null) { - throw new IOException("请先配置小程序"); - } - - String appId = setting.getString("appId"); - String appSecret = setting.getString("appSecret"); - - if (appId == null || appSecret == null) { - throw new IOException("小程序配置不完整,缺少 appId 或 appSecret"); - } - - HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/cgi-bin/token") - .newBuilder() - .addQueryParameter("grant_type", "client_credential") - .addQueryParameter("appid", appId) - .addQueryParameter("secret", appSecret) - .build(); - - Request req = new Request.Builder().url(url).get().build(); - try (Response resp = http.newCall(req).execute()) { - String body = resp.body().string(); - JsonNode json = om.readTree(body); - if (json.has("access_token")) { - String token = json.get("access_token").asText(); - long expiresIn = json.get("expires_in").asInt(7200); - - // 缓存完整的JSON响应,与其他方法保持一致 - redisUtil.set(key, body, expiresIn, TimeUnit.SECONDS); - tokenExpireEpoch = now + expiresIn; - System.out.println("获取新的access_token成功(Local),租户ID: " + tenantId); - return token; - } else { - throw new IOException("Get access_token failed: " + body); - } - } - } - - /** - * 文件上传位置(服务器) - */ - private String getUploadDir() { - return config.getUploadPath(); - } - - @Operation(summary = "调试:检查微信小程序配置") - @GetMapping("/debug/checkWxConfig") - public ApiResult debugCheckWxConfig() { - Integer tenantId = getTenantId(); - Map result = new HashMap<>(); - result.put("tenantId", tenantId); - - try { - // 尝试从缓存获取配置 - JSONObject setting = getWxConfigFromCache(tenantId); - result.put("hasConfig", true); - result.put("config", setting); - result.put("cacheKey", MP_WX_KEY + tenantId); - } catch (Exception e) { - result.put("hasConfig", false); - result.put("error", e.getMessage()); - result.put("cacheKey", MP_WX_KEY + tenantId); - - // 提供创建配置的建议 - Map suggestion = new HashMap<>(); - suggestion.put("message", "请在Redis中创建微信小程序配置"); - suggestion.put("cacheKey", MP_WX_KEY + tenantId); - suggestion.put("tenantId", tenantId); - suggestion.put("sampleConfig", createSampleWxConfig()); - result.put("suggestion", suggestion); - } - - return success("配置检查完成", result); - } - - @Operation(summary = "调试:创建示例微信小程序配置") - @PostMapping("/debug/createSampleWxConfig") - public ApiResult debugCreateSampleWxConfig(@RequestBody Map params) { - Integer tenantId = getTenantId(); - - String appId = params.get("appId"); - String appSecret = params.get("appSecret"); - - if (appId == null || appSecret == null) { - return fail("请提供 appId 和 appSecret", null); - } - - try { - // 直接在Redis中创建配置 - String key = MP_WX_KEY + tenantId; - - // 创建配置内容 - Map config = new HashMap<>(); - config.put("appId", appId); - config.put("appSecret", appSecret); - config.put("tenantId", tenantId.toString()); - config.put("settingKey", "mp-weixin"); - config.put("settingId", "301"); - - // 保存到Redis缓存 - redisUtil.set(key, JSON.toJSONString(config)); - - return success("微信小程序配置创建成功", config); - } catch (Exception e) { - return fail("创建配置失败: " + e.getMessage(), null); - } - } - - private Map createSampleWxConfig() { - Map sample = new HashMap<>(); - sample.put("appId", "wx_your_app_id_here"); - sample.put("appSecret", "your_app_secret_here"); - return sample; - } - - @Operation(summary = "调试:获取AccessToken") - @GetMapping("/debug/getAccessToken") - public ApiResult debugGetAccessToken() { - try { - // 获取当前线程的租户ID - Integer tenantId = getTenantId(); - if (tenantId == null) { - tenantId = 10550; // 默认租户 - } - - System.out.println("=== 开始调试获取AccessToken,租户ID: " + tenantId + " ==="); - - // 手动调用获取AccessToken - String accessToken = getAccessTokenForTenant(tenantId); - - String result = "获取AccessToken成功: " + (accessToken != null ? accessToken.substring(0, Math.min(10, accessToken.length())) + "..." : "null"); - System.out.println("调试结果: " + result); - - return success(result); - } catch (Exception e) { - System.err.println("调试获取AccessToken异常: " + e.getMessage()); - e.printStackTrace(); - return fail("获取AccessToken失败: " + e.getMessage()); - } - } - - /** - * 从Redis缓存中获取微信小程序配置 - * @param tenantId 租户ID - * @return 微信配置信息 - */ - private JSONObject getWxConfigFromCache(Integer tenantId) { - String key = MP_WX_KEY + tenantId; - String cacheValue = redisUtil.get(key); - if (StrUtil.isBlank(cacheValue)) { - throw new BusinessException("未找到微信小程序配置,请检查缓存key: " + key); - } - try { - return JSON.parseObject(cacheValue); - } catch (Exception e) { - throw new BusinessException("微信小程序配置格式错误: " + e.getMessage()); - } - } - - /** - * 从scene参数中提取租户ID - * scene格式: uid_userId_tenantId(优先)或 uid_userId(兼容旧格式) - */ - private Integer extractTenantIdFromScene(String scene) { - try { - System.out.println("解析scene参数: " + scene); - - if (scene != null && scene.startsWith("uid_")) { - String content = scene.substring(4); // 去掉"uid_"前缀 - - // 优先解析 uid_userId_tenantId 格式 - String[] parts = content.split("_"); - if (parts.length >= 2) { - try { - Integer tenantId = Integer.parseInt(parts[1]); - System.out.println("从scene直接解析到tenantId = " + tenantId); - return tenantId; - } catch (NumberFormatException e) { - System.err.println("scene中tenantId格式异常: " + parts[1]); - } - } - - // 兼容旧格式 uid_userId:根据用户ID查询租户ID - if (parts.length == 1) { - Integer userId = Integer.parseInt(parts[0]); - System.out.println("userId = " + userId); - try { - List users = userService.listByIdIgnoreTenant(userId); - System.out.println("查询到用户数量 = " + (users != null ? users.size() : 0)); - if (users != null && !users.isEmpty()) { - System.out.println("从用户ID " + userId + " 获取到租户ID: " + users.get(0).getTenantId()); - return users.get(0).getTenantId(); - } else { - System.err.println("未找到用户ID: " + userId); - } - } catch (Exception ex) { - System.err.println("查询用户异常: " + ex.getMessage()); - } - } - } - - // 如果无法解析,默认使用租户10550 - System.out.println("无法解析scene参数,使用默认租户ID: 10550"); - return 10550; - - } catch (Exception e) { - System.err.println("解析scene参数异常: " + e.getMessage()); - // 出现异常时,默认使用租户10550 - return 10550; - } - } - - /** - * 为指定租户获取AccessToken - */ - private String getAccessTokenForTenant(Integer tenantId) { - try { - String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId.toString()); - - // 从缓存获取access_token - String value = redisUtil.get(key); - if (value != null) { - try { - // 尝试解析为JSON格式(与getAccessToken方法保持一致) - JSONObject response = JSON.parseObject(value); - String accessToken = response.getString("access_token"); - if (accessToken != null) { - System.out.println("从缓存获取到access_token: " + accessToken.substring(0, Math.min(10, accessToken.length())) + "..."); - return accessToken; - } - } catch (Exception e) { - // 如果解析失败,可能是旧格式的纯字符串token,直接返回 - System.out.println("缓存token格式异常,使用原值: " + e.getMessage()); - return value; - } - } - - // 缓存中没有,重新获取 - JSONObject wxConfig = getWxConfigFromCache(tenantId); - String appId = wxConfig.getString("appId"); - String appSecret = wxConfig.getString("appSecret"); - - String apiUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret; - System.out.println("调用微信API获取token - 租户ID: " + tenantId + ", AppID: " + (appId != null ? appId.substring(0, Math.min(8, appId.length())) + "..." : "null")); - System.out.println("微信API请求URL: " + apiUrl.replaceAll("secret=[^&]*", "secret=***")); - String result = HttpUtil.get(apiUrl); - System.out.println("微信API响应: " + result); - JSONObject json = JSON.parseObject(result); - - // 检查是否有错误 - if (json.containsKey("errcode")) { - Integer errcode = json.getInteger("errcode"); - String errmsg = json.getString("errmsg"); - System.err.println("微信API错误 - errcode: " + errcode + ", errmsg: " + errmsg); - - if (errcode == 40125) { - throw new RuntimeException("微信AppSecret配置错误,请检查并更新正确的AppSecret"); - } else if (errcode == 40013) { - throw new RuntimeException("微信AppID配置错误,请检查并更新正确的AppID"); - } else { - throw new RuntimeException("微信API调用失败: " + errmsg + " (errcode: " + errcode + ")"); - } - } - - if (json.containsKey("access_token")) { - String accessToken = json.getString("access_token"); - Integer expiresIn = json.getInteger("expires_in"); - - // 缓存access_token,存储完整JSON响应(与getAccessToken方法保持一致) - redisUtil.set(key, result, (long) (expiresIn - 300), TimeUnit.SECONDS); - - System.out.println("获取新的access_token成功,租户ID: " + tenantId); - return accessToken; - } else { - throw new RuntimeException("获取access_token失败: " + result); - } - - } catch (Exception e) { - System.err.println("获取access_token异常,租户ID: " + tenantId + ", 错误: " + e.getMessage()); - throw new RuntimeException("获取access_token失败: " + e.getMessage()); - } - } - -} +// } catch (Exception ex) { +// System.err.println("查询用户异常: " + ex.getMessage()); +// } +// } +// } +// +// // 如果无法解析,默认使用租户10550 +// System.out.println("无法解析scene参数,使用默认租户ID: 10550"); +// return 10550; +// +// } catch (Exception e) { +// System.err.println("解析scene参数异常: " + e.getMessage()); +// // 出现异常时,默认使用租户10550 +// return 10550; +// } +// } +// +// /** +// * 为指定租户获取AccessToken +// */ +// private String getAccessTokenForTenant(Integer tenantId) { +// try { +// String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId.toString()); +// +// // 从缓存获取access_token +// String value = redisUtil.get(key); +// if (value != null) { +// try { +// // 尝试解析为JSON格式(与getAccessToken方法保持一致) +// JSONObject response = JSON.parseObject(value); +// String accessToken = response.getString("access_token"); +// if (accessToken != null) { +// System.out.println("从缓存获取到access_token: " + accessToken.substring(0, Math.min(10, accessToken.length())) + "..."); +// return accessToken; +// } +// } catch (Exception e) { +// // 如果解析失败,可能是旧格式的纯字符串token,直接返回 +// System.out.println("缓存token格式异常,使用原值: " + e.getMessage()); +// return value; +// } +// } +// +// // 缓存中没有,重新获取 +// JSONObject wxConfig = getWxConfigFromCache(tenantId); +// String appId = wxConfig.getString("appId"); +// String appSecret = wxConfig.getString("appSecret"); +// +// String apiUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret; +// System.out.println("调用微信API获取token - 租户ID: " + tenantId + ", AppID: " + (appId != null ? appId.substring(0, Math.min(8, appId.length())) + "..." : "null")); +// System.out.println("微信API请求URL: " + apiUrl.replaceAll("secret=[^&]*", "secret=***")); +// String result = HttpUtil.get(apiUrl); +// System.out.println("微信API响应: " + result); +// JSONObject json = JSON.parseObject(result); +// +// // 检查是否有错误 +// if (json.containsKey("errcode")) { +// Integer errcode = json.getInteger("errcode"); +// String errmsg = json.getString("errmsg"); +// System.err.println("微信API错误 - errcode: " + errcode + ", errmsg: " + errmsg); +// +// if (errcode == 40125) { +// throw new RuntimeException("微信AppSecret配置错误,请检查并更新正确的AppSecret"); +// } else if (errcode == 40013) { +// throw new RuntimeException("微信AppID配置错误,请检查并更新正确的AppID"); +// } else { +// throw new RuntimeException("微信API调用失败: " + errmsg + " (errcode: " + errcode + ")"); +// } +// } +// +// if (json.containsKey("access_token")) { +// String accessToken = json.getString("access_token"); +// Integer expiresIn = json.getInteger("expires_in"); +// +// // 缓存access_token,存储完整JSON响应(与getAccessToken方法保持一致) +// redisUtil.set(key, result, (long) (expiresIn - 300), TimeUnit.SECONDS); +// +// System.out.println("获取新的access_token成功,租户ID: " + tenantId); +// return accessToken; +// } else { +// throw new RuntimeException("获取access_token失败: " + result); +// } +// +// } catch (Exception e) { +// System.err.println("获取access_token异常,租户ID: " + tenantId + ", 错误: " + e.getMessage()); +// throw new RuntimeException("获取access_token失败: " + e.getMessage()); +// } +// } +// +//} diff --git a/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java b/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java index 10f33ef..0b5091c 100644 --- a/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java +++ b/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java @@ -2,11 +2,11 @@ package com.gxwebsoft.glt.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; -import com.gxwebsoft.common.core.annotation.IgnoreTenant; import com.gxwebsoft.glt.entity.GltTicketTemplate; import com.gxwebsoft.glt.entity.GltUserTicket; import com.gxwebsoft.glt.entity.GltUserTicketLog; import com.gxwebsoft.glt.entity.GltUserTicketRelease; +import com.gxwebsoft.glt.task.DealerOrderSettlement10584Task; import com.gxwebsoft.shop.entity.ShopOrder; import com.gxwebsoft.shop.entity.ShopOrderGoods; import com.gxwebsoft.shop.service.ShopOrderGoodsService; @@ -15,7 +15,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.springframework.scheduling.annotation.Async; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionTemplate; @@ -56,6 +55,7 @@ public class GltTicketIssueService { private final GltUserTicketReleaseService gltUserTicketReleaseService; private final GltUserTicketLogService gltUserTicketLogService; private final TransactionTemplate transactionTemplate; + private final DealerOrderSettlement10584Task dealerOrderSettlement; /** * 扫描“今日订单”,执行套票发放。 @@ -140,7 +140,10 @@ public class GltTicketIssueService { //1.发送水票 suerTicketRelease(orderNo, tenantId); - //2.优惠券扣减、积分发送、消息通知 + //2.执行分销员分销、统计门店/服务商分销业务 + dealerOrderSettlement.orderSettlement(orderNo); + + //3.执行平台分红业务 TODO 待开发 } @@ -149,6 +152,7 @@ public class GltTicketIssueService { * @param orderNo 订单号 * @param tenantId 租户ID */ + @Transactional public void suerTicketRelease(String orderNo, Integer tenantId){ //1.订单为空跳过执行 ShopOrder shopOrder = shopOrderService.getByOrderNo(orderNo, tenantId); diff --git a/src/main/java/com/gxwebsoft/glt/service/impl/GltTicketOrderServiceImpl.java b/src/main/java/com/gxwebsoft/glt/service/impl/GltTicketOrderServiceImpl.java index 519d576..d6e24cb 100644 --- a/src/main/java/com/gxwebsoft/glt/service/impl/GltTicketOrderServiceImpl.java +++ b/src/main/java/com/gxwebsoft/glt/service/impl/GltTicketOrderServiceImpl.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.enums.ShopDealerCapitalUpdateEnum; +import com.gxwebsoft.common.core.enums.ShopDealerTypeEnum; import com.gxwebsoft.common.core.exception.BusinessException; import com.gxwebsoft.common.core.web.PageParam; import com.gxwebsoft.common.core.web.PageResult; @@ -19,6 +21,7 @@ import com.gxwebsoft.glt.param.GltTicketOrderParam; import com.gxwebsoft.glt.service.GltTicketOrderService; import com.gxwebsoft.glt.service.GltUserTicketLogService; import com.gxwebsoft.glt.service.GltUserTicketService; +import com.gxwebsoft.shop.dto.ShopDealerUserReduceDto; import com.gxwebsoft.shop.entity.*; import com.gxwebsoft.shop.mapper.ShopUserAddressMapper; import com.gxwebsoft.shop.service.ShopDealerCapitalService; @@ -765,6 +768,25 @@ public class GltTicketOrderServiceImpl extends ServiceImpl orderLambdaQueryWrapper; + if(shopOrderId != null){ + orderLambdaQueryWrapper = new LambdaQueryWrapper().eq(ShopOrder::getOrderId, shopOrderId).eq(ShopOrder::getOrderStatus, 0); + }else{ + orderLambdaQueryWrapper = new LambdaQueryWrapper().eq(ShopOrder::getOrderId, shopOrderNo).eq(ShopOrder::getOrderStatus, 0); + } + ShopOrder order = shopOrderService.getOne(orderLambdaQueryWrapper); + if(order != null){ + ShopDealerUserReduceDto reduceDto = new ShopDealerUserReduceDto(); + reduceDto.setTypeEnum(ShopDealerTypeEnum.DEFROST); + reduceDto.setOrderUserId(order.getUserId()); + reduceDto.setOrderNo(order.getOrderNo()); + reduceDto.setUpdateEnum(ShopDealerCapitalUpdateEnum.FREEZE_MONEY_THAW); + + //按订单号资金解冻 + shopDealerUserService.reduceBalance(reduceDto); + } + LambdaUpdateWrapper uw = new LambdaUpdateWrapper() .eq(ShopOrder::getTenantId, tenantId) .eq(ShopOrder::getDeleted, 0) @@ -776,7 +798,6 @@ public class GltTicketOrderServiceImpl extends ServiceImpl waterFormIds = loadWaterFormIds(); + + //查询商品列表存在已支付未核销订单数据 List orders = findUnsettledPaidOrders(waterFormIds); if (orders.isEmpty()) { return; @@ -108,7 +112,11 @@ public class DealerOrderSettlement10584Task { // Per-run caches to reduce DB chatter across orders. Map level1ParentCache = new HashMap<>(); Map shopRoleCache = new HashMap<>(); + + //获取系统设置分销等级 DealerBasicSetting dealerBasicSetting = findDealerBasicSetting(); + + //获取分销员type=2第一个分销人作为平台总分红人 ShopDealerUser totalDealerUser = findTotalDealerUser(); if (totalDealerUser == null || totalDealerUser.getUserId() == null) { log.warn("未找到分红账号,订单仍可结算但不会发放分红 - tenantId={}", TENANT_ID); @@ -124,6 +132,7 @@ public class DealerOrderSettlement10584Task { try { transactionTemplate.executeWithoutResult(status -> { // 先“认领”订单:并发/多实例下避免重复结算(update=0 表示被其他线程/实例处理) + //更新商品订单为已结算状态 if (!claimOrderToSettle(order.getOrderId(), waterFormIds)) { return; } @@ -138,6 +147,88 @@ public class DealerOrderSettlement10584Task { } } + /** + * 每10分钟执行一次。 + */ + @Scheduled(cron = "0 0/10 * * * ?") + @IgnoreTenant("该定时任务仅处理租户10584,但需要显式按tenantId过滤,避免定时任务线程无租户上下文导致查询异常") + public void settleTenant10584OrdersV2() { + try { + //获取水票模板对应的商品信息列表 + Set waterFormIds = loadWaterFormIds(); + + //查询商品列表存在已支付未核销订单数据【isSettled = 0, payStatus= 1, orderStatus 不在列表(2, 3, 4, 5, 6, 7)】 + List orders = findUnsettledPaidOrders(waterFormIds); + if (orders.isEmpty()) { + return; + } + + // Per-run caches to reduce DB chatter across orders. + Map level1ParentCache = new HashMap<>(); + Map shopRoleCache = new HashMap<>(); + + //获取系统设置分销等级 + DealerBasicSetting dealerBasicSetting = findDealerBasicSetting(); + + //获取分销员type=2第一个分销人作为平台总分红人 + ShopDealerUser totalDealerUser = findTotalDealerUser(); + if (totalDealerUser == null || totalDealerUser.getUserId() == null) { + log.warn("未找到分红账号,订单仍可结算但不会发放分红 - tenantId={}", TENANT_ID); + } + log.debug("租户{}分销设置 - level={}", TENANT_ID, dealerBasicSetting.level); + + log.info("租户{}待结算订单数: {}, orderNos(sample)={}", + TENANT_ID, + orders.size(), + orders.stream().limit(10).map(ShopOrder::getOrderNo).toList()); + + for (ShopOrder order : orders) { + try { + transactionTemplate.executeWithoutResult(status -> { + // 先“认领”订单:并发/多实例下避免重复结算(update=0 表示被其他线程/实例处理) + //更新商品订单为已结算状态 + if (!claimOrderToSettle(order.getOrderId(), waterFormIds)) { + return; + } + settleOneOrderV2(order, level1ParentCache, shopRoleCache, totalDealerUser, dealerBasicSetting.level); + }); + } catch (Exception e) { + log.error("订单结算失败,将回滚本订单并在下次任务重试 - orderId={}, orderNo={}", order.getOrderId(), order.getOrderNo(), e); + } + } + } catch (Exception e) { + log.error("租户{}分销订单结算任务执行失败", TENANT_ID, e); + } + } + + /** + * 订单分销金、分润金结算 + */ + @Transactional + public void orderSettlement(String orderNo){ + LambdaQueryWrapper orderLambdaQueryWrapper = new LambdaQueryWrapper().eq(ShopOrder::getOrderNo, orderNo).eq(ShopOrder::getIsSettled, 0).eq(ShopOrder::getPayStatus, 1); + ShopOrder order = shopOrderService.getOne(orderLambdaQueryWrapper); + if(order != null){ + //获取系统设置分销等级 + DealerBasicSetting dealerBasicSetting = findDealerBasicSetting(); + + //获取分销员type=2第一个分销人作为平台总分红人 + ShopDealerUser totalDealerUser = findTotalDealerUser(); + + Map level1ParentCache = new HashMap<>(); + Map shopRoleCache = new HashMap<>(); + + transactionTemplate.executeWithoutResult(status -> { + // 先“认领”订单:并发/多实例下避免重复结算(update=0 表示被其他线程/实例处理) + //更新商品订单为已结算状态 + if (!claimOrderToSettleV2(order.getOrderId())) { + return; + } + settleOneOrderV2(order, level1ParentCache, shopRoleCache, totalDealerUser, dealerBasicSetting.level); + }); + } + } + private List findUnsettledPaidOrders(Set waterFormIds) { // 租户10584约定: // - 普通订单:以发货为准(deliveryStatus=20)才结算; @@ -148,7 +239,7 @@ public class DealerOrderSettlement10584Task { .eq(ShopOrder::getPayStatus, true) .eq(ShopOrder::getIsSettled, 0) // 退款/取消订单不结算,避免“退款后仍发放分红/分润/佣金” - .and(w -> w.notIn(ShopOrder::getOrderStatus, 2, 4, 5, 6, 7).or().isNull(ShopOrder::getOrderStatus)); + .and(w -> w.notIn(ShopOrder::getOrderStatus, 2, 3, 4, 5, 6, 7).or().isNull(ShopOrder::getOrderStatus)); if (waterFormIds != null && !waterFormIds.isEmpty()) { qw.and(w -> w.eq(ShopOrder::getDeliveryStatus, 20).or().in(ShopOrder::getFormId, waterFormIds)); @@ -178,6 +269,17 @@ public class DealerOrderSettlement10584Task { return shopOrderService.update(uw); } + private boolean claimOrderToSettleV2(Integer orderId) { + LambdaUpdateWrapper uw = new LambdaUpdateWrapper() + .eq(ShopOrder::getOrderId, orderId) + .eq(ShopOrder::getTenantId, TENANT_ID) + .eq(ShopOrder::getIsSettled, 0) + // 二次防御:退款/取消订单不允许被“认领结算” + .and(w -> w.notIn(ShopOrder::getOrderStatus, 2, 3, 4, 5, 6, 7).or().isNull(ShopOrder::getOrderStatus)); + uw.set(ShopOrder::getIsSettled, 1); + return shopOrderService.update(uw); + } + private Set loadWaterFormIds() { try { return gltTicketTemplateService.list( @@ -252,6 +354,45 @@ public class DealerOrderSettlement10584Task { log.info("订单结算完成 - orderId={}, orderNo={}, baseAmount={}", order.getOrderId(), order.getOrderNo(), baseAmount); } + private void settleOneOrderV2(ShopOrder order, Map level1ParentCache, Map shopRoleCache, + ShopDealerUser totalDealerUser, int dealerLevel) { + if (order.getUserId() == null || order.getOrderNo() == null) { + throw new IllegalStateException("订单关键信息缺失,无法结算 - orderId=" + order.getOrderId()); + } + + BigDecimal totalPrice = order.getTotalPrice(); + BigDecimal payPrice = order.getPayPrice(); + BigDecimal rate = payPrice.divide(totalPrice, 2, RoundingMode.HALF_UP); + + if(payPrice.compareTo(BigDecimal.ZERO) <= 0){ + log.info("订单号:{},实付金额为0,无需执行分销逻辑" + order.getOrderNo()); + return; + } + + //查询订单号订单所有已开启分销的商品分润信息 + List orderGoodsVOList = shopOrderMapper.getOrderGoodsInfo(order.getOrderNo()); + if(CollectionUtils.isNotEmpty(orderGoodsVOList)){ + // 1) 直推/间推(直接增加冻结账户余额) + DealerRefereeCommissionV2 dealerRefereeCommission = settleDealerRefereeCommissionV2(order, rate, orderGoodsVOList, dealerLevel); + + // 2) 门店分润上级:从下单用户开始逐级向上找,命中 ShopDealerUser.type=1 的最近两级(直推门店/间推门店)【只统计数据,不对分销账户进行处理, + // 已日结形式,统计分销记录表:shop_dealer_order 做对应一级二级管理津贴结算】 + ShopRoleCommission shopRoleCommission = settleShopRoleRefereeCommissionV2(order, rate, orderGoodsVOList, level1ParentCache, shopRoleCache); + + // 3) 分红:固定比率,每个订单都分 TODO 总分红未开发,还按原逻辑走 + int goodsQty = orderGoodsVOList.stream().mapToInt(ShopOrderGoodsVO::getTotalNum).sum(); + TotalDealerCommission totalDealerCommission = settleTotalDealerCommissionV2(order, goodsQty, totalDealerUser); + + // 4) 写入分销订单记录(用于排查/统计;详细分佣以 ShopDealerCapital 为准) + createDealerOrderRecordV2(order, dealerRefereeCommission, shopRoleCommission, totalDealerCommission); + + log.info("订单结算完成 - orderId={}, orderNo={}, baseAmount={}", order.getOrderId(), order.getOrderNo(), payPrice); + }else { + log.error("订单号:{},未找到下单分销商品数据!", order.getOrderNo()); + return; + } + } + private DealerRefereeCommission settleDealerRefereeCommission( ShopOrder order, BigDecimal baseAmount, @@ -329,6 +470,85 @@ public class DealerOrderSettlement10584Task { return new DealerRefereeCommission(directDealerId, directMoney, simpleDealerId, simpleMoney, thirdDealerId, thirdMoney); } + /** + * 获取分销员分销霍金数据 + * @param order + * @param orderGoodsVOList + * @param dealerLevel + * @return + */ + private DealerRefereeCommissionV2 settleDealerRefereeCommissionV2(ShopOrder order, BigDecimal rate, List orderGoodsVOList, int dealerLevel) { + Integer directDealerId = null; + Integer simpleDealerId = null; + AtomicReference directMoney = new AtomicReference<>(BigDecimal.ZERO); + AtomicReference simpleMoney = new AtomicReference<>(BigDecimal.ZERO); + + + if (dealerLevel >= 1) { + Integer dealerId = shopDealerRefereeMapper.getDealerIdByUserId(order.getUserId()); + if(dealerId != null){ + directDealerId = dealerId; + } + } + if (dealerLevel >= 2 && directDealerId != null) { + Integer dealerId = shopDealerRefereeMapper.getDealerIdByUserId(directDealerId); + if(dealerId != null){ + simpleDealerId = dealerId; + } + } + + if(directDealerId != null || simpleDealerId != null){ + Integer finalDirectDealerId = directDealerId; + Integer finalSimpleDealerId = simpleDealerId; + orderGoodsVOList.forEach(orderGoodsVO -> { + //获取商品分润比例/金额 + BigDecimal firstMoney = orderGoodsVO.getFirstMoney(); + BigDecimal secondMoney = orderGoodsVO.getSecondMoney(); + + //按实付比例计算单项应参与分润金额 + BigDecimal itemRatePrice = orderGoodsVO.getPrice().multiply(BigDecimal.valueOf(orderGoodsVO.getTotalNum())).multiply(rate); + + //一级分销员存在(type = 0)且单项实付金额大于0及商品设置了一级分销比例/金额 + if(finalDirectDealerId != null && itemRatePrice.compareTo(BigDecimal.ZERO) > 0 && firstMoney.compareTo(BigDecimal.ZERO) > 0){ + BigDecimal one = calcMoneyByCommissionType(itemRatePrice, firstMoney, orderGoodsVO.getTotalNum(), 2, orderGoodsVO.getCommissionType()); + directMoney.accumulateAndGet(one, BigDecimal::add); + } + + //一级分销员存在(type = 0)且单项实付金额大于0及商品设置了一级分销比例/金额 + if(finalSimpleDealerId != null && itemRatePrice.compareTo(BigDecimal.ZERO) > 0 && secondMoney.compareTo(BigDecimal.ZERO) > 0 ){ + BigDecimal two = calcMoneyByCommissionType(itemRatePrice, secondMoney, orderGoodsVO.getTotalNum(), 2, orderGoodsVO.getCommissionType()); + simpleMoney.accumulateAndGet(two, BigDecimal::add); + } + }); + + //一级分销员账户增加冻结金额 + if (directDealerId != null && directMoney.get().compareTo(BigDecimal.ZERO) > 0) { + ShopDealerUserReduceDto reduceDto = new ShopDealerUserReduceDto(); + reduceDto.setTypeEnum(ShopDealerTypeEnum.FREEZE_ACCOUNT); + reduceDto.setUserId(directDealerId); + reduceDto.setOrderUserId(order.getUserId()); + reduceDto.setOrderNo(order.getOrderNo()); + reduceDto.setPrice(directMoney.get()); + reduceDto.setUpdateEnum(ShopDealerCapitalUpdateEnum.DISTRIBUTION_INCOME); + shopDealerUserService.reduceBalance(reduceDto); + } + + //二级分销员账户增加冻结金额 + if (simpleDealerId != null && simpleMoney.get().compareTo(BigDecimal.ZERO) > 0) { + ShopDealerUserReduceDto reduceDto = new ShopDealerUserReduceDto(); + reduceDto.setTypeEnum(ShopDealerTypeEnum.FREEZE_ACCOUNT); + reduceDto.setUserId(simpleDealerId); + reduceDto.setOrderUserId(order.getUserId()); + reduceDto.setOrderNo(order.getOrderNo()); + reduceDto.setPrice(simpleMoney.get()); + reduceDto.setUpdateEnum(ShopDealerCapitalUpdateEnum.DISTRIBUTION_INCOME); + shopDealerUserService.reduceBalance(reduceDto); + } + return new DealerRefereeCommissionV2(directDealerId, directMoney.get(), simpleDealerId, simpleMoney.get()); + } + return null; + } + private Integer getDealerRefereeId(Integer userId) { return getDealerRefereeId(userId, 1); } @@ -412,6 +632,54 @@ public class DealerOrderSettlement10584Task { return new ShopRoleCommission(shopRoleReferees.get(0), storeDirectMoney, shopRoleReferees.get(1), storeSimpleMoney); } + private ShopRoleCommission settleShopRoleRefereeCommissionV2(ShopOrder order, BigDecimal rate, List orderGoodsVOList, Map level1ParentCache, Map shopRoleCache) { + List shopRoleReferees = findFirstTwoShopRoleReferees(order.getUserId(), level1ParentCache, shopRoleCache); + log.info("门店分润命中结果(type=1门店角色取前两级) - orderNo={}, buyerUserId={}, shopRoleReferees={}", + order.getOrderNo(), order.getUserId(), shopRoleReferees); + if (shopRoleReferees.isEmpty()) { + return ShopRoleCommission.empty(); + } + + if(CollectionUtils.isNotEmpty(shopRoleReferees)){ + Integer storeDirectUserId; + Integer storeSimpleUserId = null; + AtomicReference storeDirectMoney = new AtomicReference<>(BigDecimal.ZERO); + AtomicReference storeSimpleMoney = new AtomicReference<>(BigDecimal.ZERO); + + if(shopRoleReferees.size() == 1){ + storeDirectUserId = shopRoleReferees.get(0); + }else { + storeDirectUserId = shopRoleReferees.get(0); + storeSimpleUserId = shopRoleReferees.get(1); + } + + + Integer finalStoreDirectUserId = storeDirectUserId; + Integer finalStoreSimpleUserId = storeSimpleUserId; + orderGoodsVOList.forEach(orderGoodsVO ->{ + //获取商品对应服务商管理费分润比例/金额 + BigDecimal firstMoney = orderGoodsVO.getFirstDividend(); + BigDecimal secondMoney = orderGoodsVO.getSecondDividend(); + + //按实付比例计算单项应参与分润金额 + BigDecimal itemRatePrice = orderGoodsVO.getPrice().multiply(BigDecimal.valueOf(orderGoodsVO.getTotalNum())).multiply(rate); + + if(finalStoreDirectUserId != null && itemRatePrice.compareTo(BigDecimal.ZERO) > 0){ + BigDecimal one = calcMoneyByCommissionType(itemRatePrice, firstMoney, orderGoodsVO.getTotalNum(), 2, orderGoodsVO.getCommissionType()); + storeDirectMoney.accumulateAndGet(one, BigDecimal::add); + } + + if(finalStoreSimpleUserId != null && itemRatePrice.compareTo(BigDecimal.ZERO) > 0){ + BigDecimal two = calcMoneyByCommissionType(itemRatePrice, secondMoney, orderGoodsVO.getTotalNum(), 2, orderGoodsVO.getCommissionType()); + storeSimpleMoney.accumulateAndGet(two, BigDecimal::add); + } + }); + return new ShopRoleCommission(storeDirectUserId, storeDirectMoney.get(), storeSimpleUserId, storeSimpleMoney.get()); + }else { + return null; + } + } + private TotalDealerCommission settleTotalDealerCommission( ShopOrder order, BigDecimal baseAmount, @@ -438,6 +706,32 @@ public class DealerOrderSettlement10584Task { return new TotalDealerCommission(totalDealerUser.getUserId(), money); } + private TotalDealerCommission settleTotalDealerCommissionV2(ShopOrder order, int goodsQty, ShopDealerUser totalDealerUser) { + if (totalDealerUser == null || totalDealerUser.getUserId() == null) { + return TotalDealerCommission.empty(); + } + BigDecimal rate = safePositive(totalDealerUser.getRate()); + if (rate.signum() <= 0) { + rate = TOTAL_DEALER_DIVIDEND_RATE; + } + BigDecimal money = calcMoneyByCommissionType(order.getPayPrice(), rate, goodsQty, DIVIDEND_SCALE, 20); + + //一级分销员账户增加冻结金额 + if (money.compareTo(BigDecimal.ZERO) > 0) { + ShopDealerUserReduceDto reduceDto = new ShopDealerUserReduceDto(); + reduceDto.setTypeEnum(ShopDealerTypeEnum.FREEZE_ACCOUNT); + reduceDto.setUserId(totalDealerUser.getUserId()); + reduceDto.setOrderUserId(order.getUserId()); + reduceDto.setOrderNo(order.getOrderNo()); + reduceDto.setPrice(money); + reduceDto.setUpdateEnum(ShopDealerCapitalUpdateEnum.DIVIDEND_INCOME); + shopDealerUserService.reduceBalance(reduceDto); + + return new TotalDealerCommission(totalDealerUser.getUserId(), money); + } + return TotalDealerCommission.empty(); + } + private ShopDealerUser findTotalDealerUser() { return shopDealerUserService.getOne( new LambdaQueryWrapper() @@ -756,6 +1050,92 @@ public class DealerOrderSettlement10584Task { order.getOrderNo(), dealerOrder.getFirstUserId(), dealerOrder.getSecondUserId(), dealerOrder.getFirstDividendUser(), dealerOrder.getSecondDividendUser()); } + /** + * 记录订单分销业务 + * @param order 商品订单 + * @param dealerRefereeCommission 一级、二级分销员分销数据 + * @param shopRoleCommission 门店/服务商一级、二级管理津贴数据 + */ + private void createDealerOrderRecordV2(ShopOrder order, DealerRefereeCommissionV2 dealerRefereeCommission, ShopRoleCommission shopRoleCommission, TotalDealerCommission totalDealerCommission) { + // 幂等:同一订单只写一条(依赖 order_no + tenant_id 作为业务唯一) + ShopDealerOrder existed = shopDealerOrderService.getOne( + new LambdaQueryWrapper() + .eq(ShopDealerOrder::getTenantId, TENANT_ID) + .eq(ShopDealerOrder::getOrderNo, order.getOrderNo()) + .last("limit 1") + ); + if (existed != null) { + // 允许“补发”门店分润时回填分润字段,避免订单已结算但分润字段一直为空,影响排查/对账。 + LambdaUpdateWrapper uw = new LambdaUpdateWrapper() + .eq(ShopDealerOrder::getTenantId, TENANT_ID) + .eq(ShopDealerOrder::getOrderNo, order.getOrderNo()); + boolean needUpdate = false; + if (shopRoleCommission != null && shopRoleCommission.storeDirectUserId != null) { + Integer existedUser = existed.getFirstDividendUser(); + boolean needSetUser = existedUser == null; + boolean needSetMoney = existed.getFirstDividend() == null || existed.getFirstDividend().signum() == 0; + if (needSetUser) { + uw.set(ShopDealerOrder::getFirstDividendUser, shopRoleCommission.storeDirectUserId); + needUpdate = true; + } + boolean sameUser = existedUser == null || Objects.equals(existedUser, shopRoleCommission.storeDirectUserId); + if (sameUser && needSetMoney && shopRoleCommission.storeDirectMoney != null && shopRoleCommission.storeDirectMoney.signum() > 0) { + uw.set(ShopDealerOrder::getFirstDividend, shopRoleCommission.storeDirectMoney); + needUpdate = true; + } + } + if (shopRoleCommission != null && shopRoleCommission.storeSimpleUserId != null) { + Integer existedUser = existed.getSecondDividendUser(); + boolean needSetUser = existedUser == null; + boolean needSetMoney = existed.getSecondDividend() == null || existed.getSecondDividend().signum() == 0; + if (needSetUser) { + uw.set(ShopDealerOrder::getSecondDividendUser, shopRoleCommission.storeSimpleUserId); + needUpdate = true; + } + boolean sameUser = existedUser == null || Objects.equals(existedUser, shopRoleCommission.storeSimpleUserId); + if (sameUser && needSetMoney && shopRoleCommission.storeSimpleMoney != null && shopRoleCommission.storeSimpleMoney.signum() > 0) { + uw.set(ShopDealerOrder::getSecondDividend, shopRoleCommission.storeSimpleMoney); + needUpdate = true; + } + } + if (needUpdate) { + shopDealerOrderService.update(uw); + log.info("ShopDealerOrder已存在,回填门店分润字段 - orderNo={}, firstDividendUser={}, secondDividendUser={}", + order.getOrderNo(), shopRoleCommission.storeDirectUserId, shopRoleCommission.storeSimpleUserId); + } else { + log.info("ShopDealerOrder已存在,跳过写入 - orderNo={}", order.getOrderNo()); + } + return; + }else { + + ShopDealerOrder dealerOrder = new ShopDealerOrder(); + dealerOrder.setUserId(order.getUserId()); // 买家用户ID + dealerOrder.setOrderNo(order.getOrderNo()); + dealerOrder.setOrderPrice(order.getTotalPrice()); + dealerOrder.setPayPrice(order.getPayPrice()); + + //一级、二级分销员分销佣金统计 + dealerOrder.setFirstUserId(dealerRefereeCommission != null ? dealerRefereeCommission.directDealerId : null); + dealerOrder.setFirstMoney(dealerRefereeCommission != null ? dealerRefereeCommission.directMoney : BigDecimal.ZERO); + dealerOrder.setSecondUserId(dealerRefereeCommission != null ? dealerRefereeCommission.simpleDealerId : null); + dealerOrder.setSecondMoney(dealerRefereeCommission != null ? dealerRefereeCommission.simpleMoney : BigDecimal.ZERO); + + //门店(角色shop)两级分润单独落字段(详细以 ShopDealerCapital 为准) + dealerOrder.setFirstDividendUser(shopRoleCommission != null ? shopRoleCommission.storeDirectUserId : null); + dealerOrder.setFirstDividend(shopRoleCommission != null ? shopRoleCommission.storeDirectMoney : BigDecimal.ZERO); + dealerOrder.setSecondDividendUser(shopRoleCommission != null ? shopRoleCommission.storeSimpleUserId : null); + dealerOrder.setSecondDividend(shopRoleCommission != null ? shopRoleCommission.storeSimpleMoney : BigDecimal.ZERO); + + dealerOrder.setIsSettled(1); + dealerOrder.setSettleTime(LocalDateTime.now()); + dealerOrder.setMonth(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM"))); + dealerOrder.setTenantId(TENANT_ID); + dealerOrder.setComments(buildCommissionTraceCommentV2(dealerRefereeCommission, shopRoleCommission, totalDealerCommission)); + + shopDealerOrderService.save(dealerOrder); + } + } + private String buildCommissionTraceComment( DealerRefereeCommission dealerRefereeCommission, ShopRoleCommission shopRoleCommission, @@ -770,6 +1150,30 @@ public class DealerOrderSettlement10584Task { + ",totalDealer=" + totalDealerCommission.userId + ":" + totalDealerCommission.money; } + private String buildCommissionTraceCommentV2( + DealerRefereeCommissionV2 dealerRefereeCommission, + ShopRoleCommission shopRoleCommission, + TotalDealerCommission totalDealerCommission + ) { + // 轻量“过程”留痕,方便排查;详细分佣以 ShopDealerCapital 为准。 + Integer direct = dealerRefereeCommission != null ? dealerRefereeCommission.directDealerId : null; + BigDecimal directMoney = dealerRefereeCommission != null ? dealerRefereeCommission.directMoney : BigDecimal.ZERO; + Integer simpleDealerId = dealerRefereeCommission != null ? dealerRefereeCommission.simpleDealerId : null; + BigDecimal simpleMoney = dealerRefereeCommission != null ? dealerRefereeCommission.simpleMoney : BigDecimal.ZERO; + Integer storeDirectUserId = shopRoleCommission != null ? shopRoleCommission.storeDirectUserId : null; + BigDecimal storeDirectMoney = shopRoleCommission != null ? shopRoleCommission.storeDirectMoney : BigDecimal.ZERO; + Integer storeSimpleUserId = shopRoleCommission != null ? shopRoleCommission.storeSimpleUserId : null; + BigDecimal storeSimpleMoney = shopRoleCommission != null ? shopRoleCommission.storeSimpleMoney : BigDecimal.ZERO; + Integer userId = totalDealerCommission != null ? totalDealerCommission.userId : null; + BigDecimal money = totalDealerCommission != null ? totalDealerCommission.money : BigDecimal.ZERO; + + return "direct=" + direct + ":" + directMoney + + ",simple=" + simpleDealerId + ":" + simpleMoney + + ",dividend1=" + storeDirectUserId + ":" + storeDirectMoney + + ",dividend2=" + storeSimpleUserId + ":" + storeSimpleMoney + + ",totalDealer=" + userId + ":" + money; + } + private BigDecimal getOrderBaseAmount(ShopOrder order) { if (order == null) { return null; @@ -962,6 +1366,25 @@ public class DealerOrderSettlement10584Task { } } + private static class DealerRefereeCommissionV2 { + private final Integer directDealerId; + private final BigDecimal directMoney; + private final Integer simpleDealerId; + private final BigDecimal simpleMoney; + + private DealerRefereeCommissionV2( + Integer directDealerId, + BigDecimal directMoney, + Integer simpleDealerId, + BigDecimal simpleMoney + ) { + this.directDealerId = directDealerId; + this.directMoney = directMoney != null ? directMoney : BigDecimal.ZERO; + this.simpleDealerId = simpleDealerId; + this.simpleMoney = simpleMoney != null ? simpleMoney : BigDecimal.ZERO; + } + } + private static class ShopRoleCommission { private final Integer storeDirectUserId; private final BigDecimal storeDirectMoney; diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopDealerOrder.java b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerOrder.java index 43bbff8..52cf35a 100644 --- a/src/main/java/com/gxwebsoft/shop/entity/ShopDealerOrder.java +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerOrder.java @@ -7,6 +7,8 @@ import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import java.time.LocalDateTime; + +import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonFormat; import java.io.Serializable; @@ -23,6 +25,7 @@ import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = false) @Schema(name = "ShopDealerOrder对象", description = "分销商订单记录表") +@TableName("shop_dealer_order") public class ShopDealerOrder implements Serializable { private static final long serialVersionUID = 1L; @@ -66,6 +69,9 @@ public class ShopDealerOrder implements Serializable { @TableField(exist = false) private String firstNickname; + @Schema(description = "分销佣金(一级)") + private BigDecimal firstMoney; + @Schema(description = "分销商用户id(二级)") private Integer secondUserId; @@ -73,42 +79,57 @@ public class ShopDealerOrder implements Serializable { @TableField(exist = false) private String secondNickname; - @Schema(description = "分销商用户id(三级)") - private Integer thirdUserId; - - @Schema(description = "分销商用户昵称(三级)") - @TableField(exist = false) - private String thirdNickname; - - @Schema(description = "分销佣金(一级)") - private BigDecimal firstMoney; - @Schema(description = "分销佣金(二级)") private BigDecimal secondMoney; - @Schema(description = "分销佣金(三级)") + @Schema(description = "分销商用户id(弃用)") + private Integer thirdUserId; + + @Schema(description = "分销商用户昵称(弃用)") + @TableField(exist = false) + private String thirdNickname; + + @Schema(description = "分销佣金(弃用)") private BigDecimal thirdMoney; - @Schema(description = "门店(一级)") + @Schema(description = "一级服务商/门店") private Integer firstDividendUser; - @Schema(description = "门店名称(一级)") + @Schema(description = "一级服务商/门店名称") @TableField(exist = false) private String firstDividendUserName; - @Schema(description = "分红(一级)") + @Schema(description = "一级服务商/门店管理津贴") private BigDecimal firstDividend; - @Schema(description = "门店(二级)") + @Schema(description = "一级服务商/门店结算标识 0-否 1-是") + private Integer firstDividendFlag; + + @Schema(description = "一级服务商/门店结算单号") + private String firstDividendNo; + + @Schema(description = "一级服务商/门店结算时间") + private LocalDateTime firstDividendTime; + + @Schema(description = "二级服务商/门店") private Integer secondDividendUser; - @Schema(description = "门店名称(二级)") + @Schema(description = "二级服务商/门店名称") @TableField(exist = false) private String secondDividendUserName; - @Schema(description = "分红(二级)") + @Schema(description = "二级服务商/门店管理津贴") private BigDecimal secondDividend; + @Schema(description = "二级服务商/门店结算标识 0-否 1-是") + private Integer secondDividendFlag; + + @Schema(description = "二级服务商/门店结算单号") + private String secondDividendNo; + + @Schema(description = "二级服务商/门店结算时间") + private LocalDateTime secondDividendTime; + @Schema(description = "佣金比例") private BigDecimal rate; diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerRefereeMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerRefereeMapper.java index 70ddf37..7c9c491 100644 --- a/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerRefereeMapper.java +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopDealerRefereeMapper.java @@ -34,4 +34,11 @@ public interface ShopDealerRefereeMapper extends BaseMapper { */ List selectListRel(@Param("param") ShopDealerRefereeParam param); + /** + * 通过用户ID查询上一级分销人信息【type = 0】 + * @param userId 用户ID + * @return + */ + Integer getDealerIdByUserId(@Param("userId") Integer userId); + } diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderMapper.java index e36a492..b905d29 100644 --- a/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderMapper.java +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopOrderMapper.java @@ -5,9 +5,9 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.gxwebsoft.shop.entity.ShopOrder; import com.gxwebsoft.shop.param.ShopOrderParam; +import com.gxwebsoft.shop.vo.ShopOrderGoodsVO; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; -import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.List; @@ -60,4 +60,11 @@ public interface ShopOrderMapper extends BaseMapper { Map selectUserOrderStats(@Param("userId") Integer userId, @Param("tenantId") Integer tenantId, @Param("type") Integer type); + + /** + * 通过订单号查询订单所有商品分润信息 + * @param orderNo 订单号 + * @return + */ + List getOrderGoodsInfo(@Param("orderNo") String orderNo); } diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerRefereeMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerRefereeMapper.xml index a8635f1..ac748e2 100644 --- a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerRefereeMapper.xml +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopDealerRefereeMapper.xml @@ -57,5 +57,16 @@ + diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderMapper.xml index c2fd775..6a239e5 100644 --- a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderMapper.xml +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopOrderMapper.xml @@ -320,6 +320,28 @@ + diff --git a/src/main/java/com/gxwebsoft/shop/vo/ShopOrderGoodsVO.java b/src/main/java/com/gxwebsoft/shop/vo/ShopOrderGoodsVO.java new file mode 100644 index 0000000..1446162 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/vo/ShopOrderGoodsVO.java @@ -0,0 +1,47 @@ +package com.gxwebsoft.shop.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 订单商品信息 + * @author xm + * @since 2025-01-12 + */ +@Data +@Schema(description = "订单商品信息") +public class ShopOrderGoodsVO { + + @Schema(description = "订单号") + private String orderNo; + + @Schema(description = "商品ID") + private Integer goodsId; + + @Schema(description = "品名") + private String name; + + @Schema(description = "购买数量") + private Integer totalNum; + + @Schema(description = "单价") + private BigDecimal price; + + @Schema(description = "结算方式 10按金额 20按比率") + private Integer commissionType; + + @Schema(description = "一级分销员佣金比例/金额") + private BigDecimal firstMoney; + + @Schema(description = "二级分销员佣金比例/金额") + private BigDecimal secondMoney; + + @Schema(description = "一级服务商管理津贴比例/金额") + private BigDecimal firstDividend; + + @Schema(description = "二级服务商管理津贴比例/金额") + private BigDecimal secondDividend; + +}