Bläddra i källkod

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

caoliqin 1 vecka sedan
förälder
incheckning
b657a300bc
40 ändrade filer med 1532 tillägg och 29 borttagningar
  1. 41 0
      fs-admin/src/main/java/com/fs/aiVoice/VolcEngineSpeechController.java
  2. 209 0
      fs-service/src/main/java/com/fs/aiSoundReplication/client/VolcEngineApiClient.java
  3. 48 0
      fs-service/src/main/java/com/fs/aiSoundReplication/config/VolcEngineSpeechConfig.java
  4. 78 0
      fs-service/src/main/java/com/fs/aiSoundReplication/entity/dto/BatchListMegaTTSTrainStatusReq.java
  5. 213 0
      fs-service/src/main/java/com/fs/aiSoundReplication/entity/dto/BatchListMegaTTSTrainStatusResp.java
  6. 73 0
      fs-service/src/main/java/com/fs/aiSoundReplication/entity/dto/OrderAccessResourcePacksReq.java
  7. 27 0
      fs-service/src/main/java/com/fs/aiSoundReplication/entity/dto/OrderAccessResourcePacksResp.java
  8. 36 0
      fs-service/src/main/java/com/fs/aiSoundReplication/entity/dto/RenewAccessResourcePacksReq.java
  9. 21 0
      fs-service/src/main/java/com/fs/aiSoundReplication/service/VolcEngineSpeechService.java
  10. 83 0
      fs-service/src/main/java/com/fs/aiSoundReplication/service/impl/VolcEngineSpeechServiceImpl.java
  11. 5 2
      fs-service/src/main/java/com/fs/company/service/impl/call/node/AbstractWorkflowNode.java
  12. 2 0
      fs-service/src/main/java/com/fs/course/param/FsUserVideoListUParam.java
  13. 9 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  14. 146 4
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  15. 5 0
      fs-service/src/main/java/com/fs/his/config/StoreConfig.java
  16. 12 0
      fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java
  17. 11 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  18. 4 4
      fs-service/src/main/java/com/fs/his/mapper/FsIntegralGoodsMapper.java
  19. 3 0
      fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderItemMapper.java
  20. 18 0
      fs-service/src/main/java/com/fs/his/param/CreateOrderFromCartParm.java
  21. 3 0
      fs-service/src/main/java/com/fs/his/service/IFsIntegralCartService.java
  22. 2 0
      fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java
  23. 128 13
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralCartServiceImpl.java
  24. 5 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  25. 50 0
      fs-service/src/main/java/com/fs/his/vo/FsIntegralGoodsVo.java
  26. 3 0
      fs-service/src/main/java/com/fs/his/vo/GetCartGoodsDetailsVo.java
  27. 7 0
      fs-service/src/main/java/com/fs/his/vo/GetFsIntegralCartListVo.java
  28. 10 0
      fs-service/src/main/java/com/fs/im/service/OpenIMService.java
  29. 71 0
      fs-service/src/main/java/com/fs/im/service/impl/OpenIMServiceImpl.java
  30. 1 1
      fs-service/src/main/resources/application-druid-mengniu.yml
  31. 3 3
      fs-service/src/main/resources/mapper/his/FsIntegralCartMapper.xml
  32. 7 0
      fs-service/src/main/resources/mapper/his/FsIntegralGoodsMapper.xml
  33. 10 0
      fs-service/src/main/resources/mapper/his/FsIntegralOrderMapper.xml
  34. 9 0
      fs-service/src/main/resources/mapper/his/FsStoreOrderItemMapper.xml
  35. 1 1
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  36. 77 1
      fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java
  37. 11 0
      fs-user-app/src/main/java/com/fs/app/controller/FsIntegralCartController.java
  38. 34 0
      fs-user-app/src/main/java/com/fs/app/controller/PackageController.java
  39. 31 0
      fs-user-app/src/main/java/com/fs/app/param/Pagination.java
  40. 25 0
      fs-user-app/src/main/java/com/fs/app/param/UserRequestParam.java

+ 41 - 0
fs-admin/src/main/java/com/fs/aiVoice/VolcEngineSpeechController.java

@@ -0,0 +1,41 @@
+package com.fs.aiVoice;
+
+import com.fs.aiSoundReplication.entity.dto.*;
+import com.fs.aiSoundReplication.service.impl.VolcEngineSpeechServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 火山引擎音色管理控制器
+ */
+@RestController
+@RequestMapping("/volcengine/speech")
+public class VolcEngineSpeechController {
+
+    @Autowired
+    private VolcEngineSpeechServiceImpl speechService;
+
+    /**
+     * 分页查询音色状态
+     */
+    @PostMapping("/batchListMegaTTSTrainStatus")
+    public BatchListMegaTTSTrainStatusResp batchListMegaTTSTrainStatus(@RequestBody BatchListMegaTTSTrainStatusReq req) {
+        return speechService.batchListMegaTTSTrainStatus(req);
+    }
+
+    /**
+     * 音色下单
+     */
+    @PostMapping("/orderAccessResourcePacks")
+    public OrderAccessResourcePacksResp orderAccessResourcePacks(@RequestBody OrderAccessResourcePacksReq req) {
+        return speechService.orderAccessResourcePacks(req);
+    }
+
+    /**
+     * 音色续费
+     */
+    @PostMapping("/renewAccessResourcePacks")
+    public OrderAccessResourcePacksResp renewAccessResourcePacks(@RequestBody RenewAccessResourcePacksReq req) {
+        return speechService.renewAccessResourcePacks(req);
+    }
+}

+ 209 - 0
fs-service/src/main/java/com/fs/aiSoundReplication/client/VolcEngineApiClient.java

@@ -0,0 +1,209 @@
+package com.fs.aiSoundReplication.client;
+
+import com.fs.aiSoundReplication.config.VolcEngineSpeechConfig;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.DataInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * 火山引擎API客户端 - 负责HTTP请求发送和签名验证
+ */
+@Slf4j
+@Component
+public class VolcEngineApiClient {
+
+    @Autowired
+    private VolcEngineSpeechConfig config;
+
+    private static final BitSet URLENCODER = new BitSet(256);
+    private static final String CONST_ENCODE = "0123456789abcdef";
+    public static final Charset UTF_8 = StandardCharsets.UTF_8;
+
+    static {
+        int i;
+        for (i = 97; i <= 122; ++i) {
+            URLENCODER.set(i);
+        }
+
+        for (i = 65; i <= 90; ++i) {
+            URLENCODER.set(i);
+        }
+
+        for (i = 48; i <= 57; ++i) {
+            URLENCODER.set(i);
+        }
+        URLENCODER.set('-');
+        URLENCODER.set('_');
+        URLENCODER.set('.');
+        URLENCODER.set('~');
+    }
+
+    /**
+     * 执行API请求
+     */
+    public String doRequest(String method, Object requestBody,
+                            Date date, String action, String version) throws Exception {
+        ObjectMapper objectMapper = new ObjectMapper();
+        String bodyJson = objectMapper.writeValueAsString(requestBody);
+        byte[] body = bodyJson.getBytes();
+
+        String xContentSha256 = hashSHA256(body);
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
+        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
+        String xDate = sdf.format(date);
+        String shortXDate = xDate.substring(0, 8);
+        String contentType = "application/json; charset=utf-8";
+        String signHeader = "content-type;host;x-content-sha256;x-date";
+        SortedMap<String, String> realQueryList = new TreeMap<>();
+        realQueryList.put("Action", action);
+        realQueryList.put("Version", version);
+        StringBuilder querySB = new StringBuilder();
+        for (String key : realQueryList.keySet()) {
+            querySB.append(signStringEncoder(key)).append("=").append(signStringEncoder(realQueryList.get(key))).append("&");
+        }
+        querySB.deleteCharAt(querySB.length() - 1);
+
+        String canonicalStringBuilder = method + "\n" + config.getPath() + "\n" + querySB + "\n" +
+                "content-type:" + contentType + "\n" +
+                "host:" + config.getEndpoint() + "\n" +
+                "x-content-sha256:" + xContentSha256 + "\n" +
+                "x-date:" + xDate + "\n" +
+                "\n" +
+                signHeader + "\n" +
+                xContentSha256;
+
+        log.debug("Canonical Request: {}", canonicalStringBuilder);
+
+        String hashcanonicalString = hashSHA256(canonicalStringBuilder.getBytes());
+        String credentialScope = shortXDate + "/" + config.getRegion() + "/" + config.getService() + "/request";
+        String signString = "HMAC-SHA256" + "\n" + xDate + "\n" + credentialScope + "\n" + hashcanonicalString;
+
+        byte[] signKey = genSigningSecretKeyV4(config.getSecretAccessKey(), shortXDate, config.getRegion(), config.getService());
+        String signature = bytesToHex(hmacSHA256(signKey, signString));
+        log.debug("Signature: {}", signature);
+
+        String urlString = config.getSchema() + "://" + config.getEndpoint() + config.getPath() + "?" + querySB;
+        log.info("Request URL: {}", urlString);
+
+        URL url = new URL(urlString);
+
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setRequestMethod(method);
+        conn.setRequestProperty("Host", config.getEndpoint());
+        conn.setRequestProperty("X-Date", xDate);
+        conn.setRequestProperty("X-Content-Sha256", xContentSha256);
+        conn.setRequestProperty("Content-Type", contentType);
+        conn.setRequestProperty("Authorization", "HMAC-SHA256" +
+                " Credential=" + config.getAccessKeyId() + "/" + credentialScope +
+                ", SignedHeaders=" + signHeader +
+                ", Signature=" + signature);
+
+        if (!Objects.equals(conn.getRequestMethod(), "GET")) {
+            conn.setDoOutput(true);
+            OutputStream os = conn.getOutputStream();
+            os.write(body);
+            os.flush();
+            os.close();
+        }
+        conn.connect();
+
+        int responseCode = conn.getResponseCode();
+
+        InputStream is;
+        if (responseCode == 200) {
+            is = conn.getInputStream();
+        } else {
+            is = conn.getErrorStream();
+        }
+        byte[] bytes = new byte[is.available()];
+        DataInputStream dataInputStream = new DataInputStream(is);
+        dataInputStream.readFully(bytes);
+        String responseBody = new String(bytes);
+        is.close();
+
+        log.info("Response Code: {}", responseCode);
+        log.debug("Response Body: {}", responseBody);
+
+        return responseBody;
+    }
+
+    private String signStringEncoder(String source) {
+        if (source == null) {
+            return null;
+        }
+        StringBuilder buf = new StringBuilder(source.length());
+        ByteBuffer bb = UTF_8.encode(source);
+        while (bb.hasRemaining()) {
+            int b = bb.get() & 255;
+            if (URLENCODER.get(b)) {
+                buf.append((char) b);
+            } else if (b == 32) {
+                buf.append("%20");
+            } else {
+                buf.append("%");
+                char hex1 = CONST_ENCODE.charAt(b >> 4);
+                char hex2 = CONST_ENCODE.charAt(b & 15);
+                buf.append(hex1);
+                buf.append(hex2);
+            }
+        }
+
+        return buf.toString();
+    }
+
+    public static String hashSHA256(byte[] content) throws Exception {
+        try {
+            MessageDigest md = MessageDigest.getInstance("SHA-256");
+
+            return bytesToHex(md.digest(content));
+        } catch (Exception e) {
+            throw new Exception(
+                    "Unable to compute hash while signing request: "
+                            + e.getMessage(), e);
+        }
+    }
+
+    public static byte[] hmacSHA256(byte[] key, String content) throws Exception {
+        try {
+            Mac mac = Mac.getInstance("HmacSHA256");
+            mac.init(new SecretKeySpec(key, "HmacSHA256"));
+            return mac.doFinal(content.getBytes());
+        } catch (Exception e) {
+            throw new Exception(
+                    "Unable to calculate a request signature: "
+                            + e.getMessage(), e);
+        }
+    }
+
+    private byte[] genSigningSecretKeyV4(String secretKey, String date, String region, String service) throws Exception {
+        byte[] kDate = hmacSHA256((secretKey).getBytes(), date);
+        byte[] kRegion = hmacSHA256(kDate, region);
+        byte[] kService = hmacSHA256(kRegion, service);
+        return hmacSHA256(kService, "request");
+    }
+
+    public static String bytesToHex(byte[] bytes) {
+        char[] hexChars = new char[bytes.length * 2];
+        for (int j = 0; j < bytes.length; j++) {
+            int v = bytes[j] & 0xFF;
+            hexChars[j * 2] = CONST_ENCODE.toCharArray()[v >>> 4];
+            hexChars[j * 2 + 1] = CONST_ENCODE.toCharArray()[v & 0x0F];
+        }
+        return new String(hexChars);
+    }
+}

+ 48 - 0
fs-service/src/main/java/com/fs/aiSoundReplication/config/VolcEngineSpeechConfig.java

@@ -0,0 +1,48 @@
+package com.fs.aiSoundReplication.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 配置类 - 火山引擎配置
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "volcengine.speech")
+public class VolcEngineSpeechConfig {
+    /**
+     * Access Key ID - 火山引擎访问密钥ID
+     */
+    private String accessKeyId="AKLTMzhiNjY4Yjg5ODljNGFlZDljNTIwOWM2MDQzYzBmYmY";//测试数据
+
+    /**
+     * Secret Access Key - 火山引擎访问密钥
+     */
+    private String secretAccessKey="zxcvbnmzxcvbnmxcvbnm";//测试数据
+
+    /**
+     * 区域 - 默认值: cn-north-1
+     */
+    private String region = "cn-north-1";
+
+    /**
+     * 服务名称 - 默认值: speech_saas_prod
+     */
+    private String service = "speech_saas_prod";
+
+    /**
+     * API端点域名 - 默认值: open.volcengineapi.com
+     */
+    private String endpoint = "open.volcengineapi.com";
+
+    /**
+     * 协议 - 默认值: https
+     */
+    private String schema = "https";
+
+    /**
+     * 请求路径 - 默认值: /
+     */
+    private String path = "/";
+}

+ 78 - 0
fs-service/src/main/java/com/fs/aiSoundReplication/entity/dto/BatchListMegaTTSTrainStatusReq.java

@@ -0,0 +1,78 @@
+package com.fs.aiSoundReplication.entity.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 批量查询音色状态请求
+ */
+@Data
+public class BatchListMegaTTSTrainStatusReq {
+    /**
+     * 应用ID
+     */
+    @JsonProperty("AppID")
+    private String appId;
+    
+    /**
+     * SpeakerID的列表,传空为返回指定APPID下的全部SpeakerID
+     */
+    @JsonProperty("SpeakerIDs")
+    private List<String> speakerIds;
+    
+    /**
+     * 音色状态,支持取值:Unknown、Training、Success、Active、Expired、Reclaimed
+     */
+    @JsonProperty("State")
+    private String state;
+    
+    /**
+     * 页数, 需大于0, 默认为1
+     */
+    @JsonProperty("PageNumber")
+    private Integer pageNumber = 1;
+    
+    /**
+     * 每页条数, 必须在范围[1, 100]内, 默认为10
+     */
+    @JsonProperty("PageSize")
+    private Integer pageSize = 10;
+    
+    /**
+     * 上次请求返回的字符串; 如果不为空的话, 将覆盖PageNumber及PageSize的值
+     */
+    @JsonProperty("NextToken")
+    private String nextToken;
+    
+    /**
+     * 与NextToken相配合控制返回结果的最大数量; 如果不为空则必须在范围[1, 100]内, 默认为10
+     */
+    @JsonProperty("MaxResults")
+    private Integer maxResults = 10;
+    
+    /**
+     * 下单时间检索上边界毫秒级时间戳,受实例交付速度影响,可能比支付完成的时间晚
+     */
+    @JsonProperty("OrderTimeStart")
+    private Long orderTimeStart;
+    
+    /**
+     * 下单时间检索下边界毫秒级时间戳,受实例交付速度影响,可能比支付完成的时间晚
+     */
+    @JsonProperty("OrderTimeEnd")
+    private Long orderTimeEnd;
+    
+    /**
+     * 实例到期时间的检索上边界毫秒级时间戳
+     */
+    @JsonProperty("ExpireTimeStart")
+    private Long expireTimeStart;
+    
+    /**
+     * 实例到期时间的检索下边界毫秒级时间戳
+     */
+    @JsonProperty("ExpireTimeEnd")
+    private Long expireTimeEnd;
+}

+ 213 - 0
fs-service/src/main/java/com/fs/aiSoundReplication/entity/dto/BatchListMegaTTSTrainStatusResp.java

@@ -0,0 +1,213 @@
+package com.fs.aiSoundReplication.entity.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 批量查询音色状态响应
+ */
+@Data
+public class BatchListMegaTTSTrainStatusResp {
+    @JsonProperty("ResponseMetadata")
+    private ResponseMetadata responseMetadata;
+    
+    @JsonProperty("Result")
+    private ResultData result;
+    
+    @Data
+    public static class ResponseMetadata {
+        /**
+         * 请求ID
+         */
+        @JsonProperty("RequestId")
+        private String requestId;
+        
+        /**
+         * 动作
+         */
+        @JsonProperty("Action")
+        private String action;
+        
+        /**
+         * 版本
+         */
+        @JsonProperty("Version")
+        private String version;
+        
+        /**
+         * 服务
+         */
+        @JsonProperty("Service")
+        private String service;
+        
+        /**
+         * 区域
+         */
+        @JsonProperty("Region")
+        private String region;
+        
+        /**
+         * 错误信息
+         */
+        @JsonProperty("Error")
+        private ErrorInfo error;
+    }
+    
+    @Data
+    public static class ResultData {
+        /**
+         * 应用ID
+         */
+        @JsonProperty("AppID")
+        private String appId;
+        
+        /**
+         * SpeakerIDs总数量
+         */
+        @JsonProperty("TotalCount")
+        private Integer totalCount;
+        
+        /**
+         * NextToken字符串,可发送请求后面的结果; 如果没有更多结果将为空
+         */
+        @JsonProperty("NextToken")
+        private String nextToken;
+        
+        /**
+         * 使用分页参数时的当前页数
+         */
+        @JsonProperty("PageNumber")
+        private Integer pageNumber;
+        
+        /**
+         * 使用分页参数时当前页包含的条数
+         */
+        @JsonProperty("PageSize")
+        private Integer pageSize;
+        
+        /**
+         * 状态列表
+         */
+        @JsonProperty("Statuses")
+        private List<SpeakerStatus> statuses;
+    }
+    
+    @Data
+    public static class SpeakerStatus {
+        /**
+         * 创建时间,unix epoch格式,单位ms
+         */
+        @JsonProperty("CreateTime")
+        private Long createTime;
+        
+        /**
+         * demo音频链接
+         */
+        @JsonProperty("DemoAudio")
+        private String demoAudio;
+        
+        /**
+         * 火山引擎实例Number
+         */
+        @JsonProperty("InstanceNO")
+        private String instanceNo;
+        
+        /**
+         * 是否可激活
+         */
+        @JsonProperty("IsActivable")
+        private Boolean isActivable;
+        
+        /**
+         * SpeakerID
+         */
+        @JsonProperty("SpeakerID")
+        private String speakerId;
+        
+        /**
+         * SpeakerID的状态
+         */
+        @JsonProperty("State")
+        private String state;
+        
+        /**
+         * SpeakerID已训练过的次数
+         */
+        @JsonProperty("Version")
+        private String version;
+        
+        /**
+         * 到期时间
+         */
+        @JsonProperty("ExpireTime")
+        private Long expireTime;
+        
+        /**
+         * 下单时间
+         */
+        @JsonProperty("OrderTime")
+        private Long orderTime;
+        
+        /**
+         * 别名,和控制台同步
+         */
+        @JsonProperty("Alias")
+        private String alias;
+        
+        /**
+         * 剩余训练次数
+         */
+        @JsonProperty("AvailableTrainingTimes")
+        private Integer availableTrainingTimes;
+        
+        /**
+         * 模型类型详情列表
+         */
+        @JsonProperty("ModelTypeDetails")
+        private List<ModelTypeDetail> modelTypeDetails;
+    }
+    
+    @Data
+    public static class ModelTypeDetail {
+        /**
+         * 模型类型
+         */
+        @JsonProperty("ModelType")
+        private Integer modelType;
+        
+        /**
+         * demo音频链接
+         */
+        @JsonProperty("DemoAudio")
+        private String demoAudio;
+        
+        /**
+         * ICL Speaker ID
+         */
+        @JsonProperty("IclSpeakerId")
+        private String iclSpeakerId;
+        
+        /**
+         * 资源ID
+         */
+        @JsonProperty("ResourceID")
+        private String resourceId;
+    }
+    
+    @Data
+    public static class ErrorInfo {
+        /**
+         * 错误码
+         */
+        @JsonProperty("Code")
+        private String code;
+        
+        /**
+         * 错误消息
+         */
+        @JsonProperty("Message")
+        private String message;
+    }
+}

+ 73 - 0
fs-service/src/main/java/com/fs/aiSoundReplication/entity/dto/OrderAccessResourcePacksReq.java

@@ -0,0 +1,73 @@
+package com.fs.aiSoundReplication.entity.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * 音色下单请求
+ */
+@Data
+public class OrderAccessResourcePacksReq {
+    /**
+     * 应用ID
+     */
+    @JsonProperty("AppID")
+    private Integer appId;
+    
+    /**
+     * 平台的服务类型资源标识,固定值:volc.megatts.voiceclone
+     */
+    @JsonProperty("ResourceID")
+    private String resourceId = "volc.megatts.voiceclone";
+    
+    /**
+     * 平台的计费项标识,固定值:Model_storage 声音复刻
+     */
+    @JsonProperty("Code")
+    private String code = "Model_storage";
+    
+    /**
+     * 下单单个音色的时长,单位为月
+     */
+    @JsonProperty("Times")
+    private Integer times;
+    
+    /**
+     * 下单音色的个数,如100,即为购买100个音色
+     */
+    @JsonProperty("Quantity")
+    private Integer quantity;
+    
+    /**
+     * 是否自动使用代金券,默认false
+     */
+    @JsonProperty("AutoUseCoupon")
+    private Boolean autoUseCoupon = false;
+    
+    /**
+     * 代金券ID,通过代金券管理获取
+     */
+    @JsonProperty("CouponID")
+    private String couponId;
+    
+    /**
+     * 项目&标签账单配置
+     */
+    @JsonProperty("ResourceTag")
+    private ResourceTag resourceTag;
+}
+
+@Data
+class ResourceTag {
+    /**
+     * 标签,通过标签管理获取
+     */
+    @JsonProperty("CustomTags")
+    private java.util.Map<String, String> customTags;
+    
+    /**
+     * 项目名称,通过项目管理获取
+     */
+    @JsonProperty("ProjectName")
+    private String projectName;
+}

+ 27 - 0
fs-service/src/main/java/com/fs/aiSoundReplication/entity/dto/OrderAccessResourcePacksResp.java

@@ -0,0 +1,27 @@
+package com.fs.aiSoundReplication.entity.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 下单/续费响应
+ */
+@Data
+public class OrderAccessResourcePacksResp {
+    @JsonProperty("ResponseMetadata")
+    private BatchListMegaTTSTrainStatusResp.ResponseMetadata responseMetadata;
+    
+    @JsonProperty("Result")
+    private OrderResult result;
+    
+    @Data
+    public static class OrderResult {
+        /**
+         * 购买成功返回的订单号ID列表
+         */
+        @JsonProperty("OrderIDs")
+        private List<String> orderIds;
+    }
+}

+ 36 - 0
fs-service/src/main/java/com/fs/aiSoundReplication/entity/dto/RenewAccessResourcePacksReq.java

@@ -0,0 +1,36 @@
+package com.fs.aiSoundReplication.entity.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 音色续费请求
+ */
+@Data
+public class RenewAccessResourcePacksReq {
+    /**
+     * 续费音色的时长,单位为月
+     */
+    @JsonProperty("Times")
+    private Integer times;
+    
+    /**
+     * 要续费的SpeakerID的列表,可以通过BatchListMegaTTSTrainStatus接口过滤获取
+     */
+    @JsonProperty("SpeakerIDs")
+    private List<String> speakerIds;
+    
+    /**
+     * 是否自动使用代金券,默认false
+     */
+    @JsonProperty("AutoUseCoupon")
+    private Boolean autoUseCoupon = false;
+    
+    /**
+     * 代金券ID,通过代金券管理获取
+     */
+    @JsonProperty("CouponID")
+    private String couponId;
+}

+ 21 - 0
fs-service/src/main/java/com/fs/aiSoundReplication/service/VolcEngineSpeechService.java

@@ -0,0 +1,21 @@
+package com.fs.aiSoundReplication.service;
+
+import com.fs.aiSoundReplication.entity.dto.*;
+
+
+public interface VolcEngineSpeechService {
+    /**
+     * 分页查询SpeakerID状态
+     */
+    public BatchListMegaTTSTrainStatusResp batchListMegaTTSTrainStatus(BatchListMegaTTSTrainStatusReq req);
+
+    /**
+     * 音色下单
+     */
+    public OrderAccessResourcePacksResp orderAccessResourcePacks(OrderAccessResourcePacksReq req);
+
+    /**
+     * 音色续费
+     */
+    public OrderAccessResourcePacksResp renewAccessResourcePacks(RenewAccessResourcePacksReq req);
+}

+ 83 - 0
fs-service/src/main/java/com/fs/aiSoundReplication/service/impl/VolcEngineSpeechServiceImpl.java

@@ -0,0 +1,83 @@
+package com.fs.aiSoundReplication.service.impl;
+
+import com.fs.aiSoundReplication.client.VolcEngineApiClient;
+import com.fs.aiSoundReplication.entity.dto.*;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.aiSoundReplication.service.VolcEngineSpeechService;
+import com.fs.common.exception.CustomException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+
+@Slf4j
+@Service
+public class VolcEngineSpeechServiceImpl implements VolcEngineSpeechService {
+
+    @Autowired
+    private VolcEngineApiClient apiClient;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    /**
+     * 分页查询SpeakerID状态
+     */
+    public BatchListMegaTTSTrainStatusResp batchListMegaTTSTrainStatus(BatchListMegaTTSTrainStatusReq req) {
+        try {
+            //火山引擎-音色管理API固定公共参数
+            String response = apiClient.doRequest(
+                    "POST",
+                    req,
+                    new Date(),
+                    "BatchListMegaTTSTrainStatus",
+                    "2023-11-07"
+            );
+            
+            return objectMapper.readValue(response, BatchListMegaTTSTrainStatusResp.class);
+        } catch (Exception e) {
+            log.error("批量查询音色状态失败", e);
+            throw new CustomException("批量查询音色状态失败", e);
+        }
+    }
+
+    /**
+     * 音色下单
+     */
+    public OrderAccessResourcePacksResp orderAccessResourcePacks(OrderAccessResourcePacksReq req) {
+        try {
+            String response = apiClient.doRequest(
+                    "POST",
+                    req,
+                    new Date(),
+                    "OrderAccessResourcePacks",
+                    "2023-11-07"
+            );
+            
+            return objectMapper.readValue(response, OrderAccessResourcePacksResp.class);
+        } catch (Exception e) {
+            log.error("音色下单失败", e);
+            throw new CustomException("音色下单失败", e);
+        }
+    }
+
+    /**
+     * 音色续费
+     */
+    public OrderAccessResourcePacksResp renewAccessResourcePacks(RenewAccessResourcePacksReq req) {
+        try {
+            String response = apiClient.doRequest(
+                    "POST",
+                    req,
+                    new Date(),
+                    "RenewAccessResourcePacks",
+                    "2023-11-07"
+            );
+            
+            return objectMapper.readValue(response, OrderAccessResourcePacksResp.class);
+        } catch (Exception e) {
+            log.error("音色续费失败", e);
+            throw new CustomException("音色续费失败", e);
+        }
+    }
+}

+ 5 - 2
fs-service/src/main/java/com/fs/company/service/impl/call/node/AbstractWorkflowNode.java

@@ -164,9 +164,12 @@ public abstract class AbstractWorkflowNode implements IWorkflowNode {
             if (lock != null && lock.isHeldByCurrentThread()) {
                 if(getType().equals(NodeTypeEnum.AI_CALL_TASK)){
                     updateLogStatusIfExist(context,ExecutionStatusEnum.PAUSED,ExecutionStatusEnum.SUCCESS);
-                }
-                else if(getType().equals(NodeTypeEnum.AI_ADD_WX_TASK)){
+                } else if(getType().equals(NodeTypeEnum.AI_CALL_TASK)){
+                    updateLogStatusIfExist(context,ExecutionStatusEnum.WAITING_DO_CALL,ExecutionStatusEnum.SUCCESS);
+                } else if(getType().equals(NodeTypeEnum.AI_ADD_WX_TASK)){
                     updateLogStatusIfExist(context,ExecutionStatusEnum.WAITING,ExecutionStatusEnum.SUCCESS);
+                } else if (getType().equals(NodeTypeEnum.OUTBOUND_TASK)) {
+                    updateLogStatusIfExist(context,ExecutionStatusEnum.WAITING_DO_CALL,ExecutionStatusEnum.SUCCESS);
                 }
 
 //                //更新流程日志信息

+ 2 - 0
fs-service/src/main/java/com/fs/course/param/FsUserVideoListUParam.java

@@ -12,4 +12,6 @@ public class FsUserVideoListUParam extends BaseParam implements Serializable {
 
     Long videoId;
 
+    String keyword;
+
 }

+ 9 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
 import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.domain.LuckyBagCollectRecord;
 import com.fs.course.param.*;
 import com.fs.course.param.newfs.FsUserCourseAddCompanyUserParam;
 import com.fs.course.param.newfs.FsUserCourseVideoLinkParam;
@@ -274,4 +275,12 @@ public interface IFsUserCourseVideoService extends IService<FsUserCourseVideo> {
     ResponseResult<FsUserCourseVideoLinkDetailsVO> getXiaoShouYiCourseVideoDetails(FsUserCourseVideoLinkParam param);
 
     R registerQwFsUserFinish(FsUserCourseVideoAddKfUParam param);
+
+
+    /**
+     * 创建app福袋
+     * @param param
+     * @return
+     */
+    R createAppFd(LuckyBagCollectRecord param);
 }

+ 146 - 4
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.course.service.impl;
 
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
@@ -71,10 +72,7 @@ import com.fs.qw.domain.*;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwUser;
-import com.fs.qw.mapper.QwExternalContactMapper;
-import com.fs.qw.mapper.QwGroupChatMapper;
-import com.fs.qw.mapper.QwGroupChatUserMapper;
-import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.mapper.*;
 import com.fs.qw.param.FsUserCourseRedPageParam;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwExternalContactService;
@@ -132,6 +130,11 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
     @Value("${isNewWxMerchant}")
     private Boolean isNewWxMerchant;
 
+    @Autowired
+    private LuckyBagMapper luckyBagMapper;
+
+    @Autowired
+    private LuckyBagCollectRecordMapper luckyBagCollectRecordMapper;
     @Autowired
     private OpenIMService openIMService;
     @Autowired
@@ -4770,5 +4773,144 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
 
         return R.ok();
     }
+
+    @Override
+    public R createAppFd(LuckyBagCollectRecord param) {
+
+        try {
+            CompanyUser companyUser = companyUserMapper.selectCompanyUserByCompanyUserId(param.getCompanyUserId());
+            if (companyUser==null||companyUser.getCompanyId()==null||companyUser.getUserId()==null){
+                return R.error("员工不存在!");
+            }
+
+            Company company = companyMapper.selectCompanyById(param.getCompanyId());
+            if (company == null ) {
+                return  R.error().put("msg","企业不存在,请联系管理员");
+            }
+            String course = configService.selectConfigByKey("course.config");
+            CourseConfig config = JSON.parseObject(course, CourseConfig.class);
+            LocalDateTime sendDateTime = new Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+            LocalDateTime expireDateTime = sendDateTime.plusDays(config.getVideoLinkExpireDate() - 1);
+            expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
+            Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
+
+            Map<String,Object> business = addLuckyBagCollectRecord(param.getLuckyBagId(),param.getUserId(),updateTime,String.valueOf(param.getCompanyUserId()),String.valueOf(param.getCompanyId()));
+            String json = configService.selectConfigByKey("luckyBag.config");
+            Map<String, Object> luckyBagConfig = JSON.parseObject(json, Map.class);
+            business.put("img",luckyBagConfig.get("miniprogramPicUrl"));
+            return R.ok().put("data",business);
+        }catch (Exception e){
+            e.printStackTrace();
+            return R.error("发放福袋失败!");
+        }
+    }
+
+
+    private Map<String,Object> addLuckyBagCollectRecord(Long luckyBagId,
+                                                        Long userId,
+                                                        Date sendTime,
+                                                        String companyUserId,
+                                                        String companyId) {
+        try {
+
+            if (StringUtils.isEmpty(companyId) || StringUtils.isEmpty(companyUserId)) {
+                log.warn("公司ID或用户ID为空 [companyId:{}, companyUserId:{}]", companyId, companyUserId);
+                return null;
+            }
+
+            // 验证福袋ID
+            if (luckyBagId == null) {
+                log.warn("福袋ID为空");
+                return null;
+            }
+
+            // 查询福袋信息
+            LuckyBag luckyBag = luckyBagMapper.selectLuckyBagById(luckyBagId);
+            if (luckyBag == null) {
+                log.warn("未找到对应的福袋信息 [luckyBagId:{}]", luckyBagId);
+                return null;
+            }
+
+            // 检查福袋状态
+            if (luckyBag.getDataStatus() != null && luckyBag.getDataStatus().equals(0)) {
+                log.warn("福袋被禁用 [luckyBagId:{}]", luckyBagId);
+                return null;
+            }
+
+            // 查询公司信息
+            Company company = companyMapper.selectCompanyById(Long.valueOf(companyId));
+            if (company == null) {
+                log.warn("未找到对应的公司信息 [companyId:{}]", companyId);
+                return null;
+            }
+
+            LuckyBagCollectRecord luckyBagCollectRecord = buildLuckyBagRecord(luckyBagId,userId,sendTime,
+                    companyUserId, companyId, company, luckyBag);
+
+            // 插入记录并返回ID
+            int result = luckyBagCollectRecordMapper.insertLuckyBagCollectRecord(luckyBagCollectRecord);
+            if (result <= 0) {
+                log.warn("福袋记录插入失败 [luckyBagId:{}]", luckyBagId);
+                return null;
+            }
+
+            // 返回新增记录的ID
+            Long recordId = luckyBagCollectRecord.getId();
+            if (recordId == null) {
+                log.warn("福袋记录插入成功但未返回ID [luckyBagId:{}]", luckyBagId);
+                return null;
+            }
+
+            log.info("福袋记录添加成功 [recordId:{}, luckyBagId:{}]", recordId, luckyBagId);
+            Map<String,Object> map = new HashMap<>();
+            map.put("recordId",recordId);
+            map.put("luckyBag",luckyBag);
+            return map;
+
+        } catch (NumberFormatException e) {
+            log.error("ID转换失败 [companyId:{}, companyUserId:{}]", companyId, companyUserId, e);
+            return null;
+        } catch (Exception e) {
+            log.error("ID:" + (luckyBagId != null ? luckyBagId : "unknown") + "-添加福袋记录失败", e);
+            return null;
+        }
+    }
+
+    /**
+     * 构建福袋记录对象
+     */
+    private LuckyBagCollectRecord buildLuckyBagRecord(Long luckyBagId,
+                                                      Long userId,
+                                                      Date sendTime,
+                                                      String companyUserId,
+                                                      String companyId,
+                                                      Company company,
+                                                      LuckyBag luckyBag) {
+        LuckyBagCollectRecord record = new LuckyBagCollectRecord();
+        record.setLuckyBagId(luckyBagId);
+        record.setExpiryTime(sendTime);
+        record.setCollectType("0");
+        record.setSendTime(new Date());
+        record.setCompanyId(Long.valueOf(companyId));
+        record.setUserId(userId);
+        if (ObjectUtil.isNotEmpty(userId)){
+            FsUser fsUser = fsUserMapper.selectFsUserByUserId(userId);
+            record.setUserName(ObjectUtil.isNotEmpty(fsUser)?fsUser.getNickName():null);
+        }
+        record.setCompanyName(company.getCompanyName());
+        record.setCompanyUserId(Long.valueOf(companyUserId));
+
+        record.setRewardType(2L);
+
+        // 设置币种金额
+        if (luckyBag.getRewardType() != null && luckyBag.getRewardType().equals("1")) {
+            record.setCoinAmount(luckyBag.getAmount());
+        }
+
+        return record;
+    }
+
+
+
 }
 

+ 5 - 0
fs-service/src/main/java/com/fs/his/config/StoreConfig.java

@@ -3,6 +3,7 @@ package com.fs.his.config;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.math.BigDecimal;
 
 @Data
 public class StoreConfig implements Serializable {
@@ -23,4 +24,8 @@ public class StoreConfig implements Serializable {
     private String HASHCODE;
     private String SECRET_KEY;
 
+    private String payPriceTip;//支付金额达限提示
+    private BigDecimal yearPayPrice;//年支付金额限制
+    private BigDecimal canPayPrice;//达限后支付限制金额
+
 }

+ 12 - 0
fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.math.BigDecimal;
@@ -130,4 +131,15 @@ public class FsIntegralOrder
     private Integer deliveryStatus;
     @TableField(exist = false)
     private String deliveryType;
+
+    private String extendOrderId;
+    @ApiModelProperty("购物车中的商品信息(多个)")
+    private String itemCartJson;
+    @ApiModelProperty("购物车中的商品对应积分(多个)")
+    private String integralByCart;
+    @ApiModelProperty("购物车中对应的商品编码(多个)")
+    private String barCodeCart;
+    @ApiModelProperty("商品对应的数量(多个)")
+    private String quantityCart;
+
 }

+ 11 - 0
fs-service/src/main/java/com/fs/his/domain/FsUser.java

@@ -251,6 +251,17 @@ public class FsUser extends BaseEntity
 //    @TableField(exist = false)
 //    private String keywords;
 
+
+    // 可提现积分
+    private Long withdrawIntegral;
+
+    // 可提现佣金
+    private BigDecimal mayWithdraw;
+    // 累计佣金
+    private BigDecimal totalCommission;
+    // 已提现佣金
+    private BigDecimal withdrawFinish;
+
     public String getNickname() {
         return nickname;
     }

+ 4 - 4
fs-service/src/main/java/com/fs/his/mapper/FsIntegralGoodsMapper.java

@@ -2,10 +2,7 @@ package com.fs.his.mapper;
 
 import com.fs.his.domain.FsIntegralGoods;
 import com.fs.his.param.FsIntegralGoodsListUParam;
-import com.fs.his.vo.FsGoodsVO;
-import com.fs.his.vo.FsIntegralGoodsChooseVO;
-import com.fs.his.vo.FsIntegralGoodsListUVO;
-import com.fs.his.vo.FsIntegralGoodsListVO;
+import com.fs.his.vo.*;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
@@ -13,6 +10,7 @@ import org.apache.ibatis.annotations.Update;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * 积分商品Mapper接口
@@ -121,4 +119,6 @@ public interface FsIntegralGoodsMapper
      * 根据id集合查询积分商品列表
      */
     List<FsGoodsVO> getFsGoodsVOListByIds(@Param("goodsIds") List<Long> goodsIds);
+
+    List<FsIntegralGoodsVo> selectAllByGoodsIds(@Param("goodsIds") Set<Long> goodsIds);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderItemMapper.java

@@ -1,5 +1,6 @@
 package com.fs.his.mapper;
 
+import java.math.BigDecimal;
 import java.util.List;
 import com.fs.his.domain.FsStoreOrderItem;
 import com.fs.his.vo.FsStoreOrderItemExcelVO;
@@ -75,4 +76,6 @@ public interface FsStoreOrderItemMapper
     List<FsStoreOrderItemListDVO> selectFsStoreOrderItemListDVOByOrderId(Long orderId);
 
     List<FsStoreOrderItem> selectFsStoreOrderItemListByItemIds(@Param("itemIds") List<Long> itemIds);
+
+    BigDecimal selectPayPriceByYear(@Param("userId") String userId);
 }

+ 18 - 0
fs-service/src/main/java/com/fs/his/param/CreateOrderFromCartParm.java

@@ -0,0 +1,18 @@
+package com.fs.his.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Data
+public class CreateOrderFromCartParm {
+    @NotNull(message = "收货地址ID不能为空")
+    @ApiModelProperty("收货地址ID")
+    private Long addressId;
+
+    @NotNull(message = "积分商品ID不能为空")
+    @ApiModelProperty("积分商品ID")
+    private List<Long> goodsId;
+}

+ 3 - 0
fs-service/src/main/java/com/fs/his/service/IFsIntegralCartService.java

@@ -1,8 +1,10 @@
 package com.fs.his.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.common.core.domain.R;
 import com.fs.his.domain.FsIntegralCart;
 import com.fs.his.param.AddGoodsIntoCartParam;
+import com.fs.his.param.CreateOrderFromCartParm;
 import com.fs.his.param.GetFsIntegralCartDetailsParm;
 import com.fs.his.param.GetFsIntegralCartListParam;
 import com.fs.his.vo.FsIntegralCartVO;
@@ -60,4 +62,5 @@ public interface IFsIntegralCartService extends IService<FsIntegralCart> {
 
     Boolean addGoodsIntoCart(AddGoodsIntoCartParam param, Long userId);
 
+    R createOrderFromCart(CreateOrderFromCartParm param, Long aLong);
 }

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

@@ -288,4 +288,6 @@ public interface IFsStoreOrderService
     void weizouPush(Long l) throws IOException;
 
     int sendGoodsWeizou(FsStoreOrder fsStoreOrder, String opeName);
+
+    BigDecimal selectPayPriceByYear(String userId);
 }

+ 128 - 13
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralCartServiceImpl.java

@@ -1,29 +1,28 @@
 package com.fs.his.service.impl;
 
+import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
 import com.fs.common.exception.ServiceException;
-import com.fs.his.domain.FsIntegralCart;
-import com.fs.his.domain.FsIntegralGoods;
-import com.fs.his.domain.FsUser;
-import com.fs.his.domain.FsUserAddress;
-import com.fs.his.mapper.FsIntegralCartMapper;
-import com.fs.his.mapper.FsIntegralGoodsMapper;
-import com.fs.his.mapper.FsUserAddressMapper;
-import com.fs.his.mapper.FsUserMapper;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.core.utils.OrderCodeUtils;
+import com.fs.his.domain.*;
+import com.fs.his.mapper.*;
 import com.fs.his.param.AddGoodsIntoCartParam;
+import com.fs.his.param.CreateOrderFromCartParm;
 import com.fs.his.param.GetFsIntegralCartDetailsParm;
 import com.fs.his.param.GetFsIntegralCartListParam;
 import com.fs.his.service.IFsIntegralCartService;
-import com.fs.his.vo.FsIntegralCartVO;
-import com.fs.his.vo.GetCartGoodsDetailsVo;
-import com.fs.his.vo.GetFsIntegralCartDetailsVo;
-import com.fs.his.vo.GetFsIntegralCartListVo;
+import com.fs.his.service.IFsUserIntegralLogsService;
+import com.fs.his.vo.*;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.ObjectUtils;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.DuplicateKeyException;
@@ -34,6 +33,8 @@ import javax.annotation.Resource;
 import javax.validation.constraints.NotNull;
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 @Slf4j
 @Service
@@ -44,12 +45,24 @@ public class FsIntegralCartServiceImpl extends ServiceImpl<FsIntegralCartMapper,
     @Resource
     private FsUserMapper userMapper;
 
+    @Autowired
+    private FsUserMapper fsUserMapper;
+
     @Autowired
     private FsUserAddressMapper fsUserAddressMapper;
 
     @Autowired
     private FsIntegralGoodsMapper fsIntegralGoodsMapper;
 
+    @Autowired
+    private FsIntegralCartMapper fsIntegralCartMapper;
+
+    @Autowired
+    private FsIntegralOrderMapper fsIntegralOrderMapper;
+
+    @Autowired
+    IFsUserIntegralLogsService integralLogsService;
+
     /**
      * 添加或修改购物车
      *
@@ -165,7 +178,7 @@ public class FsIntegralCartServiceImpl extends ServiceImpl<FsIntegralCartMapper,
         for (GetFsIntegralCartListVo listVo : cartListVoList) {
             GetCartGoodsDetailsVo cartListVo = new GetCartGoodsDetailsVo();
             BeanUtils.copyProperties(listVo, cartListVo);
-            cartListVo.setGoodsIntegralTotal(listVo.getGoodsIntegral() * listVo.getQuantity());
+            cartListVo.setGoodsIntegralTotal(listVo.getGoodsIntegral() * listVo.getCartNum());
             goodsDetailsVos.add(cartListVo);
         }
         if (ObjectUtils.isNotEmpty(param.getAddressId())) {
@@ -230,4 +243,106 @@ public class FsIntegralCartServiceImpl extends ServiceImpl<FsIntegralCartMapper,
             }
         }
     }
+
+    @Override
+    public R createOrderFromCart(CreateOrderFromCartParm param, Long userId) {
+        RedissonClient redissonClient = SpringUtils.getBean(RedissonClient.class);
+        String lockKey = "fsIntegralCartOrderCreate:" + userId;
+        RLock lock = redissonClient.getLock(lockKey);
+        try {
+            // 尝试获取锁,最多等待3秒,持有锁时间最多30秒
+            boolean isLocked = lock.tryLock(3, 10, TimeUnit.SECONDS);
+            if (!isLocked) {
+                return R.error("系统繁忙,请稍后再试");
+            }
+            FsUser user = fsUserMapper.selectFsUserByUserId(userId);
+            FsUserAddress address = fsUserAddressMapper.selectFsUserAddressByAddressId(param.getAddressId());
+            List<FsIntegralGoodsVo> fsIntegralGoods = fsIntegralGoodsMapper.selectAllByGoodsIds(new HashSet<>(param.getGoodsId()));
+            List<FsIntegralCart> existingCart = fsIntegralCartMapper.selectList(Wrappers.<FsIntegralCart>lambdaQuery().eq(FsIntegralCart::getUserId, userId).in(FsIntegralCart::getGoodsId, param.getGoodsId()));
+            Map<Long, Integer> collect = existingCart.stream().collect(Collectors.groupingBy(FsIntegralCart::getGoodsId, Collectors.summingInt(FsIntegralCart::getCartNum)));
+            StringBuilder quantity = new StringBuilder();
+            for (FsIntegralGoodsVo fsIntegralGood : fsIntegralGoods) {
+                Integer integer = collect.get(fsIntegralGood.getGoodsId());
+                if (fsIntegralGood.getStock() < integer) {
+                    throw new ServiceException(String.format("%d库存不足,兑换失败", fsIntegralGood.getGoodsName()));
+                }
+                if (fsIntegralGood.getStatus() != 1) {
+                    this.remove(Wrappers.<FsIntegralCart>lambdaQuery().eq(FsIntegralCart::getUserId, userId).eq(FsIntegralCart::getGoodsId, fsIntegralGood.getGoodsId()));
+                    log.info("清除下架商品,userId:{}, goodsId:{}", userId, fsIntegralGood.getGoodsId());
+                    throw new ServiceException(String.format("商品[名称:%d]已下架,兑换失败", fsIntegralGood.getGoodsName()));
+                }
+                fsIntegralGood.setIntegralByNum(integer * fsIntegralGood.getIntegral());
+                fsIntegralGood.setQuantity(integer);
+                if (quantity.length() > 0) {
+                    quantity.append(",");
+                }
+                quantity.append(ObjectUtils.isNotEmpty(integer) ? integer : "0");
+            }
+            // 商品总的积分
+            Long goodsIntegral = fsIntegralGoods.stream().filter(n -> ObjectUtils.isNotEmpty(n.getIntegralByNum())).mapToLong(FsIntegralGoodsVo::getIntegralByNum).sum();
+            if (user.getIntegral() < goodsIntegral) {
+                throw new ServiceException("用户积分不足,兑换失败");
+            }
+            String barCode = fsIntegralGoods.stream().map(FsIntegralGoodsVo::getBarCode).collect(Collectors.joining(","));
+            String Integral = fsIntegralGoods.stream().map(m -> m.getIntegral().toString()).collect(Collectors.joining(","));
+//            String orderSn = OrderCodeUtils.getOrderSn();
+            String orderSn = "6666666666666666666666";
+            if (StringUtils.isEmpty(orderSn)) {
+                throw new ServiceException("订单生成失败,请重试");
+            }
+            FsIntegralOrder order = new FsIntegralOrder();
+            order.setOrderCode(orderSn);
+            order.setUserId(user.getUserId());
+            order.setStatus(1);
+            order.setBarCodeCart(barCode);
+            order.setIntegral(goodsIntegral.toString());
+            order.setIntegralByCart(Integral);
+            order.setItemJson(ObjectUtils.isNotEmpty(fsIntegralGoods) ? JSONUtil.toJsonStr(fsIntegralGoods) : null);
+            order.setItemCartJson(ObjectUtils.isNotEmpty(fsIntegralGoods) ? JSONUtil.toJsonStr(fsIntegralGoods) : null);
+            order.setUserName(address.getRealName());
+            order.setUserAddress(address.getProvince() + address.getCity() + address.getDistrict() + address.getDetail());
+            order.setUserPhone(address.getPhone());
+            order.setCreateTime(new Date());
+            order.setQuantityCart(quantity.toString());
+            if (fsIntegralOrderMapper.insertFsIntegralOrder(order) > 0) {
+                //写入日志
+                FsUser userMap = new FsUser();
+                userMap.setUserId(user.getUserId());
+                // 可消费积分
+                long consumer = user.getIntegral() - user.getWithdrawIntegral();
+                if (consumer < goodsIntegral) {
+                    // 扣除完可消费积分后,剩余的积分
+                    long extra = goodsIntegral - consumer;
+                    // 可提现积分扣除 剩余积分
+                    Long withdrawIntegral = user.getWithdrawIntegral() - extra;
+                    userMap.setIntegral(withdrawIntegral);
+                    userMap.setWithdrawIntegral(withdrawIntegral);
+                } else {
+                    userMap.setIntegral(user.getIntegral() - goodsIntegral);
+                }
+                fsUserMapper.updateFsUser(userMap);
+                FsUserIntegralLogs logs = new FsUserIntegralLogs();
+                logs.setIntegral(-goodsIntegral);
+                logs.setUserId(order.getUserId());
+                logs.setBalance(userMap.getIntegral());
+                logs.setLogType(5);
+                logs.setBusinessId(order.getOrderId().toString());
+                logs.setCreateTime(new Date());
+                logs.setNickName(user.getNickName());
+                logs.setPhone(user.getPhone());
+                integralLogsService.insertFsUserIntegralLogs(logs);
+                //清空购物车对应商品
+                this.remove(Wrappers.<FsIntegralCart>lambdaQuery().eq(FsIntegralCart::getUserId, userId).in(FsIntegralCart::getGoodsId, param.getGoodsId()));
+                return R.ok("兑换成功").put("order", order);
+            } else {
+                return R.error("订单创建失败");
+            }
+        } catch (Exception e) {
+            return R.error(e.getMessage());
+        } finally {
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        }
+    }
 }

+ 5 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java

@@ -4666,4 +4666,9 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
         }
 
     }
+
+    @Override
+    public BigDecimal selectPayPriceByYear(String userId){
+        return fsStoreOrderItemMapper.selectPayPriceByYear(userId);
+    }
 }

+ 50 - 0
fs-service/src/main/java/com/fs/his/vo/FsIntegralGoodsVo.java

@@ -0,0 +1,50 @@
+package com.fs.his.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class FsIntegralGoodsVo {
+    private Long goodsId;
+
+    @ApiModelProperty("封面图")
+    private String imgUrl;
+
+    @ApiModelProperty("组图")
+    private String images;
+
+    @ApiModelProperty("商品名称")
+    private String goodsName;
+
+    @ApiModelProperty("原价")
+    private BigDecimal otPrice;
+
+    @ApiModelProperty("商品分类")
+    private Long goodsType;
+
+    @ApiModelProperty("状态")
+    private Long status;
+
+    @ApiModelProperty("单个商品积分")
+    private Long integral;
+
+    @ApiModelProperty("排序")
+    private Long sort;
+
+    @ApiModelProperty("库存")
+    private Long stock;
+
+    @ApiModelProperty("详情")
+    private String descs;
+
+    @ApiModelProperty("产品编码")
+    private String barCode;
+
+    @ApiModelProperty("加上数量的商品积分")
+    private Long integralByNum;
+
+    @ApiModelProperty("商品数量")
+    private Integer quantity;
+}

+ 3 - 0
fs-service/src/main/java/com/fs/his/vo/GetCartGoodsDetailsVo.java

@@ -32,6 +32,9 @@ public class GetCartGoodsDetailsVo {
     @ApiModelProperty("商品数量")
     private Integer quantity;
 
+    @ApiModelProperty("购买的商品数量")
+    private Integer cartNum;
+
     @ApiModelProperty("是否选中:1是,0否")
     private Integer isSelected;
 

+ 7 - 0
fs-service/src/main/java/com/fs/his/vo/GetFsIntegralCartListVo.java

@@ -7,6 +7,10 @@ import java.math.BigDecimal;
 
 @Data
 public class GetFsIntegralCartListVo {
+
+    @ApiModelProperty("主键ID")
+    private Long Id;
+
     @ApiModelProperty("主键ID")
     private Long cartId;
 
@@ -49,6 +53,9 @@ public class GetFsIntegralCartListVo {
     @ApiModelProperty("商品数量")
     private Integer quantity;
 
+    @ApiModelProperty("购买的商品数量")
+    private Integer cartNum;
+
     @ApiModelProperty("用户的芳华币总数")
     private BigDecimal userIntegral;
 

+ 10 - 0
fs-service/src/main/java/com/fs/im/service/OpenIMService.java

@@ -98,4 +98,14 @@ public interface OpenIMService {
 
     OpenImResponseDTO doctorSendMsgToUser(Long userId,Long doctorId);
 
+    OpenImResponseDTO getFriendList(String userID, int pageNumber, int showNumber,Integer applyType);
+
+    /**
+     * 删除好友并拉黑
+     *
+     * @param ownerUserID 需要删除好友的用户
+     * @param friendUserID 待删除的好友
+     * @return
+     */
+    OpenImResponseDTO deleteUserInfo(String ownerUserID,String friendUserID);
 }

+ 71 - 0
fs-service/src/main/java/com/fs/im/service/impl/OpenIMServiceImpl.java

@@ -1781,4 +1781,75 @@ public class OpenIMServiceImpl implements OpenIMService {
         openImMsgDTO.setContent(content);
         return openIMSendMsg(openImMsgDTO);
     }
+
+    @Override
+    public OpenImResponseDTO getFriendList(String userID, int pageNumber, int showNumber,Integer applyType) {
+        String adminToken = getAdminToken();
+        JSONObject jsonObject = new JSONObject();
+        Map<String,Object> map = new HashMap<>();
+        map.put("pageNumber", pageNumber);
+        map.put("showNumber", showNumber);
+        jsonObject.put("userID",userID);
+        jsonObject.put("pagination",map);
+        if (applyType == 1){
+            String body = HttpRequest.post(IMConfig.URL+"/friend/get_friend_list")
+                    .header("operationID", String.valueOf(System.currentTimeMillis()))
+                    .header("token", adminToken)
+                    .body(jsonObject.toString())
+                    .execute()
+                    .body();
+            OpenImResponseDTO responseDTO= JSONUtil.toBean(body,OpenImResponseDTO.class);
+            return responseDTO;
+        }else if (applyType == 2){
+            String body = HttpRequest.post(IMConfig.URL+"/friend/get_self_friend_apply_list")
+                    .header("operationID", String.valueOf(System.currentTimeMillis()))
+                    .header("token", adminToken)
+                    .body(jsonObject.toString())
+                    .execute()
+                    .body();
+            OpenImResponseDTO responseDTO= JSONUtil.toBean(body,OpenImResponseDTO.class);
+            return responseDTO;
+        }else if (applyType == 3){
+            String body = HttpRequest.post(IMConfig.URL+"/friend/get_friend_apply_list")
+                    .header("operationID", String.valueOf(System.currentTimeMillis()))
+                    .header("token", adminToken)
+                    .body(jsonObject.toString())
+                    .execute()
+                    .body();
+            OpenImResponseDTO responseDTO= JSONUtil.toBean(body,OpenImResponseDTO.class);
+            return responseDTO;
+        }
+        return null;
+    }
+
+    @Override
+    public OpenImResponseDTO deleteUserInfo(String ownerUserID,String friendUserID) {
+        OpenImResponseDTO responseDTO = null;
+        String adminToken = getAdminToken();
+        //删除好友
+        Map<String, Object> bodyMap = new HashMap<>();
+        bodyMap.put("ownerUserID", ownerUserID);
+        bodyMap.put("friendUserID", friendUserID);
+        String jsonBody1 = JSONUtil.toJsonStr(bodyMap);
+        String result1 = HttpRequest.post(IMConfig.URL+"/friend/delete_friend")
+                .header("operationID", String.valueOf(System.currentTimeMillis()))
+                .header("token", adminToken)
+                .body(jsonBody1)
+                .execute()
+                .body();
+
+        //增加黑名单
+        Map<String, Object> bodyMap1 = new HashMap<>();
+        bodyMap1.put("ownerUserID", ownerUserID);
+        bodyMap1.put("blackUserID", friendUserID);
+        String jsonBody2 = JSONUtil.toJsonStr(bodyMap1);
+        String result2 = HttpRequest.post(IMConfig.URL+"/friend/add_black")
+                .header("operationID", String.valueOf(System.currentTimeMillis()))
+                .header("token", adminToken)
+                .body(jsonBody2)
+                .execute()
+                .body();
+        responseDTO= JSONUtil.toBean(result2,OpenImResponseDTO.class);
+        return responseDTO;
+    }
 }

+ 1 - 1
fs-service/src/main/resources/application-druid-mengniu.yml

@@ -168,7 +168,7 @@ token:
 openIM:
     secret: openIM123
     userID: imAdmin
-    url: https://mnIM.ylrzcloud.com
+    url: https://mnIM.ylrzcloud.com/api
 #是否使用新im
 im:
     type: OPENIM

+ 3 - 3
fs-service/src/main/resources/mapper/his/FsIntegralCartMapper.xml

@@ -94,7 +94,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and fg.goods_name like concat('%', #{param.goodsName}, '%')
             </if>
             <if test="cartId != null and cartId.size() > 0">
-                and fc.cart_id in
+                and fc.id in
                 <foreach collection="cartId" item="cartId" open="(" close=")" separator=",">#{cartId}</foreach>
             </if>
         </where>
@@ -103,10 +103,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <update id="updateQuantityAtomically">
         UPDATE fs_integral_cart
-        SET quantity = LEAST(quantity + #{addQuantity}, #{maxQuantity}),
+        SET cart_num = LEAST(cart_num + #{addQuantity}, #{maxQuantity}),
             is_selected = 1,
             update_time = NOW()
-        WHERE user_id = #{userId} AND goods_id = #{goodsId} AND quantity <![CDATA[<]]> #{maxQuantity}
+        WHERE user_id = #{userId} AND goods_id = #{goodsId} AND cart_num <![CDATA[<]]> #{maxQuantity}
     </update>
     <delete id="deleteCartByGoodsId">
         delete from fs_integral_cart where goods_id = #{goodsId}

+ 7 - 0
fs-service/src/main/resources/mapper/his/FsIntegralGoodsMapper.xml

@@ -174,4 +174,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{goodsId}
         </foreach>
     </select>
+
+    <select id="selectAllByGoodsIds" resultType="com.fs.his.vo.FsIntegralGoodsVo">
+        select * from fs_integral_goods where goods_id in
+        <foreach item="goodsIds" collection="goodsIds" open="(" separator="," close=")">
+            #{goodsIds}
+        </foreach>
+    </select>
 </mapper>

+ 10 - 0
fs-service/src/main/resources/mapper/his/FsIntegralOrderMapper.xml

@@ -162,6 +162,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="qwUserId != null">qw_user_id,</if>
             <if test="companyUserId != null">company_user_id,</if>
             <if test="companyId != null">company_id,</if>
+            <if test="extendOrderId != null">extend_order_id,</if>
+            <if test="itemCartJson != null">item_cart_json,</if>
+            <if test="integralByCart != null">integral_by_cart,</if>
+            <if test="barCodeCart != null">bar_code_cart,</if>
+            <if test="quantityCart != null">quantity_cart,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="orderId != null">#{orderId},</if>
@@ -187,6 +192,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="qwUserId != null">#{qwUserId},</if>
             <if test="companyUserId != null">#{companyUserId},</if>
             <if test="companyId != null">#{companyId},</if>
+            <if test="extendOrderId != null">#{extendOrderId},</if>
+            <if test="itemCartJson != null">#{itemCartJson},</if>
+            <if test="integralByCart != null">#{integralByCart},</if>
+            <if test="barCodeCart != null">#{barCodeCart},</if>
+            <if test="quantityCart != null">#{quantityCart},</if>
          </trim>
     </insert>
 

+ 9 - 0
fs-service/src/main/resources/mapper/his/FsStoreOrderItemMapper.xml

@@ -106,4 +106,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{itemId}
         </foreach>
     </delete>
+
+    <select id="selectPayPriceByYear" resultType="java.math.BigDecimal">
+        SELECT SUM(pay_price) AS total_pay
+        FROM fs_store_order
+        WHERE status NOT IN (1, -2)
+          AND user_id = #{userId}
+          AND pay_time >= CONCAT(YEAR(CURDATE()), '-01-01')
+          AND pay_time &lt; DATE_ADD(CONCAT(YEAR(CURDATE()), '-01-01'), INTERVAL 1 YEAR);
+    </select>
 </mapper>

+ 1 - 1
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -62,7 +62,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                tui_user_count, ma_open_id, mp_open_id, union_id, is_del, user_code, remark, create_time, update_time,
                last_ip, balance,is_weixin_auth,parent_id,qw_user_id,app_id,company_id,company_user_id,is_promoter,
                now_money,brokerage_price,spread_user_id, spread_time,pay_count, spread_count,user_type,
-               app_open_id,apple_key,history_app
+               app_open_id,apple_key,history_app,birthday,qw_user_id,withdraw_integral,total_commission,withdraw_finish,may_withdraw,app_open_id
         from fs_user
     </sql>
 

+ 77 - 1
fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java

@@ -3,14 +3,18 @@ package com.fs.app.controller;
 
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.date.DateTime;
+import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.app.annotation.Login;
 import com.fs.app.param.*;
 import com.fs.app.utils.WxUtil;
 import com.fs.common.VerifyCodeUtil;
+import com.fs.common.annotation.Log;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.enums.BusinessType;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.service.ISmsService;
 import com.fs.common.utils.ServletUtils;
@@ -18,7 +22,10 @@ import com.fs.common.utils.ip.IpUtils;
 import com.fs.common.utils.sign.Md5Utils;
 import com.fs.core.config.WxOpenProperties;
 import com.fs.course.domain.FsCoursePlaySourceConfig;
+import com.fs.course.domain.LuckyBag;
+import com.fs.course.domain.LuckyBagCollectRecord;
 import com.fs.course.mapper.FsCoursePlaySourceConfigMapper;
+import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUserNewTask;
@@ -28,6 +35,9 @@ import com.fs.his.service.IFsUserNewTaskService;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.FsUserRegisterParam;
+import com.fs.im.dto.OpenImResponseDTO;
+import com.fs.im.service.OpenIMService;
+import com.fs.qw.service.ILuckyBagService;
 import com.fs.watch.domain.WatchDeviceSetup;
 import com.fs.watch.domain.WatchUser;
 import com.fs.watch.service.WatchUserService;
@@ -67,6 +77,9 @@ public class AppLoginController extends AppBaseController{
     @Autowired
     private WxOpenProperties openProperties;
 
+    @Autowired
+    private OpenIMService openIMService;
+
     @Autowired
     private FsCoursePlaySourceConfigMapper fsCoursePlaySourceConfigMapper;
 
@@ -76,6 +89,12 @@ public class AppLoginController extends AppBaseController{
     @Autowired
     private IFsUserCouponService fsUserCouponService;
 
+    @Autowired
+    private IFsUserCourseVideoService fsUserCourseVideoService;
+
+    @Autowired
+    private ILuckyBagService luckyBagService;
+
     @Autowired
     private ISmsService smsService;
     @ApiOperation("注册app用户")
@@ -419,7 +438,7 @@ public class AppLoginController extends AppBaseController{
 
         userMap.setLoginDevice(param.getLoginDevice());
         userMap.setSource(param.getSource());
-        if (userMap.getNickName().equals("匿名用户**")) {
+        if ("匿名用户**".equals(userMap.getNickName())) {
             userMap.setNickName("苹果用户" + param.getPhone().substring(param.getPhone().length() - 4));
         }
         userMap.setAppleKey(param.getAppleKey());
@@ -1027,4 +1046,61 @@ public class AppLoginController extends AppBaseController{
         // 不存在,追加到末尾
         return currentAppIds + "," + newAppId;
     }
+
+
+    /**
+     * 创建 福袋
+     */
+
+    @RepeatSubmit
+    @PostMapping("/createFdMiniLink")
+    @Log(title = "createFdMiniLink", businessType = BusinessType.INSERT)
+    public R createAppFd(@RequestBody LuckyBagCollectRecord param) {
+
+        if (ObjectUtil.isEmpty(param.getLuckyBagId())){
+            return R.error("福袋id不能为空");
+        }
+        return fsUserCourseVideoService.createAppFd(param);
+    }
+    /**
+     * 查询福袋配置信息
+     *
+     * @param reward
+     * @return
+     */
+    @GetMapping("/luckyBagList")
+    public TableDataInfo list(LuckyBag reward)
+    {
+        reward.setDataStatus("1");
+
+        reward.setCompanyId(String.valueOf(reward.getCompanyId()));
+        startPage();
+        List<LuckyBag> list = luckyBagService.selectLuckyBagList(reward);
+        return getDataTable(list);
+    }
+
+
+    /**
+     * 获取好友申请列表
+     *
+     * @param userRequestParam
+     * @return
+     */
+    @PostMapping("/getFriendList")
+    public R getFriendList(@RequestBody UserRequestParam userRequestParam)
+    {
+        return R.ok().put("data",openIMService.getFriendList(userRequestParam.getUserID(),userRequestParam.getPagination().getPageNumber(),userRequestParam.getPagination().getShowNumber(),userRequestParam.getApplyType()));
+    }
+
+
+
+
+    @ApiOperation("删除好友并拉黑")
+    @PostMapping("/deleteFriend")
+    public R deleteUserInfo(@RequestBody HashMap<String,String> map){
+        String ownerUserID = map.get("ownerUserID");
+        String friendUserID =map.get("friendUserID");
+        OpenImResponseDTO openImResponseDTO = openIMService.deleteUserInfo(ownerUserID, friendUserID);
+        return R.ok().put("data",openImResponseDTO);
+    }
 }

+ 11 - 0
fs-user-app/src/main/java/com/fs/app/controller/FsIntegralCartController.java

@@ -2,9 +2,11 @@ package com.fs.app.controller;
 
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.app.annotation.Login;
+import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.his.domain.FsIntegralCart;
 import com.fs.his.param.AddGoodsIntoCartParam;
+import com.fs.his.param.CreateOrderFromCartParm;
 import com.fs.his.param.GetFsIntegralCartDetailsParm;
 import com.fs.his.param.GetFsIntegralCartListParam;
 import com.fs.his.service.IFsIntegralCartService;
@@ -84,4 +86,13 @@ public class FsIntegralCartController extends AppBaseController {
         return b ? R.ok("删除商品成功") : R.error("删除商品失败");
     }
 
+    @Login
+    @RepeatSubmit
+    @ApiOperation("从购物车生成订单(兑换按钮)")
+    @PostMapping("/createOrderFromCart")
+    public R createOrderFromCart(@RequestBody CreateOrderFromCartParm param) {
+        Long aLong = Long.valueOf(getUserId());
+        return fsIntegralCartService.createOrderFromCart(param, aLong);
+    }
+
 }

+ 34 - 0
fs-user-app/src/main/java/com/fs/app/controller/PackageController.java

@@ -1,6 +1,7 @@
 package com.fs.app.controller;
 
 
+import cn.hutool.json.JSONUtil;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -15,6 +16,7 @@ import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.course.domain.FsUserCourseFavorite;
 import com.fs.course.param.FsUserCourseFollowUParam;
+import com.fs.his.config.StoreConfig;
 import com.fs.his.domain.*;
 import com.fs.his.param.*;
 import com.fs.his.service.*;
@@ -22,6 +24,7 @@ import com.fs.his.vo.FsDiseaseListUVO;
 import com.fs.his.vo.FsDoctorListUVO;
 import com.fs.his.vo.FsPackageDetailVO;
 import com.fs.his.vo.FsPackageListUVO;
+import com.fs.system.service.ISysConfigService;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.jsonwebtoken.Claims;
@@ -40,6 +43,7 @@ import javax.imageio.ImageIO;
 import javax.servlet.http.HttpServletRequest;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.net.URL;
 import java.util.Date;
 import java.util.List;
@@ -65,6 +69,11 @@ public class PackageController extends AppBaseController {
     private ICompanyUserService companyUserService;
     @Autowired
     private ICompanyService companyService;
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private IFsStoreOrderService storeOrderService;
 
     @ApiOperation("获取套餐分类")
     @GetMapping("/getPackagCateList")
@@ -193,4 +202,29 @@ public class PackageController extends AppBaseController {
         }
     }
 
+
+    @ApiOperation("校验是否可继续下单")
+    @GetMapping("/checkContinue/{packageId}")
+    public R checkContinue(@PathVariable("packageId") Long packageId){
+        String userId = getUserId();
+        String json = configService.selectConfigByKey("his.store");
+        StoreConfig config = JSONUtil.toBean(json, StoreConfig.class);
+        BigDecimal canPayPrice = config.getCanPayPrice();
+        BigDecimal yearPayPrice = config.getYearPayPrice();
+        if(canPayPrice==null||yearPayPrice==null){
+            return R.ok();
+        }
+        String payPriceTip = config.getPayPriceTip();
+        //查询用户本年内支付处方订单+公私域套餐包总金额
+        FsPackage fsPackage = packageService.selectFsPackageByPackageId(packageId);
+        BigDecimal bigDecimal = storeOrderService.selectPayPriceByYear(userId);
+        if(bigDecimal==null){
+            return R.ok();
+        }
+        if (bigDecimal.compareTo(yearPayPrice)>=0&&fsPackage.getTotalPrice().compareTo(canPayPrice)>=0){
+            return R.ok().put("tip",payPriceTip);
+        }
+        return R.ok();
+    }
+
 }

+ 31 - 0
fs-user-app/src/main/java/com/fs/app/param/Pagination.java

@@ -0,0 +1,31 @@
+package com.fs.app.param;
+
+import lombok.Data;
+
+@Data
+public class Pagination {
+    private int pageNumber;
+    private int showNumber;
+
+    public Pagination(int pageNumber, int showNumber) {
+        this.pageNumber = pageNumber;
+        this.showNumber = showNumber;
+    }
+
+    // Getters and Setters
+    public int getPageNumber() {
+        return pageNumber;
+    }
+
+    public void setPageNumber(int pageNumber) {
+        this.pageNumber = pageNumber;
+    }
+
+    public int getShowNumber() {
+        return showNumber;
+    }
+
+    public void setShowNumber(int showNumber) {
+        this.showNumber = showNumber;
+    }
+}

+ 25 - 0
fs-user-app/src/main/java/com/fs/app/param/UserRequestParam.java

@@ -0,0 +1,25 @@
+package com.fs.app.param;
+
+import lombok.Data;
+
+@Data
+public class UserRequestParam {
+
+    /**
+     * 会员id
+     */
+    private String userID;
+
+    /**
+     * 好友申请类型
+     */
+    private Integer applyType;
+
+    /**
+     * 请求参数
+     */
+    private Pagination pagination;
+
+}
+
+