Просмотр исходного кода

Merge branch 'master' of http://115.29.66.169:10080/yiyao_shop/api

lubo месяцев назад: 4
Родитель
Сommit
6416434c26

+ 56 - 0
ruoyi-api/src/main/java/com/ruoyi/api/controller/shop/ApiBusinessIntegralVerifierController.java

@@ -0,0 +1,56 @@
+package com.ruoyi.api.controller.shop;
+
+import com.ruoyi.api.controller.common.AbstractApiController;
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.enums.FilePathSplicingType;
+import com.ruoyi.common.filepathsplicing.FilePathSplicing;
+import com.ruoyi.common.utils.redis.RedisUtils;
+import com.ruoyi.shop.business.domain.bo.UserBusinessVerifierIntegralBo;
+import com.ruoyi.shop.business.service.IUserBusinessRoleService;
+import com.ruoyi.user.domain.vo.UserVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 商家积分核销
+ *
+ * @author ruoyi
+ * @date 2025-10-16
+ */
+@Validated
+@Api(value = "商家积分核销", tags = {"商家积分核销"})
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/api/business/integralVerifier")
+public class ApiBusinessIntegralVerifierController extends AbstractApiController {
+
+    private final IUserBusinessRoleService userBusinessRoleService;
+
+    /**
+     * 会员码查用户信息
+     */
+    @FilePathSplicing(type = FilePathSplicingType.RESPONSE)
+    @ApiOperation("会员码查用户信息")
+    @GetMapping("/queryUserInfo/{code}")
+    public R<UserVo> queryUserInfo(@PathVariable String code) {
+        Long userId = RedisUtils.getCacheObject(Constants.MEMBER_CODE + code);
+        return R.ok(userBusinessRoleService.queryUserInfo(userId, getBusinessByUserId(getUserId(true), true)));
+    }
+
+    /**
+     * 积分核销
+     */
+    @ApiOperation("积分核销")
+    @PostMapping("/verify")
+    @RepeatSubmit()
+    public R<Void> verify(@Validated @RequestBody UserBusinessVerifierIntegralBo bo) {
+        bo.setBusinessId(getBusinessByUserId(getUserId(true), true));
+        userBusinessRoleService.verify(bo);
+        return R.ok();
+    }
+}

+ 22 - 1
ruoyi-api/src/main/java/com/ruoyi/api/controller/user/ApiUserController.java

@@ -1,8 +1,8 @@
 package com.ruoyi.api.controller.user;
 
 import cn.hutool.core.util.IdUtil;
-import cn.hutool.core.util.ObjectUtil;
 import com.ruoyi.api.controller.common.AbstractApiController;
+import com.ruoyi.common.annotation.RepeatSubmit;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.validate.AddGroup;
 import com.ruoyi.common.core.validate.EditGroup;
@@ -14,6 +14,7 @@ import com.ruoyi.system.domain.SysOss;
 import com.ruoyi.system.service.ISysOssService;
 import com.ruoyi.user.domain.User;
 import com.ruoyi.user.domain.bo.*;
+import com.ruoyi.user.domain.vo.MemberCodeVo;
 import com.ruoyi.user.domain.vo.UserBindingVo;
 import com.ruoyi.user.domain.vo.UserVo;
 import com.ruoyi.user.mapper.UserMapper;
@@ -155,6 +156,26 @@ public class ApiUserController extends AbstractApiController {
         return R.ok();
     }
 
+    /**
+     * 生成会员码和对应二维码
+     */
+    @ApiOperation("生成会员码和对应二维码")
+    @PostMapping("/getMemberCode")
+    @RepeatSubmit()
+    public R<MemberCodeVo> getMemberCode() {
+        return R.ok(iUserService.generateMemberCode(getUserId(true), false));
+    }
+
+    /**
+     * 刷新二维码
+     */
+    @ApiOperation("刷新二维码")
+    @PostMapping("/refreshQrCode")
+    @RepeatSubmit()
+    public R<MemberCodeVo> refreshQrCode() {
+        return R.ok(iUserService.generateMemberCode(getUserId(true), true));
+    }
+
 
     private String getSceneType(WxMinQrCode wxMinQrCode) {
         switch (wxMinQrCode.getSceneType()){

+ 10 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java

@@ -159,5 +159,15 @@ public interface Constants {
      * 飞信短信 redis key
      */
     String FX_SMS_CAPTCHA_CODE_KEY = "fei_xin_captcha_code:";
+
+    /**
+     * 会员-会员码 redis key
+     */
+    String USER_MEMBER = "user:member:";
+
+    /**
+     * 会员码-会员 redis key
+     */
+    String MEMBER_CODE = "member:code:";
 }
 

+ 2 - 1
ruoyi-common/src/main/java/com/ruoyi/common/enums/ExceptionEnum.java

@@ -17,7 +17,8 @@ public enum ExceptionEnum implements IIntegerEnum{
     BO_IS_NULL(100009, "提交的数据不能为空"),
     SAVE_DATA_IS_NULL(100010, "保存数据不能为空"),
     USER_INTEGRAL_IS_LACK(100091, "用户积分不足"),
-    USER_BALANCE_IS_LACK(100092, "用户余额不足");
+    USER_BALANCE_IS_LACK(100092, "用户余额不足"),
+    ;
 
     private Integer code;
     private String msg;

+ 1 - 1
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/ApiTokenService.java

@@ -40,7 +40,7 @@ public class ApiTokenService {
     @Autowired
     private StringRedisTemplate redisTemplate;
 
-    private static final String TOKEN_PREFIX = "token:";
+    private static final String TOKEN_PREFIX = "api:token:";
 
     public String generateToken(User user, Integer clientType) {
         //先清掉token

+ 33 - 0
ruoyi-shop/src/main/java/com/ruoyi/shop/business/domain/bo/UserBusinessVerifierIntegralBo.java

@@ -0,0 +1,33 @@
+package com.ruoyi.shop.business.domain.bo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+public class UserBusinessVerifierIntegralBo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 会员码
+     */
+    @ApiModelProperty("会员码")
+    @NotBlank(message = "会员码不能为空")
+    private String code;
+    /**
+     * 变动积分
+     */
+    @ApiModelProperty("变动积分")
+    @NotBlank(message = "变动积分不能为空")
+    private BigDecimal integral;
+    /**
+     * 业务备注 可不传
+     */
+    @ApiModelProperty("业务备注 可不传")
+    private String remark;
+
+    private Long businessId;
+}

+ 2 - 1
ruoyi-shop/src/main/java/com/ruoyi/shop/business/exception/UserBusinessRoleExceptionEnum.java

@@ -11,7 +11,8 @@ public enum UserBusinessRoleExceptionEnum implements IIntegerEnum {
 
     // TODO 注意检查错误码,保证系统内唯一
     UserBusinessRole_IS_NOT_EXISTS(190001, "会员在每个店铺中的等级不存在"),
-    USER_BUSINESS_ROLE_IS_BOUND_OTHER_BUSINESS(190002, "会员已关联本店铺");
+    USER_BUSINESS_ROLE_IS_BOUND_OTHER_BUSINESS(190002, "会员已关联本店铺"),
+    USER_MEMBER_CODE_NOT_EXIST(190003, "会员码不存在");
 
     private Integer code;
 

+ 8 - 0
ruoyi-shop/src/main/java/com/ruoyi/shop/business/service/IUserBusinessRoleService.java

@@ -5,8 +5,10 @@ import com.ruoyi.common.core.page.TableDataInfo;
 import com.ruoyi.shop.business.domain.UserBusinessRole;
 import com.ruoyi.shop.business.domain.bo.UserBusinessChangeIntegralBo;
 import com.ruoyi.shop.business.domain.bo.UserBusinessRoleBo;
+import com.ruoyi.shop.business.domain.bo.UserBusinessVerifierIntegralBo;
 import com.ruoyi.shop.business.domain.vo.UserBusinessRoleVo;
 import com.ruoyi.shop.order.domain.bo.BusinessCountBo;
+import com.ruoyi.user.domain.vo.UserVo;
 
 import java.math.BigDecimal;
 import java.util.Collection;
@@ -95,6 +97,8 @@ public interface IUserBusinessRoleService {
      */
     UserBusinessRole queryByUserAndBusiness(Long userId, Long businessId);
 
+    UserBusinessRole queryBindByUserAndBusiness(Long userId, Long businessId);
+
     /**
      * 修改会员的积分
      *
@@ -113,4 +117,8 @@ public interface IUserBusinessRoleService {
      * @return
      */
     Integer selectCount(BusinessCountBo today);
+
+    UserVo queryUserInfo(Long userId, Long businessId);
+
+    void verify(UserBusinessVerifierIntegralBo bo);
 }

+ 45 - 0
ruoyi-shop/src/main/java/com/ruoyi/shop/business/service/impl/UserBusinessRoleServiceImpl.java

@@ -11,23 +11,29 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.business.domain.vo.BusinessUserRoleConfigVo;
 import com.ruoyi.business.service.IBusinessService;
 import com.ruoyi.business.service.IBusinessUserRoleConfigService;
+import com.ruoyi.common.constant.Constants;
 import com.ruoyi.common.core.domain.PageQuery;
 import com.ruoyi.common.core.page.TableDataInfo;
 import com.ruoyi.common.enums.ExceptionEnum;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.BeanCopyUtils;
 import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.redis.RedisUtils;
 import com.ruoyi.shop.business.domain.UserBusinessRole;
 import com.ruoyi.shop.business.domain.bo.UserBusinessChangeIntegralBo;
 import com.ruoyi.shop.business.domain.bo.UserBusinessIntegralRecordBo;
 import com.ruoyi.shop.business.domain.bo.UserBusinessRoleBo;
+import com.ruoyi.shop.business.domain.bo.UserBusinessVerifierIntegralBo;
 import com.ruoyi.shop.business.domain.vo.UserBusinessRoleVo;
 import com.ruoyi.shop.business.exception.UserBusinessRoleExceptionEnum;
 import com.ruoyi.shop.business.mapper.UserBusinessRoleMapper;
 import com.ruoyi.shop.business.service.IUserBusinessIntegralRecordService;
 import com.ruoyi.shop.business.service.IUserBusinessRoleService;
 import com.ruoyi.shop.order.domain.bo.BusinessCountBo;
+import com.ruoyi.user.domain.vo.UserVo;
 import com.ruoyi.user.enums.IntegralBillType;
+import com.ruoyi.user.enums.IntegralSourceType;
+import com.ruoyi.user.service.IUserService;
 import lombok.RequiredArgsConstructor;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
@@ -57,6 +63,9 @@ public class UserBusinessRoleServiceImpl implements IUserBusinessRoleService {
     @Lazy
     @Resource
     private IBusinessService businessService;
+    @Lazy
+    @Resource
+    private IUserService userService;
 
     /**
      * 查询会员在每个店铺中的等级分页
@@ -224,6 +233,15 @@ public class UserBusinessRoleServiceImpl implements IUserBusinessRoleService {
 
     @Override
     public UserBusinessRole queryByUserAndBusiness(Long userId, Long businessId) {
+        return baseMapper.selectOne(new LambdaQueryWrapper<UserBusinessRole>()
+            .eq(UserBusinessRole::getUserId, userId)
+            .eq(UserBusinessRole::getBusinessId, businessId)
+            .last("limit 1")
+        );
+    }
+
+    @Override
+    public UserBusinessRole queryBindByUserAndBusiness(Long userId, Long businessId) {
         return baseMapper.selectOne(new LambdaQueryWrapper<UserBusinessRole>()
             .eq(UserBusinessRole::getUserId, userId)
             .eq(UserBusinessRole::getBusinessId, businessId)
@@ -308,4 +326,31 @@ public class UserBusinessRoleServiceImpl implements IUserBusinessRoleService {
         return baseMapper.selectCustomCount(bo);
     }
 
+    @Override
+    public UserVo queryUserInfo(Long userId, Long businessId) {
+        UserBusinessRole role = queryByUserAndBusiness(userId, businessId);
+        UserVo vo = userService.getUserById(userId, true);
+        vo.setBusinessIntegralAble(ObjectUtil.isEmpty(role) ? BigDecimal.ZERO : role.getIntegralAble());
+        return vo;
+    }
+
+    @Override
+    public void verify(UserBusinessVerifierIntegralBo bo) {
+        Long userId = RedisUtils.getCacheObject(Constants.MEMBER_CODE + bo.getCode());
+        if (ObjectUtil.isEmpty(userId)) {
+            throw new ServiceException(UserBusinessRoleExceptionEnum.USER_MEMBER_CODE_NOT_EXIST);
+        }
+        changeUserIntegral(UserBusinessChangeIntegralBo.builder()
+            .userId(userId)
+            .businessId(bo.getBusinessId())
+            .integral(bo.getIntegral())
+            .isAdd(false)
+            .canExpire(false)
+            .sourceType(IntegralSourceType.INTEGRAL_VERIFY)
+//            .sourceId(add.getOrderId())
+//            .sourceCode(add.getOrderNo())
+//            .remark("商城购物下单积分抵扣")
+            .build());
+    }
+
 }

+ 15 - 0
ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/MemberCodeVo.java

@@ -0,0 +1,15 @@
+package com.ruoyi.user.domain.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class MemberCodeVo implements Serializable {
+
+    @ApiModelProperty(value = "会员码")
+    private String code;
+    @ApiModelProperty(value = "会员码二维码")
+    private String qrUrl;
+}

+ 5 - 0
ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/UserVo.java

@@ -169,4 +169,9 @@ public class UserVo {
      */
     @ApiModelProperty("支付密码")
     private String payPassword;
+
+    /**
+     * 某个商家下 可用积分
+     */
+    private BigDecimal businessIntegralAble;
 }

+ 1 - 0
ruoyi-user/src/main/java/com/ruoyi/user/enums/IntegralSourceType.java

@@ -26,6 +26,7 @@ public enum IntegralSourceType implements IIntegerEnum {
 
     EXPIRE(99, "过期"),
 
+    INTEGRAL_VERIFY(100, "积分核销"),
     ;
 
     @EnumValue

+ 4 - 4
ruoyi-user/src/main/java/com/ruoyi/user/service/IUserService.java

@@ -4,10 +4,7 @@ import com.ruoyi.common.core.domain.PageQuery;
 import com.ruoyi.common.core.page.TableDataInfo;
 import com.ruoyi.user.domain.User;
 import com.ruoyi.user.domain.bo.*;
-import com.ruoyi.user.domain.vo.UserBindingVo;
-import com.ruoyi.user.domain.vo.UserLoginVo;
-import com.ruoyi.user.domain.vo.UserStatisticsVo;
-import com.ruoyi.user.domain.vo.UserVo;
+import com.ruoyi.user.domain.vo.*;
 import com.ruoyi.user.enums.UserThirdType;
 import com.ruoyi.weixin.domain.WxUserDto;
 
@@ -175,4 +172,7 @@ public interface IUserService {
     void updateMobile(MobileUpdateBo bo, Long userId);
 
     void setPayPassword(PasswordUpdateBo bo, Long userId);
+
+    MemberCodeVo generateMemberCode(Long userId, Boolean isRefresh);
+
 }

+ 76 - 4
ruoyi-user/src/main/java/com/ruoyi/user/service/impl/UserServiceImpl.java

@@ -18,6 +18,10 @@ import com.alipay.api.response.AlipayUserInfoShareResponse;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.github.binarywang.utils.qrcode.MatrixToImageWriter;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
 import com.ruoyi.common.constant.Constants;
 import com.ruoyi.common.core.domain.PageQuery;
 import com.ruoyi.common.core.page.TableDataInfo;
@@ -29,14 +33,12 @@ import com.ruoyi.common.utils.IOSToeknUtils;
 import com.ruoyi.common.utils.ShareCodeUtils;
 import com.ruoyi.common.utils.SmsSend;
 import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.redis.RedisUtils;
 import com.ruoyi.common.utils.rest.RestUtil;
 import com.ruoyi.user.domain.User;
 import com.ruoyi.user.domain.UserThirdIdentity;
 import com.ruoyi.user.domain.bo.*;
-import com.ruoyi.user.domain.vo.UserBindingVo;
-import com.ruoyi.user.domain.vo.UserLoginVo;
-import com.ruoyi.user.domain.vo.UserStatisticsVo;
-import com.ruoyi.user.domain.vo.UserVo;
+import com.ruoyi.user.domain.vo.*;
 import com.ruoyi.user.enums.IntegralBillType;
 import com.ruoyi.user.enums.UserThirdType;
 import com.ruoyi.user.exception.UserExceptionEnum;
@@ -50,6 +52,10 @@ import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.*;
 
@@ -611,6 +617,72 @@ public class UserServiceImpl implements IUserService {
         this.baseMapper.updateById(user);
     }
 
+    @Override
+    public MemberCodeVo generateMemberCode(Long userId, Boolean isRefresh) {
+        String userKey = Constants.USER_MEMBER + userId;
+        String codeKeyPrefix = Constants.MEMBER_CODE;
+        if (isRefresh) {
+            // 清除旧的映射关系
+            String oldCode = RedisUtils.getCacheObject(userKey);
+            if (StringUtils.isNotBlank(oldCode)) {
+                RedisUtils.deleteObject(codeKeyPrefix + oldCode);
+            }
+            RedisUtils.deleteObject(userKey);
+        }
+        String code = RedisUtils.getCacheObject(userKey);
+        if (StringUtils.isBlank(code)) {
+            code = generateUniqueMemberCode(userId);
+            RedisUtils.setCacheObject(userKey, code);
+            RedisUtils.setCacheObject(codeKeyPrefix + code, userId);
+        }
+        MemberCodeVo vo = new MemberCodeVo();
+        vo.setCode(code);
+        vo.setQrUrl(generateQr(code));
+        return vo;
+    }
+
+    private String generateUniqueMemberCode(Long userId) {
+        //生成唯一码 用户不会重复 6-8位
+        String userIdPart = String.valueOf(userId);
+        if (userIdPart.length() < 4) {
+            userIdPart = String.format("%04d", userId); // 补0凑4位(如123 → 0123)
+        } else {
+            userIdPart = userIdPart.substring(userIdPart.length() - 4); // 取后4位
+        }
+        // 2. 处理时间戳(取后4位,不足4位补0)
+        String timePart = String.valueOf(System.currentTimeMillis());
+        timePart = timePart.substring(timePart.length() - 4);
+        if (timePart.length() < 4) {
+            timePart = String.format("%04d", Long.parseLong(timePart)); // 补0凑4位(如123 → 0123)
+        }
+        String randomPart = String.valueOf((int)(Math.random() * 1000));
+        // 3. 拼接为12位会员码
+        return userIdPart + timePart + randomPart;
+    }
+
+    private String generateQr(String memberCode) {
+        QRCodeWriter writer = new QRCodeWriter();
+        ByteArrayOutputStream outputStream = null;
+        try {
+            BitMatrix bitMatrix = writer.encode(memberCode, BarcodeFormat.QR_CODE, 300, 300);
+            BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix);
+
+            outputStream = new ByteArrayOutputStream();
+            ImageIO.write(image, "png", outputStream);
+            return "data:image/png;base64," + Base64.getEncoder().encodeToString(outputStream.toByteArray());
+        } catch (Exception e) {
+            return null;
+        }  finally {
+            if (outputStream != null) {
+                try {
+                    outputStream.close();
+                } catch (IOException e) {
+                    // 静默关闭,不影响主流程
+                }
+            }
+        }
+    }
+
     /**
      * 获取微信OpenId
      *