瀏覽代碼

套餐链接唯一性校验

yuhongqi 3 天之前
父節點
當前提交
5e5d5a5c5a

+ 123 - 0
fs-company-app/src/main/java/com/fs/app/controller/StoreProductPackageController.java

@@ -3,8 +3,16 @@ package com.fs.app.controller;
 
 import cn.hutool.json.JSONArray;
 import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSONObject;
 import com.fs.app.annotation.Login;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.http.HttpUtils;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyService;
+import com.fs.company.service.ICompanyUserService;
 import com.fs.store.domain.FsStoreProduct;
 import com.fs.store.domain.FsStoreProductAttrValue;
 import com.fs.store.domain.FsStoreProductPackage;
@@ -13,14 +21,19 @@ import com.fs.store.dto.StorePackageProductDTO;
 import com.fs.store.param.FsStoreProductPackageQueryParam;
 import com.fs.store.service.IFsStoreProductAttrValueService;
 import com.fs.store.service.IFsStoreProductPackageService;
+import com.fs.store.service.IFsStoreProductPackageUniqueCodeService;
 import com.fs.store.service.IFsStoreProductService;
 import com.fs.store.vo.FsStoreProductPacketVO;
+import com.fs.wx.miniapp.config.WxMaProperties;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
@@ -35,12 +48,24 @@ import java.util.List;
 @RequestMapping(value="/app/storeProductPackage")
 public class StoreProductPackageController extends  AppBaseController {
 
+    private static final Logger log = LoggerFactory.getLogger(StoreProductPackageController.class);
+
     @Autowired
     private IFsStoreProductPackageService productPackageService;
     @Autowired
     private IFsStoreProductAttrValueService attrValueService;
     @Autowired
     private IFsStoreProductService storeProductService;
+    @Autowired
+    private IFsStoreProductPackageUniqueCodeService productPackageUniqueCodeService;
+    @Autowired
+    private ICompanyService companyService;
+    @Autowired
+    private ICompanyUserService companyUserService;
+    @Autowired
+    private WxMaProperties properties;
+    @Autowired
+    private RedisCache redisCache;
     @Login
     @ApiOperation("获取套餐列表")
     @GetMapping("/getStoreProductPackage")
@@ -82,4 +107,102 @@ public class StoreProductPackageController extends  AppBaseController {
         return R.ok().put("data",storeProductPackage);
     }
 
+    @ApiOperation("套餐urlScheme")
+    @GetMapping("/getPackageAppletScheme")
+    public R getPackageAppletScheme(@RequestParam("packageId") Long packageId,
+                                    @RequestParam("companyId") Long companyId,
+                                    @RequestParam("companyUserId") Long companyUserId,
+                                    @RequestParam("path") String path) {
+        try {
+            if (StringUtils.isEmpty(path)) {
+                return R.error("path不能为空");
+            }
+            FsStoreProductPackage storeProductPackage = productPackageService.selectFsStoreProductPackageById(packageId);
+            if (storeProductPackage == null) {
+                return R.error("套餐不存在");
+            }
+            Company company = companyService.selectCompanyById(companyId);
+            if (company == null) {
+                return R.error("公司不存在");
+            }
+            CompanyUser companyUser = companyUserService.selectCompanyUserById(companyUserId);
+            if (companyUser == null) {
+                return R.error("未查询到销售信息,链接生成失败");
+            }
+            if (!companyId.equals(companyUser.getCompanyId())) {
+                return R.error("销售与公司信息不匹配");
+            }
+
+            R uniqueCodeResult = productPackageUniqueCodeService.createUniqueCode(packageId, companyId, companyUserId);
+            if (!uniqueCodeResult.isSuccess()) {
+                return uniqueCodeResult;
+            }
+            String uniqueCode = (String) uniqueCodeResult.get("uniqueCode");
+
+            String queryParam = "packageId=" + packageId + "&companyId=" + companyId + "&companyUserId=" + companyUserId + "&shareUniqueCode=" + uniqueCode;
+            String pageUrl = path.contains("?") ? path + "&" + queryParam : path + "?" + queryParam;
+            String appId = properties.getConfigs().get(0).getAppid();
+            String secret = properties.getConfigs().get(0).getSecret();
+
+            String cacheKey = "wx:access_token:" + appId;
+            String access_token = redisCache.getCacheObject(cacheKey);
+
+            if (StringUtils.isEmpty(access_token)) {
+                String rspStr = HttpUtils.sendGet("https://api.weixin.qq.com/cgi-bin/token", "grant_type=client_credential&" + "appid=" + appId + "&secret=" + secret);
+                JSONObject obj = JSONObject.parseObject(rspStr);
+                access_token = obj.getString("access_token");
+
+                if (StringUtils.isEmpty(access_token)) {
+                    log.error("获取微信 access_token 失败: {}", obj);
+                    return R.error("获取微信 access_token 失败");
+                }
+
+                redisCache.setCacheObject(cacheKey, access_token, 7200, java.util.concurrent.TimeUnit.SECONDS);
+                log.info("微信 access_token 已刷新并缓存,appId: {}", appId);
+            } else {
+                log.debug("从 Redis 缓存中获取 access_token,appId: {}", appId);
+            }
+
+            JSONObject jump_wxaObj = new JSONObject();
+            jump_wxaObj.put("page_url", pageUrl);
+            String paramStr = jump_wxaObj.toJSONString();
+            String postStr = HttpUtils.sendPost("https://api.weixin.qq.com/wxa/genwxashortlink?access_token=" + access_token, paramStr);
+            JSONObject obj = JSONObject.parseObject(postStr);
+
+            if (obj != null && (obj.getInteger("errcode") != null && obj.getInteger("errcode") == 40001)) {
+                log.warn("access_token 已失效,清除缓存并重新获取,appId: {}", appId);
+                redisCache.deleteObject(cacheKey);
+                String rspStr = HttpUtils.sendGet("https://api.weixin.qq.com/cgi-bin/token", "grant_type=client_credential&" + "appid=" + appId + "&secret=" + secret);
+                JSONObject tokenObj = JSONObject.parseObject(rspStr);
+                access_token = tokenObj.getString("access_token");
+                if (StringUtils.isNotEmpty(access_token)) {
+                    redisCache.setCacheObject(cacheKey, access_token, 7200, java.util.concurrent.TimeUnit.SECONDS);
+                    postStr = HttpUtils.sendPost("https://api.weixin.qq.com/wxa/genwxashortlink?access_token=" + access_token, paramStr);
+                    obj = JSONObject.parseObject(postStr);
+                }
+            }
+
+            return R.ok().put("result", obj).put("uniqueCode", uniqueCode);
+        } catch (Exception e) {
+            log.error("生成小程序 Scheme 失败", e);
+            return R.error("操作失败");
+        }
+    }
+
+    @Login
+    @ApiOperation("生成套餐分享唯一编码")
+    @PostMapping("/createUniqueCode")
+    public R createUniqueCode(@RequestParam("packageId") Long packageId,
+                              @RequestParam("companyId") Long companyId,
+                              @RequestParam("companyUserId") Long companyUserId) {
+        return productPackageUniqueCodeService.createUniqueCode(packageId, companyId, companyUserId);
+    }
+
+    @Login
+    @ApiOperation("校验套餐分享唯一编码是否已使用")
+    @GetMapping("/checkUniqueCode")
+    public R checkUniqueCode(@RequestParam("uniqueCode") String uniqueCode) {
+        return productPackageUniqueCodeService.checkUniqueCode(uniqueCode);
+    }
+
 }

+ 36 - 0
fs-service-system/src/main/java/com/fs/store/domain/FsStoreProductPackageUniqueCode.java

@@ -0,0 +1,36 @@
+package com.fs.store.domain;
+
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 套餐包分享唯一编码 fs_store_product_package_unique_code
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsStoreProductPackageUniqueCode extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 主键 */
+    private Long id;
+
+    /** 套餐ID */
+    private Long packageId;
+
+    /** 公司ID */
+    private Long companyId;
+
+    /** 公司员工ID */
+    private Long companyUserId;
+
+    /** 唯一编码:packageId + companyId + companyUserId + 雪花ID */
+    private String uniqueCode;
+
+    /** 是否已使用 0未使用 1已使用 */
+    private Integer isUsed;
+
+    /** 关联订单ID */
+    private Long orderId;
+}

+ 16 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsStoreProductPackageUniqueCodeMapper.java

@@ -0,0 +1,16 @@
+package com.fs.store.mapper;
+
+import com.fs.store.domain.FsStoreProductPackageUniqueCode;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 套餐包分享唯一编码 Mapper
+ */
+public interface FsStoreProductPackageUniqueCodeMapper {
+
+    int insertFsStoreProductPackageUniqueCode(FsStoreProductPackageUniqueCode record);
+
+    FsStoreProductPackageUniqueCode selectByUniqueCode(String uniqueCode);
+
+    int markUniqueCodeUsed(@Param("uniqueCode") String uniqueCode, @Param("orderId") Long orderId);
+}

+ 16 - 0
fs-service-system/src/main/java/com/fs/store/param/FsStorePackageOrderUniqueCodeCreateParam.java

@@ -0,0 +1,16 @@
+package com.fs.store.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsStorePackageOrderUniqueCodeCreateParam extends FsStorePackageOrderCreateParam {
+
+    @ApiModelProperty(value = "分享唯一编码")
+    @NotNull(message = "uniqueCode不能为空")
+    private String uniqueCode;
+}

+ 2 - 0
fs-service-system/src/main/java/com/fs/store/service/IFsStoreOrderService.java

@@ -135,6 +135,8 @@ public interface IFsStoreOrderService
 
     R createPackageOrder(long userId, FsStorePackageOrderCreateParam param);
 
+    R createPackageOrderWithUniqueCode(long userId, FsStorePackageOrderUniqueCodeCreateParam param);
+
     R finishOrder(Long orderId);
 
     String payConfirm(Integer type,Long orderId,String payCode,String tradeNo,String bankTransactionId,String bankSerialNo);

+ 27 - 0
fs-service-system/src/main/java/com/fs/store/service/IFsStoreProductPackageUniqueCodeService.java

@@ -0,0 +1,27 @@
+package com.fs.store.service;
+
+import com.fs.common.core.domain.R;
+import com.fs.store.domain.FsStoreProductPackageUniqueCode;
+
+/**
+ * 套餐包分享唯一编码 Service
+ */
+public interface IFsStoreProductPackageUniqueCodeService {
+
+    /**
+     * 生成并保存唯一编码
+     */
+    R createUniqueCode(Long packageId, Long companyId, Long companyUserId);
+
+    /**
+     * 校验 unique_code 是否存在及是否已被使用
+     */
+    R checkUniqueCode(String uniqueCode);
+
+    /**
+     * 标记分享码已使用并关联订单
+     */
+    int markUniqueCodeUsed(String uniqueCode, Long orderId);
+
+    FsStoreProductPackageUniqueCode selectByUniqueCode(String uniqueCode);
+}

+ 44 - 0
fs-service-system/src/main/java/com/fs/store/service/impl/FsStoreOrderServiceImpl.java

@@ -291,6 +291,9 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService
     @Autowired
     private ICompanyMoneyLogsService moneyLogsService;
 
+    @Autowired
+    private IFsStoreProductPackageUniqueCodeService productPackageUniqueCodeService;
+
     @Override
     public void syncExpressToWx() {
         List<FsWxExpressTask> fsWxExpressTasks = fsWxExpressTaskMapper.selectPendingData();
@@ -1792,6 +1795,47 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService
         }
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
+    public R createPackageOrderWithUniqueCode(long uid, FsStorePackageOrderUniqueCodeCreateParam param) {
+        if (StringUtils.isEmpty(param.getUniqueCode())) {
+            return R.error("uniqueCode不能为空");
+        }
+        String uniqueCode = param.getUniqueCode().trim();
+        FsStoreProductPackageUniqueCode uniqueCodeRecord = productPackageUniqueCodeService.selectByUniqueCode(uniqueCode);
+        if (uniqueCodeRecord == null) {
+            return R.error("分享码不存在");
+        }
+        if (uniqueCodeRecord.getIsUsed() != null && uniqueCodeRecord.getIsUsed() == 1) {
+            return R.error("此链接只能唯一使用")
+                    .put("isUsed", true)
+                    .put("uniqueCode", uniqueCodeRecord.getUniqueCode());
+        }
+        if (param.getPackageId() != null && !param.getPackageId().equals(uniqueCodeRecord.getPackageId())) {
+            return R.error("分享码与套餐不匹配");
+        }
+        if (param.getCompanyUserId() != null && !param.getCompanyUserId().equals(uniqueCodeRecord.getCompanyUserId())) {
+            return R.error("分享码信息不匹配");
+        }
+
+        IFsStoreOrderService orderServiceProxy = (IFsStoreOrderService) AopContext.currentProxy();
+        R result = orderServiceProxy.createPackageOrder(uid, param);
+        if (result == null || !result.isSuccess()) {
+            return result;
+        }
+
+        Object orderObj = result.get("order");
+        if (!(orderObj instanceof FsStoreOrder)) {
+            throw new CustomException("订单创建异常");
+        }
+        FsStoreOrder order = (FsStoreOrder) orderObj;
+        int updated = productPackageUniqueCodeService.markUniqueCodeUsed(uniqueCode, order.getId());
+        if (updated == 0) {
+            throw new CustomException("此链接只能唯一使用");
+        }
+        return result;
+    }
+
     @Override
     public synchronized R finishOrder(Long orderId) {
         IFsStoreOrderService fsStoreOrderService = (IFsStoreOrderService) AopContext.currentProxy();

+ 97 - 0
fs-service-system/src/main/java/com/fs/store/service/impl/FsStoreProductPackageUniqueCodeServiceImpl.java

@@ -0,0 +1,97 @@
+package com.fs.store.service.impl;
+
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.store.domain.FsStoreProductPackageUniqueCode;
+import com.fs.store.mapper.FsStoreProductPackageUniqueCodeMapper;
+import com.fs.store.service.IFsStoreProductPackageUniqueCodeService;
+import com.fs.store.service.IFsStoreProductPackageService;
+import com.fs.system.config.SnowflakeUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 套餐包分享唯一编码 Service 实现
+ */
+@Service
+public class FsStoreProductPackageUniqueCodeServiceImpl implements IFsStoreProductPackageUniqueCodeService {
+
+    @Autowired
+    private FsStoreProductPackageUniqueCodeMapper uniqueCodeMapper;
+
+    @Autowired
+    private IFsStoreProductPackageService productPackageService;
+
+    @Override
+    public R createUniqueCode(Long packageId, Long companyId, Long companyUserId) {
+        if (packageId == null) {
+            return R.error("packageId不能为空");
+        }
+        if (companyId == null) {
+            return R.error("companyId不能为空");
+        }
+        if (companyUserId == null) {
+            return R.error("companyUserId不能为空");
+        }
+        if (productPackageService.selectFsStoreProductPackageById(packageId) == null) {
+            return R.error("套餐不存在");
+        }
+
+        String snowflakeId = SnowflakeUtils.nextId();
+        String uniqueCode = packageId + String.valueOf(companyId) + companyUserId + snowflakeId;
+
+        FsStoreProductPackageUniqueCode record = new FsStoreProductPackageUniqueCode();
+        record.setPackageId(packageId);
+        record.setCompanyId(companyId);
+        record.setCompanyUserId(companyUserId);
+        record.setUniqueCode(uniqueCode);
+        record.setIsUsed(0);
+        record.setCreateTime(DateUtils.getNowDate());
+        uniqueCodeMapper.insertFsStoreProductPackageUniqueCode(record);
+
+        return R.ok()
+                .put("id", record.getId())
+                .put("packageId", packageId)
+                .put("companyId", companyId)
+                .put("companyUserId", companyUserId)
+                .put("uniqueCode", uniqueCode)
+                .put("isUsed", 0);
+    }
+
+    @Override
+    public R checkUniqueCode(String uniqueCode) {
+        if (StringUtils.isEmpty(uniqueCode)) {
+            return R.error("uniqueCode不能为空");
+        }
+        FsStoreProductPackageUniqueCode record = uniqueCodeMapper.selectByUniqueCode(uniqueCode.trim());
+        if (record == null) {
+            return R.error("分享码不存在");
+        }
+        if (record.getIsUsed() != null && record.getIsUsed() == 1) {
+            return R.error("该分享码已被使用")
+                    .put("isUsed", true)
+                    .put("uniqueCode", record.getUniqueCode());
+        }
+        return R.ok("分享码可用")
+                .put("isUsed", false)
+                .put("id", record.getId())
+                .put("packageId", record.getPackageId())
+                .put("companyId", record.getCompanyId())
+                .put("companyUserId", record.getCompanyUserId())
+                .put("uniqueCode", record.getUniqueCode());
+    }
+
+    @Override
+    public FsStoreProductPackageUniqueCode selectByUniqueCode(String uniqueCode) {
+        return uniqueCodeMapper.selectByUniqueCode(uniqueCode);
+    }
+
+    @Override
+    public int markUniqueCodeUsed(String uniqueCode, Long orderId) {
+        if (StringUtils.isEmpty(uniqueCode) || orderId == null) {
+            return 0;
+        }
+        return uniqueCodeMapper.markUniqueCodeUsed(uniqueCode.trim(), orderId);
+    }
+}

+ 37 - 0
fs-service-system/src/main/resources/mapper/store/FsStoreProductPackageUniqueCodeMapper.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.store.mapper.FsStoreProductPackageUniqueCodeMapper">
+
+    <resultMap type="FsStoreProductPackageUniqueCode" id="FsStoreProductPackageUniqueCodeResult">
+        <result property="id" column="id"/>
+        <result property="packageId" column="package_id"/>
+        <result property="companyId" column="company_id"/>
+        <result property="companyUserId" column="company_user_id"/>
+        <result property="uniqueCode" column="unique_code"/>
+        <result property="isUsed" column="is_used"/>
+        <result property="orderId" column="order_id"/>
+        <result property="createTime" column="create_time"/>
+    </resultMap>
+
+    <insert id="insertFsStoreProductPackageUniqueCode" parameterType="FsStoreProductPackageUniqueCode"
+            useGeneratedKeys="true" keyProperty="id">
+        insert into fs_store_product_package_unique_code
+        (package_id, company_id, company_user_id, unique_code, is_used, create_time)
+        values (#{packageId}, #{companyId}, #{companyUserId}, #{uniqueCode}, #{isUsed}, #{createTime})
+    </insert>
+
+    <select id="selectByUniqueCode" parameterType="String" resultMap="FsStoreProductPackageUniqueCodeResult">
+        select id, package_id, company_id, company_user_id, unique_code, is_used, order_id, create_time
+        from fs_store_product_package_unique_code
+        where unique_code = #{uniqueCode}
+    </select>
+
+    <update id="markUniqueCodeUsed">
+        update fs_store_product_package_unique_code
+        set is_used = 1, order_id = #{orderId}
+        where unique_code = #{uniqueCode} and is_used = 0
+    </update>
+
+</mapper>

+ 8 - 0
fs-user-app/src/main/java/com/fs/app/controller/StoreOrderController.java

@@ -315,6 +315,14 @@ public class StoreOrderController extends  AppBaseController {
         return orderService.createPackageOrder(Long.parseLong(getUserId()),param);
     }
 
+    @Login
+    @ApiOperation("创建套餐分享订单")
+    @PostMapping("/createPackageOrderWithUniqueCode")
+    @RepeatSubmit(intervalTime = 3)
+    public R createPackageOrderWithUniqueCode(@Validated @RequestBody FsStorePackageOrderUniqueCodeCreateParam param){
+        return orderService.createPackageOrderWithUniqueCode(Long.parseLong(getUserId()),param);
+    }
+
     @Login
     @ApiOperation("完成订单")
     @PostMapping("/finishOrder")

+ 117 - 0
fs-user-app/src/main/java/com/fs/app/controller/StoreProductPackageController.java

@@ -3,8 +3,15 @@ package com.fs.app.controller;
 
 import cn.hutool.json.JSONArray;
 import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSONObject;
 import com.fs.app.annotation.Login;
 import com.fs.common.core.domain.R;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.http.HttpUtils;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyService;
+import com.fs.company.service.ICompanyUserService;
 import com.fs.store.domain.FsStoreProduct;
 import com.fs.store.domain.FsStoreProductAttrValue;
 import com.fs.store.domain.FsStoreProductPackage;
@@ -13,12 +20,16 @@ import com.fs.store.dto.StorePackageProductDTO;
 import com.fs.store.param.FsStoreProductPackageQueryParam;
 import com.fs.store.service.IFsStoreProductAttrValueService;
 import com.fs.store.service.IFsStoreProductPackageService;
+import com.fs.store.service.IFsStoreProductPackageUniqueCodeService;
 import com.fs.store.service.IFsStoreProductService;
 import com.fs.store.vo.FsStoreProductPacketVO;
+import com.fs.wx.miniapp.config.WxMaProperties;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
@@ -32,12 +43,22 @@ import java.util.List;
 @RequestMapping(value="/app/storeProductPackage")
 public class StoreProductPackageController extends  AppBaseController {
 
+    private static final Logger log = LoggerFactory.getLogger(StoreProductPackageController.class);
+
     @Autowired
     private IFsStoreProductPackageService productPackageService;
     @Autowired
     private IFsStoreProductAttrValueService attrValueService;
     @Autowired
     private IFsStoreProductService storeProductService;
+    @Autowired
+    private IFsStoreProductPackageUniqueCodeService productPackageUniqueCodeService;
+    @Autowired
+    private ICompanyService companyService;
+    @Autowired
+    private ICompanyUserService companyUserService;
+    @Autowired
+    private WxMaProperties properties;
     @Login
     @ApiOperation("获取套餐列表")
     @GetMapping("/getStoreProductPackage")
@@ -79,4 +100,100 @@ public class StoreProductPackageController extends  AppBaseController {
         return R.ok().put("data",storeProductPackage);
     }
 
+    @ApiOperation("套餐urlScheme")
+    @GetMapping("/getPackageAppletScheme")
+    public R getPackageAppletScheme(@RequestParam("packageId") Long packageId,
+                                    @RequestParam("companyId") Long companyId,
+                                    @RequestParam("companyUserId") Long companyUserId,
+                                    @RequestParam("path") String path) {
+        try {
+            if (StringUtils.isEmpty(path)) {
+                return R.error("path不能为空");
+            }
+            FsStoreProductPackage storeProductPackage = productPackageService.selectFsStoreProductPackageById(packageId);
+            if (storeProductPackage == null) {
+                return R.error("套餐不存在");
+            }
+            Company company = companyService.selectCompanyById(companyId);
+            if (company == null) {
+                return R.error("公司不存在");
+            }
+            CompanyUser companyUser = companyUserService.selectCompanyUserById(companyUserId);
+            if (companyUser == null) {
+                return R.error("未查询到销售信息,链接生成失败");
+            }
+            if (!companyId.equals(companyUser.getCompanyId())) {
+                return R.error("销售与公司信息不匹配");
+            }
+
+            R uniqueCodeResult = productPackageUniqueCodeService.createUniqueCode(packageId, companyId, companyUserId);
+            if (!uniqueCodeResult.isSuccess()) {
+                return uniqueCodeResult;
+            }
+            String uniqueCode = (String) uniqueCodeResult.get("uniqueCode");
+
+            String queryParam = "packageId=" + packageId + "&companyId=" + companyId + "&companyUserId=" + companyUserId + "&shareUniqueCode=" + uniqueCode;
+            String pageUrl = path.contains("?") ? path + "&" + queryParam : path + "?" + queryParam;
+            String appId = properties.getConfigs().get(0).getAppid();
+            String secret = properties.getConfigs().get(0).getSecret();
+
+            String cacheKey = "wx:access_token:" + appId;
+            String access_token = redisCache.getCacheObject(cacheKey);
+
+            if (StringUtils.isEmpty(access_token)) {
+                String rspStr = HttpUtils.sendGet("https://api.weixin.qq.com/cgi-bin/token", "grant_type=client_credential&" + "appid=" + appId + "&secret=" + secret);
+                JSONObject obj = JSONObject.parseObject(rspStr);
+                access_token = obj.getString("access_token");
+
+                if (StringUtils.isEmpty(access_token)) {
+                    log.error("获取微信 access_token 失败: {}", obj);
+                    return R.error("获取微信 access_token 失败");
+                }
+
+                redisCache.setCacheObject(cacheKey, access_token, 7200, java.util.concurrent.TimeUnit.SECONDS);
+                log.info("微信 access_token 已刷新并缓存,appId: {}", appId);
+            } else {
+                log.debug("从 Redis 缓存中获取 access_token,appId: {}", appId);
+            }
+
+            JSONObject jump_wxaObj = new JSONObject();
+            jump_wxaObj.put("page_url", pageUrl);
+            String paramStr = jump_wxaObj.toJSONString();
+            String postStr = HttpUtils.sendPost("https://api.weixin.qq.com/wxa/genwxashortlink?access_token=" + access_token, paramStr);
+            JSONObject obj = JSONObject.parseObject(postStr);
+
+            if (obj != null && (obj.getInteger("errcode") != null && obj.getInteger("errcode") == 40001)) {
+                log.warn("access_token 已失效,清除缓存并重新获取,appId: {}", appId);
+                redisCache.deleteObject(cacheKey);
+                String rspStr = HttpUtils.sendGet("https://api.weixin.qq.com/cgi-bin/token", "grant_type=client_credential&" + "appid=" + appId + "&secret=" + secret);
+                JSONObject tokenObj = JSONObject.parseObject(rspStr);
+                access_token = tokenObj.getString("access_token");
+                if (StringUtils.isNotEmpty(access_token)) {
+                    redisCache.setCacheObject(cacheKey, access_token, 7200, java.util.concurrent.TimeUnit.SECONDS);
+                    postStr = HttpUtils.sendPost("https://api.weixin.qq.com/wxa/genwxashortlink?access_token=" + access_token, paramStr);
+                    obj = JSONObject.parseObject(postStr);
+                }
+            }
+
+            return R.ok().put("result", obj).put("uniqueCode", uniqueCode);
+        } catch (Exception e) {
+            log.error("生成小程序 Scheme 失败", e);
+            return R.error("操作失败");
+        }
+    }
+    @Login
+    @ApiOperation("生成套餐分享唯一编码")
+    @PostMapping("/createUniqueCode")
+    public R createUniqueCode(@RequestParam("packageId") Long packageId,
+                              @RequestParam("companyId") Long companyId,
+                              @RequestParam("companyUserId") Long companyUserId) {
+        return productPackageUniqueCodeService.createUniqueCode(packageId, companyId, companyUserId);
+    }
+
+    @ApiOperation("校验套餐分享唯一编码是否已使用")
+    @GetMapping("/checkUniqueCode")
+    public R checkUniqueCode(@RequestParam("uniqueCode") String uniqueCode) {
+        return productPackageUniqueCodeService.checkUniqueCode(uniqueCode);
+    }
+
 }