5 Commits a74ddb68ef ... c1d7b8923b

Autor SHA1 Mensagem Data
  yaorongkeji c1d7b8923b 添加后台登录记录显示 5 meses atrás
  yaorongkeji 74093b079b 完善 5 meses atrás
  yaorongkeji 30d63e3f2a 人脸登录优化 7 meses atrás
  yaorongkeji a2d2e83740 人脸登录优化 7 meses atrás
  yaorongkeji 4fed12818f 人脸登录 7 meses atrás
37 arquivos alterados com 2371 adições e 26 exclusões
  1. 57 0
      hcp-app/src/main/java/com/yingyangfly/app/controller/FaceController.java
  2. 18 4
      hcp-app/src/main/java/com/yingyangfly/app/controller/LoginController.java
  3. 40 0
      hcp-app/src/main/java/com/yingyangfly/app/controller/VoiceController.java
  4. 292 0
      hcp-app/src/main/java/com/yingyangfly/app/util/AsrMain.java
  5. 321 0
      hcp-app/src/main/java/com/yingyangfly/app/util/baidu/common/Base64Util.java
  6. 97 0
      hcp-app/src/main/java/com/yingyangfly/app/util/baidu/common/ConnUtil.java
  7. 7 0
      hcp-app/src/main/java/com/yingyangfly/app/util/baidu/common/DemoException.java
  8. 125 0
      hcp-app/src/main/java/com/yingyangfly/app/util/baidu/common/TokenHolder.java
  9. 8 0
      hcp-app/src/main/resources/application-dev.yml
  10. 8 0
      hcp-app/src/main/resources/application-prod.yml
  11. 2 0
      hcp-app/src/main/resources/application.yml
  12. 9 0
      hcp-app/src/test/java/com/yingyangfly/app/controller/VideoLearnControllerTest.java
  13. 365 0
      hcp-core/src/main/java/com/yingyangfly/core/api/impl/FaceContrastServer.java
  14. 25 0
      hcp-core/src/main/java/com/yingyangfly/core/bean/CreatePersonResult.java
  15. 68 0
      hcp-core/src/main/java/com/yingyangfly/core/bean/FaceSearchResult.java
  16. 72 0
      hcp-core/src/main/java/com/yingyangfly/core/domain/Face.java
  17. 49 0
      hcp-core/src/main/java/com/yingyangfly/core/domain/FaceVefLog.java
  18. 6 0
      hcp-core/src/main/java/com/yingyangfly/core/domain/LoginRecord.java
  19. 3 0
      hcp-core/src/main/java/com/yingyangfly/core/dto/SysOperLogDto.java
  20. 13 0
      hcp-core/src/main/java/com/yingyangfly/core/mapper/FaceMapper.java
  21. 11 0
      hcp-core/src/main/java/com/yingyangfly/core/mapper/FaceVefLogMapper.java
  22. 26 0
      hcp-core/src/main/java/com/yingyangfly/core/mapper/GameTaskMapper.java
  23. 56 0
      hcp-core/src/main/java/com/yingyangfly/core/service/FaceService.java
  24. 11 0
      hcp-core/src/main/java/com/yingyangfly/core/service/FaceVefLogService.java
  25. 5 4
      hcp-core/src/main/java/com/yingyangfly/core/service/LoginRecordService.java
  26. 73 0
      hcp-core/src/main/java/com/yingyangfly/core/service/impl/AppUserService.java
  27. 168 0
      hcp-core/src/main/java/com/yingyangfly/core/service/impl/FaceServiceImpl.java
  28. 5 17
      hcp-core/src/main/java/com/yingyangfly/core/service/impl/GameTaskServiceImpl.java
  29. 17 0
      hcp-core/src/main/java/com/yingyangfly/core/service/impl/LoginRecordServiceImpl.java
  30. 10 0
      hcp-core/src/main/java/com/yingyangfly/core/service/impl/SysOperLogServiceImpl.java
  31. 3 1
      hcp-core/src/main/java/com/yingyangfly/core/service/impl/SysUserService.java
  32. 279 0
      hcp-core/src/main/java/com/yingyangfly/core/util/OSSImageUtils.java
  33. 17 0
      hcp-core/src/main/java/com/yingyangfly/core/vo/AsrVo.java
  34. 53 0
      hcp-platform/src/main/java/com/yingyangfly/platform/controller/FaceContrller.java
  35. 34 0
      hcp-platform/src/main/java/com/yingyangfly/platform/controller/LoginRecordController.java
  36. 9 0
      hcp-platform/src/main/resources/application-dev.yml
  37. 9 0
      hcp-platform/src/main/resources/application-prod.yml

+ 57 - 0
hcp-app/src/main/java/com/yingyangfly/app/controller/FaceController.java

@@ -0,0 +1,57 @@
+package com.yingyangfly.app.controller;
+
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.yingyangfly.common.dto.ResultResponse;
+import com.yingyangfly.common.log.annotation.TraceLog;
+import com.yingyangfly.core.annotation.Log;
+import com.yingyangfly.core.domain.Face;
+import com.yingyangfly.core.dto.AppCurrentLoginUser;
+import com.yingyangfly.core.dto.CurrentLoginUser;
+import com.yingyangfly.core.enums.OperatorType;
+import com.yingyangfly.core.security.util.TokenUtil;
+import com.yingyangfly.core.service.FaceService;
+import io.swagger.annotations.Api;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@Slf4j
+@RestController
+@Api(tags = "人脸照片")
+@RequestMapping("/face")
+public class FaceController {
+
+    @Resource
+    private FaceService faceService;
+
+    @Autowired
+    private TokenUtil tokenUtil;
+
+    @TraceLog
+    @PostMapping("/getHumanFace")
+    public ResultResponse getHumanFace() {
+        AppCurrentLoginUser appCurrentLoginUser = tokenUtil.getAppCurrentLoginUser();
+        List<Face> faces = faceService.selectByUserId(appCurrentLoginUser.getId());
+
+        if (faces.size()>0) {
+            return ResultResponse.success(faces.get(0).getFaceBase());
+        }
+        return ResultResponse.success();
+    }
+
+
+    @Log(title = "人脸照片删除",operatorType = OperatorType.MOBILE)
+    @PostMapping("/deleteHumanFace")
+    @TraceLog
+    public ResultResponse deleteHumanFace(String faceUrl) throws TencentCloudSDKException {
+        AppCurrentLoginUser appCurrentLoginUser = tokenUtil.getAppCurrentLoginUser();
+        return ResultResponse.success(faceService.deleteFaceUrl(appCurrentLoginUser.getId(),faceUrl));
+    }
+
+
+}

+ 18 - 4
hcp-app/src/main/java/com/yingyangfly/app/controller/LoginController.java

@@ -11,11 +11,9 @@ import com.yingyangfly.core.service.impl.AppUserService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
 import org.springframework.util.StringUtils;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import java.time.LocalDate;
 
@@ -45,6 +43,22 @@ public class LoginController {
     }
 
 
+    @Log(title = "人脸登录",operatorType = OperatorType.MOBILE)
+    @TraceLog
+    @PostMapping(value = "humanFace/login",consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+    public ResultResponse humanFaceLogin(@RequestParam("imageBase") String imageBase){
+        return  ResultResponse.success(appUserService.humanFaceLogin(imageBase));
+    }
+
+    @Log(title = "人脸录入",operatorType = OperatorType.MOBILE)
+    @TraceLog
+    @PostMapping("face/registration")
+    public ResultResponse faceRegistration(String humanFaceUrl) {
+        return ResultResponse.success(appUserService.faceRegistration(humanFaceUrl));
+    }
+
+
+
     @Log(title = "手机短信登录",operatorType = OperatorType.MOBILE)
     @ApiOperation("手机短信登录")
     @PostMapping("/loginMsg")

+ 40 - 0
hcp-app/src/main/java/com/yingyangfly/app/controller/VoiceController.java

@@ -1,5 +1,6 @@
 package com.yingyangfly.app.controller;
 
+import com.yingyangfly.app.util.AsrMain;
 import com.yingyangfly.app.util.BaiduVoiceUtil;
 import com.yingyangfly.common.dto.ResultResponse;
 import com.yingyangfly.common.log.annotation.TraceLog;
@@ -7,15 +8,23 @@ import com.yingyangfly.common.utils.MD5Util;
 import com.yingyangfly.core.annotation.Log;
 import com.yingyangfly.core.dto.SpeechSynthesis;
 import com.yingyangfly.core.enums.OperatorType;
+import com.yingyangfly.core.vo.AsrVo;
 import com.yingyangfly.redis.client.RedisClient;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * 声音语音合成
  *
@@ -25,6 +34,7 @@ import org.springframework.web.bind.annotation.RestController;
 @RestController
 @RequestMapping("/app/video")
 @Api(tags = "声音语音合成")
+@Slf4j
 public class VoiceController {
 
     @Autowired
@@ -58,4 +68,34 @@ public class VoiceController {
             return baiduVoiceUtil.getVoiceUrl(voiceMsg,"3");
         }
     }
+
+
+    @PostMapping("/vs")
+    public Map sendAsr(@RequestBody AsrVo p) {
+        log.info("【***************收到语音识别请求****************】");
+        Map ret = new HashMap();
+        try {
+            AsrMain demo = new AsrMain();
+            // 填写下面信息
+            String result = demo.run(this.shortArrayToByteArray(p.getContent(), ByteOrder.LITTLE_ENDIAN));
+            ret.put("code", "0");
+            ret.put("result", result);
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.error("【语音识别系统错误】");
+            ret.put("code", "900");
+            ret.put("msg", "调用语音接口失败");
+        }
+        log.info("【***************结果已发送给客户端****************】");
+        return ret;
+    }
+
+    private byte[] shortArrayToByteArray(short[] shorts, ByteOrder order) {
+        byte[] bytes = new byte[shorts.length * 2];
+        ByteBuffer.wrap(bytes)
+                .order(order)
+                .asShortBuffer()
+                .put(shorts);
+        return bytes;
+    }
 }

+ 292 - 0
hcp-app/src/main/java/com/yingyangfly/app/util/AsrMain.java

@@ -0,0 +1,292 @@
+package com.yingyangfly.app.util;
+
+import com.yingyangfly.app.util.baidu.common.Base64Util;
+import com.yingyangfly.app.util.baidu.common.ConnUtil;
+import com.yingyangfly.app.util.baidu.common.DemoException;
+import com.yingyangfly.app.util.baidu.common.TokenHolder;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class AsrMain {
+
+    private final boolean METHOD_RAW = false; // 默认以json方式上传音频文件
+
+    //  填写网页上申请的appkey 如 $apiKey="g8eBUMSokVB1BHGmgxxxxxx"
+    private final String APP_KEY = "DCie3SjWpSUEv8Q5X5hSxRxg";
+
+    // 填写网页上申请的APP SECRET 如 $SECRET_KEY="94dc99566550d87f8fa8ece112xxxxx"
+    private final String SECRET_KEY = "egjl5HPocG4GlbazXQcJSJBp4wzmHHxL";
+
+    // 需要识别的文件
+    private final String FILENAME = "16k.wav";
+
+    // 文件格式, 支持pcm/wav/amr 格式,极速版额外支持m4a 格式
+    private final String FORMAT = FILENAME.substring(FILENAME.length() - 3);
+
+
+    private String CUID = "1234567JAVA";
+
+    // 采样率固定值
+    private final int RATE = 16000;
+
+    private String URL;
+
+    private int DEV_PID;
+
+    //private int LM_ID;//测试自训练平台需要打开此注释
+
+    private String SCOPE;
+
+    //  普通版 参数
+//    {
+//        URL = "http://vop.baidu.com/server_api"; // 可以改为https
+//        //  1537 表示识别普通话,使用输入法模型。 其它语种参见文档
+//        DEV_PID = 1537;
+//        SCOPE = "audio_voice_assistant_get";
+//    }
+
+    // 自训练平台 参数
+    /*{
+        //自训练平台模型上线后,您会看见 第二步:“”获取专属模型参数pid:8001,modelid:1234”,按照这个信息获取 dev_pid=8001,lm_id=1234
+        DEV_PID = 8001;
+        LM_ID = 1234;
+    }*/
+
+    /* 极速版 参数*/
+    {
+        URL =   "http://vop.baidu.com/pro_api"; // 可以改为https
+        DEV_PID = 80001;
+        SCOPE = "brain_enhanced_asr";
+    }
+
+
+    /* 忽略scope检查,非常旧的应用可能没有
+    {
+        SCOPE = null;
+    }
+    */
+
+    public static void main(String[] args) throws IOException, DemoException {
+        AsrMain demo = new AsrMain();
+        // 填写下面信息
+        String result = demo.run();
+        System.out.println("识别结束:结果是:");
+        System.out.println(result);
+
+        // 如果显示乱码,请打开result.txt查看
+        File file = new File("result.txt");
+        FileWriter fo = new FileWriter(file);
+        fo.write(result);
+        fo.close();
+        System.out.println("Result also wrote into " + file.getAbsolutePath());
+    }
+
+    public String run(byte[] content) throws IOException, DemoException {
+        TokenHolder holder = new TokenHolder(APP_KEY, SECRET_KEY, SCOPE);
+//        holder.resfresh();
+//        String token = holder.getToken();
+        String result = null;
+        if (METHOD_RAW) {
+//            result = runRawPostMethod(token);
+        } else {
+//            result = runJsonPostMethod(token);
+            result = runJsonPostWhithApiKey(content);
+        }
+        return result;
+    }
+
+    /**
+     * 使用ApiKey调用语音转文字接口
+     * @return
+     * @throws DemoException
+     * @throws IOException
+     */
+    public String runJsonPostWhithApiKey(byte[] content) throws DemoException, IOException {
+
+//        byte[] content = getFileContent(FILENAME);
+        String speech = base64Encode(content);
+//        String speech = content;
+
+        JSONObject params = new JSONObject();
+        params.put("dev_pid", DEV_PID);
+        //params.put("lm_id",LM_ID);//测试自训练平台需要打开注释
+        params.put("format", FORMAT);
+        params.put("rate", RATE);
+//        params.put("token", token);
+        params.put("cuid", CUID);
+        params.put("channel", "1");
+        params.put("len", content.length);
+//        params.put("len", co);
+        params.put("speech", speech);
+
+//        System.out.println(params.toString());
+        HttpURLConnection conn = (HttpURLConnection) new URL(URL).openConnection();
+        conn.setRequestProperty("Authorization", "Bearer bce-v3/ALTAK-Igmc9iQIIETCHcRT6DAR0/5e238c4f1df92474f2b122e155e1b71f9dbe36b6");
+        conn.setConnectTimeout(5000);
+        conn.setRequestMethod("POST");
+        conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
+        conn.setDoOutput(true);
+        //发送语音
+        conn.getOutputStream().write(params.toString().getBytes());
+        conn.getOutputStream().close();
+        String result = ConnUtil.getResponseString(conn);
+
+//        params.put("speech", "base64Encode(getFileContent(FILENAME))");
+//        System.out.println("url is : " + URL);
+//        System.out.println("params is :" + params.toString());
+
+        return result;
+    }
+
+    public String run() throws IOException, DemoException {
+        TokenHolder holder = new TokenHolder(APP_KEY, SECRET_KEY, SCOPE);
+//        holder.resfresh();
+//        String token = holder.getToken();
+        String result = null;
+        if (METHOD_RAW) {
+//            result = runRawPostMethod(token);
+        } else {
+//            result = runJsonPostMethod(token);
+            result = runJsonPostWhithApiKey();
+        }
+        return result;
+    }
+
+    /**
+     * 使用ApiKey调用语音转文字接口
+     * @return
+     * @throws DemoException
+     * @throws IOException
+     */
+    public String runJsonPostWhithApiKey() throws DemoException, IOException {
+
+        byte[] content = getFileContent(FILENAME);
+        String speech = base64Encode(content);
+
+        JSONObject params = new JSONObject();
+        params.put("dev_pid", DEV_PID);
+        //params.put("lm_id",LM_ID);//测试自训练平台需要打开注释
+        params.put("format", FORMAT);
+        params.put("rate", RATE);
+//        params.put("token", token);
+        params.put("cuid", CUID);
+        params.put("channel", "1");
+        params.put("len", content.length);
+        params.put("speech", speech);
+
+        // System.out.println(params.toString());
+        HttpURLConnection conn = (HttpURLConnection) new URL(URL).openConnection();
+        conn.setRequestProperty("Authorization", "Bearer bce-v3/ALTAK-Igmc9iQIIETCHcRT6DAR0/5e238c4f1df92474f2b122e155e1b71f9dbe36b6");
+        conn.setConnectTimeout(5000);
+        conn.setRequestMethod("POST");
+        conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
+        conn.setDoOutput(true);
+        //发送语音
+        conn.getOutputStream().write(params.toString().getBytes());
+        conn.getOutputStream().close();
+        String result = ConnUtil.getResponseString(conn);
+
+
+        params.put("speech", "base64Encode(getFileContent(FILENAME))");
+        System.out.println("url is : " + URL);
+        System.out.println("params is :" + params.toString());
+
+
+        return result;
+    }
+    private String runRawPostMethod(String token) throws IOException, DemoException {         
+        String url2 = URL + "?cuid=" + ConnUtil.urlEncode(CUID) + "&dev_pid=" + DEV_PID + "&token=" + token;
+        //测试自训练平台需要打开以下信息
+        //String url2 = URL + "?cuid=" + ConnUtil.urlEncode(CUID) + "&dev_pid=" + DEV_PID + "&lm_id="+ LM_ID + "&token=" + token;
+        String contentTypeStr = "audio/" + FORMAT + "; rate=" + RATE;
+        //System.out.println(url2);
+        byte[] content = getFileContent(FILENAME);
+        HttpURLConnection conn = (HttpURLConnection) new URL(url2).openConnection();
+        conn.setConnectTimeout(5000);
+        conn.setRequestProperty("Content-Type", contentTypeStr);
+        conn.setRequestMethod("POST");
+        conn.setDoOutput(true);
+        conn.getOutputStream().write(content);
+        conn.getOutputStream().close();
+        System.out.println("url is " + url2);
+        System.out.println("header is  " + "Content-Type :" + contentTypeStr);
+        String result = ConnUtil.getResponseString(conn);
+        return result;
+    }
+
+    public String runJsonPostMethod(String token) throws DemoException, IOException {
+
+        byte[] content = getFileContent(FILENAME);
+        String speech = base64Encode(content);
+
+        JSONObject params = new JSONObject();
+        params.put("dev_pid", DEV_PID);
+        //params.put("lm_id",LM_ID);//测试自训练平台需要打开注释
+        params.put("format", FORMAT);
+        params.put("rate", RATE);
+        params.put("token", token);
+        params.put("cuid", CUID);
+        params.put("channel", "1");
+        params.put("len", content.length);
+        params.put("speech", speech);
+
+        // System.out.println(params.toString());
+        HttpURLConnection conn = (HttpURLConnection) new URL(URL).openConnection();
+        conn.setConnectTimeout(5000);
+        conn.setRequestMethod("POST");
+        conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
+        conn.setDoOutput(true);
+        conn.getOutputStream().write(params.toString().getBytes());
+        conn.getOutputStream().close();
+        String result = ConnUtil.getResponseString(conn);
+
+
+        params.put("speech", "base64Encode(getFileContent(FILENAME))");
+        System.out.println("url is : " + URL);
+        System.out.println("params is :" + params.toString());
+
+
+        return result;
+    }
+
+    private byte[] getFileContent(String filename) throws DemoException, IOException {
+        File file = new File(filename);
+        if (!file.canRead()) {
+            System.err.println("文件不存在或者不可读: " + file.getAbsolutePath());
+            throw new DemoException("file cannot read: " + file.getAbsolutePath());
+        }
+        FileInputStream is = null;
+        try {
+            is = new FileInputStream(file);
+            return ConnUtil.getInputStreamContent(is);
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+    }
+
+    private String base64Encode(byte[] content) {
+        /**
+         Base64.Encoder encoder = Base64.getEncoder(); // JDK 1.8  推荐方法
+         String str = encoder.encodeToString(content);
+         **/
+
+        char[] chars = Base64Util.encode(content); // 1.7 及以下,不推荐,请自行跟换相关库
+        String str = new String(chars);
+
+        return str;
+    }
+
+}

+ 321 - 0
hcp-app/src/main/java/com/yingyangfly/app/util/baidu/common/Base64Util.java

@@ -0,0 +1,321 @@
+package com.yingyangfly.app.util.baidu.common;
+
+import java.io.*;
+
+/**
+ * Base64 编码和解码。
+ *
+ * @author jiangshuai
+ * @date 2016年10月03日
+ */
+public class Base64Util {
+
+    public Base64Util() {
+    }
+
+    /**
+     * 功能:编码字符串
+     *
+     * @author jiangshuai
+     * @date 2016年10月03日
+     * @param data
+     *            源字符串
+     * @return String
+     */
+    public static String encode(String data) {
+        return new String(encode(data.getBytes()));
+    }
+
+    /**
+     * 功能:解码字符串
+     *
+     * @author jiangshuai
+     * @date 2016年10月03日
+     * @param data
+     *            源字符串
+     * @return String
+     */
+    public static String decode(String data) {
+        return new String(decode(data.toCharArray()));
+    }
+
+    /**
+     * 功能:编码byte[]
+     *
+     * @author jiangshuai
+     * @date 2016年10月03日
+     * @param data
+     *            源
+     * @return char[]
+     */
+    public static char[] encode(byte[] data) {
+        char[] out = new char[((data.length + 2) / 3) * 4];
+        for (int i = 0, index = 0; i < data.length; i += 3, index += 4) {
+            boolean quad = false;
+            boolean trip = false;
+
+            int val = (0xFF & (int) data[i]);
+            val <<= 8;
+            if ((i + 1) < data.length) {
+                val |= (0xFF & (int) data[i + 1]);
+                trip = true;
+            }
+            val <<= 8;
+            if ((i + 2) < data.length) {
+                val |= (0xFF & (int) data[i + 2]);
+                quad = true;
+            }
+            out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)];
+            val >>= 6;
+            out[index + 2] = alphabet[(trip ? (val & 0x3F) : 64)];
+            val >>= 6;
+            out[index + 1] = alphabet[val & 0x3F];
+            val >>= 6;
+            out[index + 0] = alphabet[val & 0x3F];
+        }
+        return out;
+    }
+
+    /**
+     * 功能:解码
+     *
+     * @author jiangshuai
+     * @date 2016年10月03日
+     * @param data
+     *            编码后的字符数组
+     * @return byte[]
+     */
+    public static byte[] decode(char[] data) {
+
+        int tempLen = data.length;
+        for (int ix = 0; ix < data.length; ix++) {
+            if ((data[ix] > 255) || codes[data[ix]] < 0) {
+                --tempLen; // ignore non-valid chars and padding
+            }
+        }
+        // calculate required length:
+        // -- 3 bytes for every 4 valid base64 chars
+        // -- plus 2 bytes if there are 3 extra base64 chars,
+        // or plus 1 byte if there are 2 extra.
+
+        int len = (tempLen / 4) * 3;
+        if ((tempLen % 4) == 3) {
+            len += 2;
+        }
+        if ((tempLen % 4) == 2) {
+            len += 1;
+
+        }
+        byte[] out = new byte[len];
+
+        int shift = 0; // # of excess bits stored in accum
+        int accum = 0; // excess bits
+        int index = 0;
+
+        // we now go through the entire array (NOT using the 'tempLen' value)
+        for (int ix = 0; ix < data.length; ix++) {
+            int value = (data[ix] > 255) ? -1 : codes[data[ix]];
+
+            if (value >= 0) { // skip over non-code
+                accum <<= 6; // bits shift up by 6 each time thru
+                shift += 6; // loop, with new bits being put in
+                accum |= value; // at the bottom.
+                if (shift >= 8) { // whenever there are 8 or more shifted in,
+                    shift -= 8; // write them out (from the top, leaving any
+                    out[index++] = // excess at the bottom for next iteration.
+                            (byte) ((accum >> shift) & 0xff);
+                }
+            }
+        }
+
+        // if there is STILL something wrong we just have to throw up now!
+        if (index != out.length) {
+            throw new Error("Miscalculated data length (wrote " + index
+                    + " instead of " + out.length + ")");
+        }
+
+        return out;
+    }
+
+    /**
+     * 功能:编码文件
+     *
+     * @author jiangshuai
+     * @date 2016年10月03日
+     * @param file
+     *            源文件
+     */
+    public static void encode(File file) throws IOException {
+        if (!file.exists()) {
+            System.exit(0);
+        }
+
+        else {
+            byte[] decoded = readBytes(file);
+            char[] encoded = encode(decoded);
+            writeChars(file, encoded);
+        }
+        file = null;
+    }
+
+    /**
+     * 功能:解码文件。
+     *
+     * @author jiangshuai
+     * @date 2016年10月03日
+     * @param file
+     *            源文件
+     * @throws IOException
+     */
+    public static void decode(File file) throws IOException {
+        if (!file.exists()) {
+            System.exit(0);
+        } else {
+            char[] encoded = readChars(file);
+            byte[] decoded = decode(encoded);
+            writeBytes(file, decoded);
+        }
+        file = null;
+    }
+
+    //
+    // code characters for values 0..63
+    //
+    private static char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
+            .toCharArray();
+
+    //
+    // lookup table for converting base64 characters to value in range 0..63
+    //
+    private static byte[] codes = new byte[256];
+    static {
+        for (int i = 0; i < 256; i++) {
+            codes[i] = -1;
+            // LoggerUtil.debug(i + "&" + codes[i] + " ");
+        }
+        for (int i = 'A'; i <= 'Z'; i++) {
+            codes[i] = (byte) (i - 'A');
+            // LoggerUtil.debug(i + "&" + codes[i] + " ");
+        }
+
+        for (int i = 'a'; i <= 'z'; i++) {
+            codes[i] = (byte) (26 + i - 'a');
+            // LoggerUtil.debug(i + "&" + codes[i] + " ");
+        }
+        for (int i = '0'; i <= '9'; i++) {
+            codes[i] = (byte) (52 + i - '0');
+            // LoggerUtil.debug(i + "&" + codes[i] + " ");
+        }
+        codes['+'] = 62;
+        codes['/'] = 63;
+    }
+
+    private static byte[] readBytes(File file) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] b = null;
+        InputStream fis = null;
+        InputStream is = null;
+        try {
+            fis = new FileInputStream(file);
+            is = new BufferedInputStream(fis);
+            int count = 0;
+            byte[] buf = new byte[16384];
+            while ((count = is.read(buf)) != -1) {
+                if (count > 0) {
+                    baos.write(buf, 0, count);
+                }
+            }
+            b = baos.toByteArray();
+
+        } finally {
+            try {
+                if (fis != null)
+                    fis.close();
+                if (is != null)
+                    is.close();
+                if (baos != null)
+                    baos.close();
+            } catch (Exception e) {
+                System.out.println(e);
+            }
+        }
+
+        return b;
+    }
+
+    private static char[] readChars(File file) throws IOException {
+        CharArrayWriter caw = new CharArrayWriter();
+        Reader fr = null;
+        Reader in = null;
+        try {
+            fr = new FileReader(file);
+            in = new BufferedReader(fr);
+            int count = 0;
+            char[] buf = new char[16384];
+            while ((count = in.read(buf)) != -1) {
+                if (count > 0) {
+                    caw.write(buf, 0, count);
+                }
+            }
+
+        } finally {
+            try {
+                if (caw != null)
+                    caw.close();
+                if (in != null)
+                    in.close();
+                if (fr != null)
+                    fr.close();
+            } catch (Exception e) {
+                System.out.println(e);
+            }
+        }
+
+        return caw.toCharArray();
+    }
+
+    private static void writeBytes(File file, byte[] data) throws IOException {
+        OutputStream fos = null;
+        OutputStream os = null;
+        try {
+            fos = new FileOutputStream(file);
+            os = new BufferedOutputStream(fos);
+            os.write(data);
+
+        } finally {
+            try {
+                if (os != null)
+                    os.close();
+                if (fos != null)
+                    fos.close();
+            } catch (Exception e) {
+                System.out.println(e);
+            }
+        }
+    }
+
+    private static void writeChars(File file, char[] data) throws IOException {
+        Writer fos = null;
+        Writer os = null;
+        try {
+            fos = new FileWriter(file);
+            os = new BufferedWriter(fos);
+            os.write(data);
+
+        } finally {
+            try {
+                if (os != null)
+                    os.close();
+                if (fos != null)
+                    fos.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    // /////////////////////////////////////////////////
+    // end of test code.
+    // /////////////////////////////////////////////////
+
+}

+ 97 - 0
hcp-app/src/main/java/com/yingyangfly/app/util/baidu/common/ConnUtil.java

@@ -0,0 +1,97 @@
+package com.yingyangfly.app.util.baidu.common;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.URLEncoder;
+
+/**
+ * 与连接相关的Util类
+ */
+public class ConnUtil {
+
+    /**
+     * UrlEncode, UTF-8 编码
+     *
+     * @param str 原始字符串
+     * @return
+     */
+    public static String urlEncode(String str) {
+        String result = null;
+        try {
+            result = URLEncoder.encode(str, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+    /**
+     * 从HttpURLConnection 获取返回的字符串
+     *
+     * @param conn
+     * @return
+     * @throws IOException
+     * @throws DemoException
+     */
+    public static String getResponseString(HttpURLConnection conn) throws IOException, DemoException {
+        return new String(getResponseBytes(conn));
+    }
+
+    /**
+     * 从HttpURLConnection 获取返回的bytes
+     * 注意 HttpURLConnection自身问题, 400类错误,会直接抛出异常。不能获取conn.getInputStream();
+     *
+     * @param conn
+     * @return
+     * @throws IOException   http请求错误
+     * @throws DemoException http 的状态码不是 200
+     */
+    public static byte[] getResponseBytes(HttpURLConnection conn) throws IOException, DemoException {
+        int responseCode = conn.getResponseCode();
+        InputStream inputStream = conn.getInputStream();
+        if (responseCode != 200) {
+            System.err.println("http 请求返回的状态码错误,期望200, 当前是 " + responseCode);
+            if (responseCode == 401) {
+                System.err.println("可能是appkey appSecret 填错");
+            }
+            System.err.println("response headers" + conn.getHeaderFields());
+            if (inputStream == null) {
+                inputStream = conn.getErrorStream();
+            }
+            byte[] result = getInputStreamContent(inputStream);
+            System.err.println(new String(result));
+
+            throw new DemoException("http response code is" + responseCode);
+        }
+
+        byte[] result = getInputStreamContent(inputStream);
+        return result;
+    }
+
+    /**
+     * 将InputStream内的内容全部读取,作为bytes返回
+     *
+     * @param is
+     * @return
+     * @throws IOException @see InputStream.read()
+     */
+    public static byte[] getInputStreamContent(InputStream is) throws IOException {
+        byte[] b = new byte[1024];
+        // 定义一个输出流存储接收到的数据
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        // 开始接收数据
+        int len = 0;
+        while (true) {
+            len = is.read(b);
+            if (len == -1) {
+                // 数据读完
+                break;
+            }
+            byteArrayOutputStream.write(b, 0, len);
+        }
+        return byteArrayOutputStream.toByteArray();
+    }
+}

+ 7 - 0
hcp-app/src/main/java/com/yingyangfly/app/util/baidu/common/DemoException.java

@@ -0,0 +1,7 @@
+package com.yingyangfly.app.util.baidu.common;
+
+public class DemoException extends Exception {
+    public DemoException(String message) {
+        super(message);
+    }
+}

+ 125 - 0
hcp-app/src/main/java/com/yingyangfly/app/util/baidu/common/TokenHolder.java

@@ -0,0 +1,125 @@
+package com.yingyangfly.app.util.baidu.common;
+
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * token的获取类
+ * 将apiKey和secretKey换取token,注意有效期保存在expiresAt
+ */
+public class TokenHolder {
+
+
+    public static final String TTS_SCOPE = "audio_tts_post";
+
+    /**
+     * url , Token的url,http可以改为https
+     */
+    private static final String url = "http://aip.baidubce.com/oauth/2.0/token";
+
+    /**
+     * asr的权限 scope 是  "audio_voice_assistant_get"
+     * tts 的权限 scope 是 "audio_tts_post"
+     */
+    private String scope;
+
+    /**
+     * 网页上申请语音识别应用获取的apiKey
+     */
+    private String apiKey;
+
+    /**
+     * 网页上申请语音识别应用获取的secretKey
+     */
+    private String secretKey;
+
+    /**
+     * 保存访问接口获取的token
+     */
+    private String token;
+
+    /**
+     * 当前的时间戳,毫秒
+     */
+    private long expiresAt;
+
+    /**
+     * @param apiKey    网页上申请语音识别应用获取的apiKey
+     * @param secretKey 网页上申请语音识别应用获取的secretKey
+     */
+    public TokenHolder(String apiKey, String secretKey, String scope) {
+        this.apiKey = apiKey;
+        this.secretKey = secretKey;
+        this.scope = scope;
+    }
+
+
+    /**
+     * 获取token,refresh 方法后调用有效
+     *
+     * @return
+     */
+    public String getToken() {
+        return token;
+    }
+
+    /**
+     * 获取过期时间,refresh 方法后调用有效
+     *
+     * @return
+     */
+    public long getExpiresAt() {
+        return expiresAt;
+    }
+
+
+
+
+    /**
+     * 获取token
+     *
+     * @return
+     * @throws IOException   http请求错误
+     * @throws DemoException http接口返回不是 200, access_token未获取
+     */
+    public void resfresh() throws IOException, DemoException {
+        String getTokenURL = url + "?grant_type=client_credentials"
+                + "&client_id=" + ConnUtil.urlEncode(apiKey) + "&client_secret=" + ConnUtil.urlEncode(secretKey);
+
+        // 打印的url出来放到浏览器内可以复现
+        System.out.println("token url:" + getTokenURL);
+
+        URL url = new URL(getTokenURL);
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setConnectTimeout(5000);
+        String result = ConnUtil.getResponseString(conn);
+        System.out.println("Token result json:" + result);
+        parseJson(result);
+    }
+
+    /**
+     * @param result token接口获得的result
+     * @throws DemoException
+     */
+    private void parseJson(String result) throws DemoException {
+        JSONObject json = new JSONObject(result);
+        if (!json.has("access_token")) {
+            // 返回没有access_token字段
+            throw new DemoException("access_token not obtained, " + result);
+        }
+        if (!json.has("scope")) {
+            // 返回没有scope字段
+            throw new DemoException("scopenot obtained, " + result);
+        }
+        // scope = null, 忽略scope检查
+
+        if (scope != null && !json.getString("scope").contains(scope)) {
+            throw new DemoException("scope not exist, " + scope + "," + result);
+        }
+        token = json.getString("access_token");
+        expiresAt = System.currentTimeMillis() + json.getLong("expires_in") * 1000;
+    }
+}

+ 8 - 0
hcp-app/src/main/resources/application-dev.yml

@@ -78,6 +78,14 @@ oss:
   endpoint:  oss-cn-beijing.aliyuncs.com #填写自己oss endpoint
   outDomain: https://yaorongoss.yaorongmedical.com #填写自己oss 外网域名
 
+tencent:
+  cloudapi:
+    secretid: AKIDPo5KNCoCLFoJb1HMFvQirpfJj0nSvAA4
+    secretkey: 1whR4UimcDakfMo2gCW9bppaHm371kET
+    endpoint:  iai.tencentcloudapi.com
+    region: ap-beijing
+    groupid: 1
+
 live:
   stream: 187278.push.tlivecloud.com
   broadcast: pull.dustlee.com

+ 8 - 0
hcp-app/src/main/resources/application-prod.yml

@@ -78,6 +78,14 @@ oss:
   endpoint:  oss-cn-beijing.aliyuncs.com #填写自己oss endpoint
   outDomain: https://yaorongoss.yaorongmedical.com #填写自己oss 外网域名
 
+tencent:
+  cloudapi:
+    secretid: AKIDPo5KNCoCLFoJb1HMFvQirpfJj0nSvAA4
+    secretkey: 1whR4UimcDakfMo2gCW9bppaHm371kET
+    endpoint:  iai.tencentcloudapi.com
+    region: ap-beijing
+    groupid: prod
+
 live:
   stream: 187278.push.tlivecloud.com
   broadcast: pull.dustlee.com

+ 2 - 0
hcp-app/src/main/resources/application.yml

@@ -41,3 +41,5 @@ security:
       - /app/getChangePasswordCode
       - /app/reset/password
       - /app/sysOrg/getHospitalList
+      - /app/humanFace/login
+      - /app/video/vs

+ 9 - 0
hcp-app/src/test/java/com/yingyangfly/app/controller/VideoLearnControllerTest.java

@@ -1,9 +1,16 @@
 package com.yingyangfly.app.controller;
 import com.yingyangfly.HcpAppApplication;
+import com.yingyangfly.core.util.OSSImageUtils;
+import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.junit4.SpringRunner;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Base64;
+
 /**
  *
  * @author jiangqian
@@ -13,4 +20,6 @@ import org.springframework.test.context.junit4.SpringRunner;
 @SpringBootTest(classes = HcpAppApplication.class,webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
 public class VideoLearnControllerTest {
 
+
+
 }

+ 365 - 0
hcp-core/src/main/java/com/yingyangfly/core/api/impl/FaceContrastServer.java

@@ -0,0 +1,365 @@
+package com.yingyangfly.core.api.impl;
+
+import com.yingyangfly.core.bean.CreatePersonResult;
+import com.yingyangfly.core.bean.FaceSearchResult;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import com.tencentcloudapi.common.Credential;
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.tencentcloudapi.common.profile.ClientProfile;
+import com.tencentcloudapi.common.profile.HttpProfile;
+import com.tencentcloudapi.iai.v20200303.IaiClient;
+import com.tencentcloudapi.iai.v20200303.models.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.util.Arrays;
+
+@ConfigurationProperties(prefix = "tencent.cloudapi")
+@Component
+@Data
+@Slf4j
+public class FaceContrastServer {
+
+    private static final Logger logger = LoggerFactory.getLogger(FaceContrastServer.class);
+
+    private String secretId;
+
+    private String secretKey;
+
+    private String endpoint;
+
+    private String region;
+
+    private String groupId;
+
+    // 搜索配置
+    private static final Long MAX_FACE_NUM = 1L; // 最多检测人脸数
+    private static final Long MAX_PERSON_NUM = 1L; // 最多返回人员数
+    private static final Long QUALITY_CONTROL = 1L; // 质量控制等级 1:较高
+    private static final Long NEED_PERSON_INFO = 1L; // 是否需要返回人员信息
+    private static final float FACE_MATCH_THRESHOLD = 80.0f; // 人脸匹配阈值(0-100)
+
+    /**
+     * 在人员库中搜索最相似的人员
+     * @param base64Image Base64格式的人脸图片
+     * @return FaceSearchResult 搜索结果
+     */
+    public FaceSearchResult searchPersonByFace(String base64Image) {
+        return searchPersonByFace(base64Image, new String[]{groupId});
+    }
+
+
+    /**
+     * 在人员库中搜索最相似的人员
+     * @param base64Image Base64格式的人脸图片
+     * @param groupIds 要搜索的人员库ID数组
+     * @return FaceSearchResult 搜索结果
+     */
+    public FaceSearchResult searchPersonByFace(String base64Image, String[] groupIds) {
+        FaceSearchResult result = new FaceSearchResult();
+
+        try {
+            // 1. 初始化客户端
+            Credential cred = new Credential(secretId, secretKey);
+            HttpProfile httpProfile = new HttpProfile();
+            httpProfile.setEndpoint(endpoint);
+            ClientProfile clientProfile = new ClientProfile();
+            clientProfile.setHttpProfile(httpProfile);
+            IaiClient client = new IaiClient(cred, region, clientProfile);
+
+            // 2. 构建搜索请求
+            SearchFacesRequest req = new SearchFacesRequest();
+
+            // 设置要搜索的人员库列表
+            req.setGroupIds(groupIds);
+
+            // 设置Base64图片
+            req.setImage(base64Image);
+
+            // 设置搜索参数
+            req.setMaxFaceNum(MAX_FACE_NUM);
+            req.setMaxPersonNum(MAX_PERSON_NUM);
+            req.setQualityControl(QUALITY_CONTROL);
+            req.setNeedPersonInfo(NEED_PERSON_INFO);
+            req.setFaceMatchThreshold(FACE_MATCH_THRESHOLD);
+
+            logger.info("开始人脸搜索,人员库: {}", Arrays.toString(groupIds));
+
+            // 3. 调用接口
+            SearchFacesResponse resp = client.SearchFaces(req);
+
+            // 4. 处理响应
+            processSearchResponse(resp, result);
+
+        } catch (TencentCloudSDKException e) {
+            logger.error("腾讯云人脸搜索失败: {}", e.getMessage(), e);
+            result.setSuccess(false);
+            result.setErrorCode(e.getErrorCode());
+            result.setErrorMessage(e.getMessage());
+        } catch (Exception e) {
+            logger.error("人脸搜索异常: {}", e.getMessage(), e);
+            result.setSuccess(false);
+            result.setErrorMessage("系统异常: " + e.getMessage());
+        }
+
+        return result;
+    }
+
+
+    /**
+     * 处理搜索结果
+     */
+    private void processSearchResponse(SearchFacesResponse resp, FaceSearchResult result) {
+        // 检查人脸检测结果
+        if (resp == null) {
+            result.setSuccess(false);
+            result.setErrorCode("NO_FACE_DETECTED");
+            result.setErrorMessage("未检测到人脸");
+            logger.warn("未在图片中检测到人脸");
+            return;
+        }
+
+        // 检查人脸质量
+        if (resp.getFaceModelVersion() != null) {
+            result.setFaceModelVersion(resp.getFaceModelVersion());
+        }
+
+        // 获取人脸框信息
+        if (resp.getResults() != null && resp.getResults().length > 0) {
+            FaceRect rect = resp.getResults()[0].getFaceRect();
+            result.setFaceRect(rect);
+        }
+
+        // 检查搜索结果
+        if (resp.getResults() == null || resp.getResults().length == 0) {
+            result.setSuccess(false);
+            result.setErrorCode("NO_MATCH_FOUND");
+            result.setErrorMessage("在人员库中未找到匹配的人员");
+            logger.info("在人员库中未找到匹配的人员");
+            return;
+        }
+
+        // 获取第一个(最相似)的结果
+        Result respResult = resp.getResults()[0];
+
+        // 设置结果
+        result.setSuccess(true);
+        result.setPersonId(respResult.getCandidates()[0].getPersonId());
+        result.setPersonName(respResult.getCandidates()[0].getPersonName());
+
+        // 腾讯云返回的分数是0-100,转换为0-1的小数
+        float similarity = respResult.getCandidates()[0].getScore() != null ? respResult.getCandidates()[0].getScore() / 100.0f : 0f;
+        result.setSimilarity(similarity);
+
+        // 设置人脸ID
+        if (respResult.getCandidates()[0].getFaceId() != null) {
+            result.setFaceId(respResult.getCandidates()[0].getFaceId());
+        }
+
+        logger.info("人脸搜索成功: 人员ID={}, 姓名={}, 相似度={}%",
+                respResult.getCandidates()[0].getPersonId(),
+                respResult.getCandidates()[0].getPersonName(),
+                String.format("%.2f", similarity * 100));
+    }
+
+
+
+    /**
+     * 创建人员(注册人脸)
+     * @param personId 人员ID
+     * @param personName 人员姓名
+     * @param base64Image Base64人脸图片
+     * @return 创建结果
+     */
+    public CreatePersonResult createPerson(String personId, String personName, String base64Image) {
+        CreatePersonResult result = new CreatePersonResult();
+
+        // 1. 输入校验
+        if (!validateInput(personId, personName, base64Image, result)) {
+            return result; // 校验失败,直接返回
+        }
+
+        try {
+            // 2. 初始化客户端 (与您的代码一致)
+            Credential cred = new Credential(secretId, secretKey);
+            HttpProfile httpProfile = new HttpProfile();
+            httpProfile.setEndpoint(endpoint);
+            ClientProfile clientProfile = new ClientProfile();
+            clientProfile.setHttpProfile(httpProfile);
+            IaiClient client = new IaiClient(cred, region, clientProfile);
+
+            // 3. 构建请求,并考虑添加防重复参数
+            CreatePersonRequest req = new CreatePersonRequest();
+            req.setGroupId(groupId);
+            req.setPersonId(personId);
+            req.setPersonName(personName);
+            req.setImage(base64Image);
+            req.setQualityControl(1L); // 启用图片质量控制
+            // 建议添加:防止同一人重复入库(根据业务选择级别,如2)
+            req.setUniquePersonControl(2L);
+            req.setNeedRotateDetection(1L);
+
+            // 4. 发送请求
+            CreatePersonResponse resp = client.CreatePerson(req);
+
+            // 5. 处理成功响应
+            result.setSuccess(true);
+            result.setFaceId(resp.getFaceId());
+            result.setFaceRect(resp.getFaceRect());
+            logger.info("创建人员成功: ID={}, 姓名={}, 人脸ID={}", personId, personName, resp.getFaceId());
+
+        } catch (TencentCloudSDKException e) { // 更精确地捕获SDK异常
+            logger.error("调用腾讯云SDK失败,错误码: {}, 错误信息: {}", e.getErrorCode(), e.getMessage(), e);
+            result.setSuccess(false);
+            result.setErrorMessage("系统服务调用异常: " + e.getErrorCode());
+        } catch (Exception e) { // 捕获其他未知异常
+            logger.error("创建人员时发生未知异常: {}", e.getMessage(), e);
+            result.setSuccess(false);
+            result.setErrorMessage("系统内部异常");
+        }
+        return result;
+    }
+
+    /**
+     * 存在人员添加人脸
+     * @param personId
+     * @param imageBase64Array
+     * @param imageUrlArray
+     * @return
+     * @throws TencentCloudSDKException
+     */
+    public CreateFaceResponse addFace(String personId, String[] imageBase64Array,
+                                             String[] imageUrlArray) throws TencentCloudSDKException {
+        // 2. 初始化客户端 (与您的代码一致)
+        Credential cred = new Credential(secretId, secretKey);
+        HttpProfile httpProfile = new HttpProfile();
+        httpProfile.setEndpoint(endpoint);
+        ClientProfile clientProfile = new ClientProfile();
+        clientProfile.setHttpProfile(httpProfile);
+        IaiClient client = new IaiClient(cred, region, clientProfile);
+        CreateFaceRequest req = new CreateFaceRequest();
+
+        req.setPersonId(personId);
+
+        // 设置图片:Images.N 和 Urls.N 必须提供一个[citation:2]
+        if (imageUrlArray != null && imageUrlArray.length > 0) {
+            req.setUrls(imageUrlArray);
+        } else if (imageBase64Array != null && imageBase64Array.length > 0) {
+            req.setImages(imageBase64Array);
+        } else {
+            throw new IllegalArgumentException("必须提供Images或Urls其中之一");
+        }
+
+        // 可选参数:人脸相似度阈值,只有超过此值的人脸才能添加成功,默认60[citation:2]
+        req.setFaceMatchThreshold(60.0F);
+        req.setQualityControl(4L);
+
+        CreateFaceResponse resp = client.CreateFace(req);
+        System.out.println("AddFace 成功,添加了 " + resp.getSucFaceNum() + " 张人脸。请求ID: " + resp.getRequestId());
+        return resp;
+    }
+
+    /**
+     * 删除人脸
+     * @param personId
+     * @param faceIds
+     * @return
+     * @throws TencentCloudSDKException
+     */
+    public DeleteFaceResponse deleteFaces(String personId, String[] faceIds)
+            throws TencentCloudSDKException {
+
+        // 2. 初始化客户端 (与您的代码一致)
+        Credential cred = new Credential(secretId, secretKey);
+        HttpProfile httpProfile = new HttpProfile();
+        httpProfile.setEndpoint(endpoint);
+        ClientProfile clientProfile = new ClientProfile();
+        clientProfile.setHttpProfile(httpProfile);
+        IaiClient client = new IaiClient(cred, region, clientProfile);
+        DeleteFaceRequest req = new DeleteFaceRequest();
+
+        // 设置必填参数
+        req.setPersonId(personId);
+        req.setFaceIds(faceIds);
+
+        // 调用删除人脸接口
+        DeleteFaceResponse resp = client.DeleteFace(req);
+        return resp;
+    }
+
+
+    /**
+     * 删除人员
+     * @param personId 人员ID
+     * @return boolean 删除是否成功
+     */
+    public boolean deletePerson(String personId) {
+        return deletePerson(personId, groupId);
+    }
+
+    /**
+     * 删除人员(指定人员库)
+     * @param personId 人员ID
+     * @param groupId 人员库ID
+     * @return boolean 删除是否成功
+     */
+    public boolean deletePerson(String personId, String groupId) {
+        try {
+            // 1. 初始化客户端
+            Credential cred = new Credential(secretId, secretKey);
+            HttpProfile httpProfile = new HttpProfile();
+            httpProfile.setEndpoint(endpoint);
+            ClientProfile clientProfile = new ClientProfile();
+            clientProfile.setHttpProfile(httpProfile);
+            IaiClient client = new IaiClient(cred, region, clientProfile);
+
+
+
+            // 2. 构建删除请求
+            DeletePersonFromGroupRequest req = new DeletePersonFromGroupRequest();
+            req.setPersonId(personId);
+            req.setGroupId(groupId);
+
+            logger.info("开始删除人员: personId={}, groupId={}", personId, groupId);
+
+            // 3. 调用接口
+            DeletePersonFromGroupResponse resp = client.DeletePersonFromGroup(req);
+
+            // 4. 处理响应
+            if (resp != null) {
+                logger.info("删除人员成功: personId={}, 请求ID={}", personId, resp.getRequestId());
+                return true;
+            } else {
+                logger.error("删除人员失败: 响应为空");
+                return false;
+            }
+
+        } catch (TencentCloudSDKException e) {
+            logger.error("腾讯云删除人员失败: personId={}, errorCode={}, errorMessage={}",
+                    personId, e.getErrorCode(), e.getMessage(), e);
+            return false;
+        } catch (Exception e) {
+            logger.error("删除人员异常: personId={}, error={}", personId, e.getMessage(), e);
+            return false;
+        }
+    }
+
+    private boolean validateInput(String personId, String personName, String base64Image, CreatePersonResult result) {
+        // 校验人员ID格式(只支持英文、数字、-%@#&_)
+        if (personId == null || !personId.matches("^[A-Za-z0-9\\-%@#&_]+$")) {
+            result.setSuccess(false);
+            result.setErrorMessage("人员ID格式非法,只支持英文、数字、-%@#&_");
+            return false;
+        }
+        // 校验Base64图片大小(不超过5M)[citation:3]
+        if (base64Image == null || base64Image.length() > 5 * 1024 * 1024) {
+            result.setSuccess(false);
+            result.setErrorMessage("人脸图片数据不能为空且大小不能超过5M");
+            return false;
+        }
+        // 可以添加更多校验...
+        return true;
+    }
+}

+ 25 - 0
hcp-core/src/main/java/com/yingyangfly/core/bean/CreatePersonResult.java

@@ -0,0 +1,25 @@
+package com.yingyangfly.core.bean;
+
+
+import com.tencentcloudapi.iai.v20200303.models.FaceRect;
+
+public class CreatePersonResult {
+
+    private boolean success;
+    private String faceId;
+    private FaceRect faceRect;
+    private String errorMessage;
+
+    // Getters and Setters
+    public boolean isSuccess() { return success; }
+    public void setSuccess(boolean success) { this.success = success; }
+
+    public String getFaceId() { return faceId; }
+    public void setFaceId(String faceId) { this.faceId = faceId; }
+
+    public FaceRect getFaceRect() { return faceRect; }
+    public void setFaceRect(FaceRect faceRect) { this.faceRect = faceRect; }
+
+    public String getErrorMessage() { return errorMessage; }
+    public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }
+}

+ 68 - 0
hcp-core/src/main/java/com/yingyangfly/core/bean/FaceSearchResult.java

@@ -0,0 +1,68 @@
+package com.yingyangfly.core.bean;
+
+
+import com.tencentcloudapi.iai.v20200303.models.FaceRect;
+
+public class FaceSearchResult {
+
+    private boolean success;
+    private String personId;
+    private String personName;
+    private float similarity; // 0-1
+    private String groupId;
+    private String faceId;
+    private String faceModelVersion;
+    private FaceRect faceRect;
+    private String errorCode;
+    private String errorMessage;
+
+    // 是否匹配成功(相似度大于阈值)
+    public boolean isMatched() {
+        return success && similarity >= 0.8f; // 80%相似度阈值
+    }
+
+    // Getters and Setters
+    public boolean isSuccess() { return success; }
+    public void setSuccess(boolean success) { this.success = success; }
+
+    public String getPersonId() { return personId; }
+    public void setPersonId(String personId) { this.personId = personId; }
+
+    public String getPersonName() { return personName; }
+    public void setPersonName(String personName) { this.personName = personName; }
+
+    public float getSimilarity() { return similarity; }
+    public void setSimilarity(float similarity) { this.similarity = similarity; }
+
+    public String getGroupId() { return groupId; }
+    public void setGroupId(String groupId) { this.groupId = groupId; }
+
+    public String getFaceId() { return faceId; }
+    public void setFaceId(String faceId) { this.faceId = faceId; }
+
+    public String getFaceModelVersion() { return faceModelVersion; }
+    public void setFaceModelVersion(String faceModelVersion) { this.faceModelVersion = faceModelVersion; }
+
+    public FaceRect getFaceRect() { return faceRect; }
+    public void setFaceRect(FaceRect faceRect) { this.faceRect = faceRect; }
+
+    public String getErrorCode() { return errorCode; }
+    public void setErrorCode(String errorCode) { this.errorCode = errorCode; }
+
+    public String getErrorMessage() { return errorMessage; }
+    public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }
+
+    @Override
+    public String toString() {
+        return "FaceSearchResult{" +
+                "success=" + success +
+                ", personId='" + personId + '\'' +
+                ", personName='" + personName + '\'' +
+                ", similarity=" + similarity +
+                ", groupId='" + groupId + '\'' +
+                ", faceId='" + faceId + '\'' +
+                ", errorCode='" + errorCode + '\'' +
+                ", errorMessage='" + errorMessage + '\'' +
+                '}';
+    }
+}

+ 72 - 0
hcp-core/src/main/java/com/yingyangfly/core/domain/Face.java

@@ -0,0 +1,72 @@
+package com.yingyangfly.core.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ *
+ * @TableName face
+ */
+@TableName(value ="face")
+@Data
+public class Face implements Serializable {
+
+    /**
+     * 主键
+     */
+    private Long fid;
+
+    /**
+     * 用户关联id
+     */
+    private Long userId;
+
+    /**
+     * 图片数据 base_64编码
+     */
+    private String faceBase;
+
+    /**
+     * 插入时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+
+    /**
+     * 人脸名称
+     */
+    private String faceName;
+
+    /**
+     * 人脸备注
+     */
+    private String remark;
+
+    /**
+     * 人脸是否可用,(0==可用,1,不可用)
+     */
+    private Integer faceStatus;
+
+    /**
+     * 扩展字段1
+     */
+    private String updateExtend1;
+
+    /**
+     * 扩展字段2
+     */
+    private String updateExtend2;
+
+    /**
+     * 扩展字段3
+     */
+    private String updateExtend3;
+}

+ 49 - 0
hcp-core/src/main/java/com/yingyangfly/core/domain/FaceVefLog.java

@@ -0,0 +1,49 @@
+package com.yingyangfly.core.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ *
+ * @TableName face_vef_log
+ */
+@TableName(value ="face_vef_log")
+@Data
+public class FaceVefLog {
+
+    /**
+     * 主键
+     */
+    @TableId(type = IdType.AUTO)
+    private Integer lid;
+
+    /**
+     * 验证时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date vefTime;
+
+    /**
+     * 返回code
+     */
+    private Integer vefCode;
+
+    /**
+     * 返回的消息
+     */
+    private String vefMsg;
+
+    /**
+     * 验证人
+     */
+    private String loginName;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+}

+ 6 - 0
hcp-core/src/main/java/com/yingyangfly/core/domain/LoginRecord.java

@@ -1,5 +1,6 @@
 package com.yingyangfly.core.domain;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import lombok.Data;
 
 import java.util.Date;
@@ -22,4 +23,9 @@ public class LoginRecord {
     private String orgName;
 
     private String orgCode;
+
+    @TableField(exist = false)
+    private Integer page = 1;
+    @TableField(exist = false)
+    private Integer limit = 10;
 }

+ 3 - 0
hcp-core/src/main/java/com/yingyangfly/core/dto/SysOperLogDto.java

@@ -24,6 +24,9 @@ public class SysOperLogDto {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
     private Date startTime;
 
+    private String operUserName;
+
+    private String title;
 
     private Integer page;
 

+ 13 - 0
hcp-core/src/main/java/com/yingyangfly/core/mapper/FaceMapper.java

@@ -0,0 +1,13 @@
+package com.yingyangfly.core.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yingyangfly.core.domain.Face;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @description 针对表【face】的数据库操作Mapper
+ */
+@Mapper
+public interface FaceMapper extends BaseMapper<Face> {
+
+}

+ 11 - 0
hcp-core/src/main/java/com/yingyangfly/core/mapper/FaceVefLogMapper.java

@@ -0,0 +1,11 @@
+package com.yingyangfly.core.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yingyangfly.core.domain.FaceVefLog;
+
+/**
+ * @description 针对表【face_vef_log】的数据库操作Mapper
+ */
+public interface FaceVefLogMapper extends BaseMapper<FaceVefLog> {
+
+}

+ 26 - 0
hcp-core/src/main/java/com/yingyangfly/core/mapper/GameTaskMapper.java

@@ -1,10 +1,36 @@
 package com.yingyangfly.core.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
 import com.yingyangfly.core.domain.GameTask;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
 
 @Mapper
 public interface GameTaskMapper  extends BaseMapper<GameTask>{
 
+
+    @Select("<script>" +
+            "SELECT " +
+            "  gt.*, " +
+            "  GROUP_CONCAT(DISTINCT gtd.game_name) as gameNames " +
+            "FROM game_task gt " +
+            "LEFT JOIN game_task_detail gtd ON gt.id = gtd.task_id " +
+            "<where>" +
+            "  gt.user_id = #{userId} " +
+            "  <if test='treatmentId != null'>" +
+            "    AND gt.treatment_id = #{treatmentId} " +
+            "  </if>" +
+            "</where>" +
+            "GROUP BY gt.id " +
+            "ORDER BY gt.task_start_time DESC" +
+            "</script>")
+    List<GameTask> selectTaskWithGameNames(@Param("userId") Long userId,
+                                           @Param("treatmentId") Long treatmentId);
+
 }

+ 56 - 0
hcp-core/src/main/java/com/yingyangfly/core/service/FaceService.java

@@ -0,0 +1,56 @@
+package com.yingyangfly.core.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.yingyangfly.core.domain.Face;
+
+import java.util.List;
+
+/**
+ * @description 针对表【face】的数据库操作Service
+ */
+public interface FaceService extends IService<Face> {
+
+
+    /**
+     * 根据患者id查询对应登录人脸
+     * @param UserId
+     * @return
+     */
+    List<Face> selectByUserId(Long UserId);
+
+//    /**
+//     * 人员注册
+//     * @param imageBase
+//     * @return
+//     */
+//    Boolean initFace(String imageBase,String UserName,Long UserId);
+
+
+    /**
+     * 新增人脸
+     * @param imageBase
+     * @param UserId
+     * @param UserName
+     * @return
+     */
+    Boolean saveFace(String imageBase,Long UserId,String UserName) throws TencentCloudSDKException;
+
+
+    /**
+     * 删除人脸
+     * @param UserId
+     * @param faceId
+     * @return
+     */
+    Boolean deleteFace(Long UserId,Long faceId) throws TencentCloudSDKException;
+
+    /**
+     * 根据url删除
+     * @param userId
+     * @param url
+     * @return
+     * @throws TencentCloudSDKException
+     */
+    Boolean deleteFaceUrl(Long userId,String url) throws TencentCloudSDKException;
+}

+ 11 - 0
hcp-core/src/main/java/com/yingyangfly/core/service/FaceVefLogService.java

@@ -0,0 +1,11 @@
+package com.yingyangfly.core.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yingyangfly.core.domain.FaceVefLog;
+
+/**
+ * @description 针对表【face_vef_log】的数据库操作Service
+ */
+public interface FaceVefLogService extends IService<FaceVefLog> {
+
+}

+ 5 - 4
hcp-core/src/main/java/com/yingyangfly/core/service/LoginRecordService.java

@@ -1,17 +1,18 @@
 package com.yingyangfly.core.service;
 
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
-import com.yingyangfly.core.domain.Game;
 import com.yingyangfly.core.domain.LoginRecord;
-import com.yingyangfly.core.dto.GameDto;
+
+
 
 /**
- * 游戏表(Game)表服务接口
+ * 患者登录记录表服务接口
  * @author hpt
  * @since 2023-08-12 12:05:53
  */
 public interface LoginRecordService extends IService<LoginRecord> {
 
-
+    IPage<LoginRecord> selectByPage(LoginRecord loginRecord);
 }
 

+ 73 - 0
hcp-core/src/main/java/com/yingyangfly/core/service/impl/AppUserService.java

@@ -2,6 +2,7 @@ package com.yingyangfly.core.service.impl;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
 import com.alibaba.excel.util.StringUtils;
 import com.alibaba.fastjson2.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -17,6 +18,9 @@ import com.yingyangfly.common.exception.ServiceException;
 import com.yingyangfly.common.utils.DateUtils;
 import com.yingyangfly.common.utils.ids.IdUtils;
 import com.yingyangfly.core.api.ImApi;
+import com.yingyangfly.core.api.impl.FaceContrastServer;
+import com.yingyangfly.core.bean.CreatePersonResult;
+import com.yingyangfly.core.bean.FaceSearchResult;
 import com.yingyangfly.core.domain.*;
 import com.yingyangfly.core.dto.*;
 import com.yingyangfly.core.enums.MsgTemplateEnums;
@@ -31,6 +35,7 @@ import com.yingyangfly.redis.client.RedisClient;
 import lombok.extern.slf4j.Slf4j;
 import org.assertj.core.util.Sets;
 import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.core.userdetails.UserDetails;
@@ -733,6 +738,74 @@ public class AppUserService extends ServiceImpl<AppUserMapper, AppUser> implemen
         return ResultResponse.success(token);
     }
 
+    @Autowired
+    FaceContrastServer faceContrastServer;
+
+    /**
+     * 人脸登录
+     * @param imageBase
+     * @return
+     */
+    public String humanFaceLogin(String imageBase){
+        FaceSearchResult result = faceContrastServer.searchPersonByFace(imageBase);
+
+        if (result.isSuccess() && result.isMatched()) {
+            // 验证通过
+            AppUser appUser = this.appUserMapper.selectById(result.getPersonId());
+            AppCurrentLoginUser userDetails = (AppCurrentLoginUser) createAppLoginUser(appUser);
+
+            String token = tokenUtil.generateToken(userDetails, false,"app");
+            String mobile = Sm4Util.decrypt(appUser.getMobile());
+            String tokenRedis = redisClient.get(String.format("%s%s", "token:app:", mobile),"");
+            if (StringUtils.isNotBlank(tokenRedis)){
+                redisClient.del(String.format("%s%s", "token:app:", mobile));
+            }
+            redisClient.set(String.format("%s%s", "token:app:", mobile),token);
+
+            //记录登录历史
+            LoginRecord loginRecord = new LoginRecord();
+            loginRecord.setCreateTime(new Date());
+            loginRecord.setUpdateTime(new Date());
+            loginRecord.setLoginName(userDetails.getUsername());
+            loginRecord.setUserType("0");
+            loginRecord.setLoginUserId(userDetails.getId());
+            loginRecord.setOrgCode(userDetails.getOrgCode());
+            loginRecord.setOrgName(userDetails.getOrgName());
+            loginRecordMapper.insert(loginRecord);
+            return token;
+        } else {
+            // 验证失败
+            throw new RuntimeException("人脸验证失败");
+        }
+    }
+
+    @Resource
+    FaceService faceService;
+
+    public Boolean faceRegistration(String humanFaceUrl){
+        LambdaQueryWrapper<Face> lambdaQueryWrapper = new LambdaQueryWrapper();
+        AppCurrentLoginUser currentUser = tokenUtil.getAppCurrentLoginUser();
+        lambdaQueryWrapper.eq(Face::getUserId,currentUser.getId());
+        List<Face> list = faceService.list(lambdaQueryWrapper);
+        if (list.size()>0) throw new RuntimeException("请删除照片后再添加!");
+        Face face = new Face();
+        face.setFaceBase(humanFaceUrl);
+        face.setCreateTime(new Date());
+        face.setFaceName(currentUser.getUsername());
+        face.setFaceStatus(0);
+        face.setUserId(currentUser.getId());
+        String imageBase = OSSImageUtils.getBase64FromOssUrl(humanFaceUrl, 30, 3);
+        CreatePersonResult person = faceContrastServer.createPerson(String.valueOf(currentUser.getId()), currentUser.getUsername(), imageBase);
+        if (person.isSuccess() && org.apache.commons.lang3.StringUtils.isNotEmpty(person.getFaceId())){
+            face.setFid(Long.parseLong(person.getFaceId()));
+            return faceService.save(face);
+        }else {
+            throw new RuntimeException("添加失败");
+        }
+    }
+
+
+
     public ResultResponse getCheckCode(String mobile,String appType) {
         String redisKey = "hcp:sms:mobile:" + mobile;
         String checkCodeRedis = redisClient.get(redisKey, "");

+ 168 - 0
hcp-core/src/main/java/com/yingyangfly/core/service/impl/FaceServiceImpl.java

@@ -0,0 +1,168 @@
+package com.yingyangfly.core.service.impl;
+
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.tencentcloudapi.iai.v20200303.models.CreateFaceResponse;
+import com.tencentcloudapi.iai.v20200303.models.DeleteFaceResponse;
+import com.yingyangfly.core.api.impl.FaceContrastServer;
+import com.yingyangfly.core.bean.CreatePersonResult;
+import com.yingyangfly.core.bean.FaceSearchResult;
+import com.yingyangfly.core.domain.Face;
+import com.yingyangfly.core.mapper.FaceMapper;
+import com.yingyangfly.core.service.FaceService;
+import com.yingyangfly.core.util.OSSImageUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+
+@Service
+public class FaceServiceImpl extends ServiceImpl<FaceMapper, Face> implements FaceService {
+
+    @Autowired
+    FaceContrastServer faceContrastServer;
+
+
+    /**
+     * 根据用户id查询登录人脸照片
+     * @param UserId
+     * @return
+     */
+    @Override
+    public List<Face> selectByUserId(Long UserId) {
+        LambdaQueryWrapper<Face> faceLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        faceLambdaQueryWrapper.eq(Face::getUserId,UserId);
+        faceLambdaQueryWrapper.orderByDesc(Face::getCreateTime);
+        return list(faceLambdaQueryWrapper);
+    }
+
+    /**
+     * 新建人员
+     * @param imageUrl
+     * @param UserName
+     * @param UserId
+     * @return
+     */
+    public Boolean initFace(String imageUrl,String UserName,Long UserId){
+        String imageBase = OSSImageUtils.getBase64FromOssUrl(imageUrl, 30, 3);
+        Face face = new Face();
+        face.setFaceBase(imageUrl);
+        face.setCreateTime(new Date());
+        face.setFaceName(UserName);
+        face.setFaceStatus(0);
+        face.setUserId(UserId);
+        CreatePersonResult person = faceContrastServer.createPerson(String.valueOf(UserId), UserName, imageBase);
+        if (person.isSuccess() && StringUtils.isNotEmpty(person.getFaceId())){
+            face.setFid(Long.parseLong(person.getFaceId()));
+            return save(face);
+        }else {
+            throw new RuntimeException("添加失败");
+        }
+    }
+
+
+    /**
+     * 新增人脸
+     * @param imageUrl
+     * @param UserId
+     * @param UserName
+     * @return
+     * @throws TencentCloudSDKException
+     */
+    public Boolean saveFace(String imageUrl,Long UserId,String UserName) throws TencentCloudSDKException {
+        String imageBase = OSSImageUtils.getBase64FromOssUrl(imageUrl, 30, 3);
+        List<Face> faces = selectByUserId(UserId);
+        if (faces.size() == 0) {
+            return initFace(imageUrl,UserName,UserId);
+        }else {
+            Face face = new Face();
+            face.setFaceBase(imageUrl);
+            face.setCreateTime(new Date());
+            face.setFaceName(UserName);
+            face.setFaceStatus(0);
+            face.setUserId(UserId);
+
+            String[] imageBases = new String[]{imageBase};
+            CreateFaceResponse createFaceResponse = faceContrastServer.addFace(String.valueOf(UserId),imageBases,null);
+            if (createFaceResponse.getSucFaceNum()>0) {
+                face.setFid(Long.parseLong(createFaceResponse.getSucFaceIds()[0]));
+                return save(face);
+            }else {
+                throw new RuntimeException("添加失败");
+            }
+        }
+    }
+
+    /**
+     * 删除人脸
+     * @param userId
+     * @param faceId
+     * @return
+     * @throws TencentCloudSDKException
+     */
+    public Boolean deleteFace(Long userId,Long faceId) throws TencentCloudSDKException {
+        LambdaQueryWrapper<Face> lambdaQueryWrapper = new LambdaQueryWrapper<>();
+        lambdaQueryWrapper.eq(Face::getUserId,userId);
+        List<Face> list = list(lambdaQueryWrapper);
+        if (list.size() == 1) {
+            if (faceContrastServer.deletePerson(String.valueOf(userId))) {
+                LambdaQueryWrapper<Face> deleteLambdaQueryWrapper = new LambdaQueryWrapper<>();
+                deleteLambdaQueryWrapper.eq(Face::getUserId,userId);
+                return remove(deleteLambdaQueryWrapper);
+            }else {
+                throw new RuntimeException("删除失败");
+            }
+        }else {
+            String[] faceIds = new String[]{String.valueOf(faceId)};
+            DeleteFaceResponse deleteFaceResponse = faceContrastServer.deleteFaces(String.valueOf(userId), faceIds);
+            if (deleteFaceResponse.getSucDeletedNum()>0) {
+                LambdaQueryWrapper<Face> deleteLambdaQueryWrapper = new LambdaQueryWrapper<>();
+                deleteLambdaQueryWrapper.eq(Face::getFid,faceId);
+                return remove(deleteLambdaQueryWrapper);
+            }else {
+                throw new RuntimeException("删除失败");
+            }
+        }
+    }
+
+
+    /**
+     * 删除人脸
+     * @param userId
+     * @param url
+     * @return
+     * @throws TencentCloudSDKException
+     */
+    public Boolean deleteFaceUrl(Long userId,String url) throws TencentCloudSDKException {
+        LambdaQueryWrapper<Face> lambdaQueryWrapper = new LambdaQueryWrapper<>();
+        lambdaQueryWrapper.eq(Face::getUserId,userId);
+        List<Face> list = list(lambdaQueryWrapper);
+        if (list.size() == 1) {
+            if (faceContrastServer.deletePerson(String.valueOf(userId))) {
+                LambdaQueryWrapper<Face> deleteLambdaQueryWrapper = new LambdaQueryWrapper<>();
+                deleteLambdaQueryWrapper.eq(Face::getUserId,userId);
+                return remove(deleteLambdaQueryWrapper);
+            }else {
+                throw new RuntimeException("删除失败");
+            }
+        }else {
+            LambdaQueryWrapper<Face> faceLambdaQueryWrapper = new LambdaQueryWrapper<>();
+            faceLambdaQueryWrapper.eq(Face::getUserId,userId);
+            faceLambdaQueryWrapper.eq(Face::getFaceBase,url);
+            Face face = getOne(faceLambdaQueryWrapper);
+            String[] faceIds = new String[]{String.valueOf(face.getFid())};
+            DeleteFaceResponse deleteFaceResponse = faceContrastServer.deleteFaces(String.valueOf(userId), faceIds);
+            if (deleteFaceResponse.getSucDeletedNum()>0) {
+                LambdaQueryWrapper<Face> deleteLambdaQueryWrapper = new LambdaQueryWrapper<>();
+                deleteLambdaQueryWrapper.eq(Face::getFid,face.getFid());
+                return remove(deleteLambdaQueryWrapper);
+            }else {
+                throw new RuntimeException("删除失败");
+            }
+        }
+    }
+}

+ 5 - 17
hcp-core/src/main/java/com/yingyangfly/core/service/impl/GameTaskServiceImpl.java

@@ -128,25 +128,13 @@ public class GameTaskServiceImpl extends ServiceImpl<GameTaskMapper, GameTask> i
 
     @Override
     public List<GameTask> findMyTask(Long treatmentId) {
+        // 获取当前登录用户
         AppCurrentLoginUser appCurrentLoginUser = tokenUtil.getAppCurrentLoginUser();
 
-        QueryWrapper<GameTask> queryWrapper = new QueryWrapper<>();
-        queryWrapper.eq("user_id", appCurrentLoginUser.getId());
-        if (treatmentId != null) {
-            queryWrapper.eq("treatment_id", treatmentId);
-        }
-        queryWrapper.orderByDesc("task_start_time");
-        List<GameTask> gameTasks = gameTaskMapper.selectList(queryWrapper);
-        for (GameTask gameTask:gameTasks) {
-            Long taskId= gameTask.getId();
-            //查询任务详情
-            QueryWrapper<GameTaskDetail> queryWrapperDetail = new QueryWrapper<>();
-            queryWrapperDetail.eq("task_id", taskId);
-            List<GameTaskDetail> gameTaskDetailList = gameTaskDetailMapper.selectList(queryWrapperDetail);
-            Set<String> names = gameTaskDetailList.stream().map(e -> e.getGameName()).collect(Collectors.toSet());
-            gameTask.setGameNames(StringUtils.join(names,","));
-        }
-        return gameTasks;
+        // 执行查询
+        List<GameTask> dtoResult = gameTaskMapper.selectTaskWithGameNames(appCurrentLoginUser.getId(), treatmentId);
+
+        return dtoResult;
     }
 
     @Override

+ 17 - 0
hcp-core/src/main/java/com/yingyangfly/core/service/impl/LoginRecordServiceImpl.java

@@ -1,8 +1,12 @@
 package com.yingyangfly.core.service.impl;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.yingyangfly.core.domain.LoginRecord;
 import com.yingyangfly.core.mapper.LoginRecordMapper;
 import com.yingyangfly.core.service.LoginRecordService;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 
 /**
@@ -14,4 +18,17 @@ import org.springframework.stereotype.Service;
 @Service
 public class LoginRecordServiceImpl extends ServiceImpl<LoginRecordMapper, LoginRecord> implements LoginRecordService {
 
+    @Override
+    public IPage<LoginRecord> selectByPage(LoginRecord loginRecord) {
+
+        LambdaQueryWrapper<LoginRecord> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StringUtils.isNoneBlank(loginRecord.getLoginName()),
+                LoginRecord::getLoginName,loginRecord.getLoginName());
+        queryWrapper.eq(StringUtils.isNoneBlank(loginRecord.getUserType()),
+                LoginRecord::getUserType,loginRecord.getUserType());
+        queryWrapper.orderByDesc(LoginRecord::getCreateTime);
+        Page<LoginRecord> page = new Page<>(loginRecord.getPage(),loginRecord.getLimit());
+        Page<LoginRecord> loginRecordPage = baseMapper.selectPage(page, queryWrapper);
+        return loginRecordPage;
+    }
 }

+ 10 - 0
hcp-core/src/main/java/com/yingyangfly/core/service/impl/SysOperLogServiceImpl.java

@@ -8,6 +8,7 @@ import com.yingyangfly.core.domain.SysOperLog;
 import com.yingyangfly.core.dto.SysOperLogDto;
 import com.yingyangfly.core.mapper.SysOperLogMapper;
 import com.yingyangfly.core.service.SysOperLogService;
+import com.yingyangfly.core.util.Sm4Util;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 
@@ -30,6 +31,14 @@ public class SysOperLogServiceImpl extends ServiceImpl<SysOperLogMapper, SysOper
         if (sysOperLogDto.getStatus() != null){
             queryWrapper.eq(SysOperLog::getStatus,sysOperLogDto.getStatus());
         }
+        if (sysOperLogDto.getOperUserName() != null) {
+            queryWrapper.eq(SysOperLog::getOperUserName, Sm4Util.encrypt(sysOperLogDto.getOperUserName()));
+        }
+        queryWrapper.like(
+                StringUtils.isNotBlank(sysOperLogDto.getTitle()),  // 条件判断
+                SysOperLog::getTitle,                             // 字段
+                sysOperLogDto.getTitle()                          // 值
+        );
         queryWrapper.orderByDesc(SysOperLog::getOperTime);
         Page<SysOperLog> page = new Page<>(sysOperLogDto.getPage(), sysOperLogDto.getLimit());
         Page<SysOperLog> sysOperLogPage = baseMapper.selectPage(page, queryWrapper);
@@ -55,4 +64,5 @@ public class SysOperLogServiceImpl extends ServiceImpl<SysOperLogMapper, SysOper
         Page<SysOperLog> sysOperLogPage = baseMapper.selectPage(page, queryWrapper);
         return sysOperLogPage.getRecords();
     }
+
 }

+ 3 - 1
hcp-core/src/main/java/com/yingyangfly/core/service/impl/SysUserService.java

@@ -172,7 +172,9 @@ public class SysUserService extends  ServiceImpl<SysUserMapper,SysUser> {
         Set<String> roleNameSet = sysRoles.stream().map(sysRole -> sysRole.getRoleName()).collect(Collectors.toSet());
         currentLoginUser.setRoleCodes(roleSet);
         currentLoginUser.setRoleNames(roleNameSet);
-        currentLoginUser.setEmail(Sm4Util.decrypt(user.getEmail()));
+        if (ObjectUtils.isNotNull(user.getEmail())) {
+            currentLoginUser.setEmail(Sm4Util.decrypt(user.getEmail()));
+        }
         currentLoginUser.setMobile(Sm4Util.decrypt(user.getMobile()));
         currentLoginUser.setPhone(currentLoginUser.getMobile());
         currentLoginUser.setSex(user.getSex());

+ 279 - 0
hcp-core/src/main/java/com/yingyangfly/core/util/OSSImageUtils.java

@@ -0,0 +1,279 @@
+package com.yingyangfly.core.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Base64;
+import java.util.concurrent.*;
+
+public class OSSImageUtils {
+
+    private static final Logger logger = LoggerFactory.getLogger(OSSImageUtils.class);
+
+    // 线程池用于异步处理
+    private static final ExecutorService executorService = Executors.newFixedThreadPool(10);
+
+    /**
+     * 从OSS URL获取Base64(带超时控制)
+     */
+    public static String getBase64FromOssUrl(String imageUrl, int timeoutSeconds) {
+        return getBase64FromOssUrl(imageUrl, timeoutSeconds, 3);
+    }
+
+    /**
+     * 从OSS URL获取Base64(带超时和重试)
+     */
+    public static String getBase64FromOssUrl(String imageUrl, int timeoutSeconds, int maxRetries) {
+        Exception lastException = null;
+
+        for (int i = 0; i < maxRetries; i++) {
+            try {
+                if (i > 0) {
+                    logger.info("第{}次重试获取图片: {}", i + 1, imageUrl);
+                    Thread.sleep(1000 * i); // 重试间隔
+                }
+
+                return getBase64FromOssUrlWithTimeout(imageUrl, timeoutSeconds);
+
+            } catch (Exception e) {
+                lastException = e;
+                logger.warn("获取图片失败(第{}次): {}", i + 1, e.getMessage());
+            }
+        }
+
+        throw new RuntimeException("获取图片失败,重试" + maxRetries + "次后仍失败: " + imageUrl, lastException);
+    }
+
+    /**
+     * 异步获取Base64
+     */
+    public static CompletableFuture<String> getBase64FromOssUrlAsync(String imageUrl) {
+        return CompletableFuture.supplyAsync(() ->
+                        getBase64FromOssUrl(imageUrl, 30),
+                executorService
+        );
+    }
+
+    /**
+     * 带超时控制的获取方法
+     */
+    private static String getBase64FromOssUrlWithTimeout(String imageUrl, int timeoutSeconds)
+            throws Exception {
+
+        Future<String> future = executorService.submit(() ->
+                getBase64FromOssUrlInternal(imageUrl)
+        );
+
+        try {
+            return future.get(timeoutSeconds, TimeUnit.SECONDS);
+        } catch (TimeoutException e) {
+            future.cancel(true);
+            throw new RuntimeException("获取图片超时: " + timeoutSeconds + "秒", e);
+        } catch (ExecutionException e) {
+            throw new RuntimeException("获取图片执行异常: " + e.getMessage(), e);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("获取图片被中断", e);
+        }
+    }
+
+    /**
+     * 实际的获取逻辑
+     */
+    private static String getBase64FromOssUrlInternal(String imageUrl) throws IOException {
+        long startTime = System.currentTimeMillis();
+        HttpURLConnection connection = null;
+        InputStream inputStream = null;
+        ByteArrayOutputStream outputStream = null;
+
+        try {
+            logger.debug("开始获取图片: {}", imageUrl);
+
+            // 创建URL连接
+            URL url = new URL(imageUrl);
+            connection = (HttpURLConnection) url.openConnection();
+
+            // 配置连接
+            connection.setRequestMethod("GET");
+            connection.setConnectTimeout(10000);
+            connection.setReadTimeout(30000);
+            connection.setRequestProperty("User-Agent", "Mozilla/5.0");
+            connection.setRequestProperty("Accept", "image/*");
+
+            // 建立连接
+            connection.connect();
+
+            // 检查响应码
+            int responseCode = connection.getResponseCode();
+            if (responseCode != HttpURLConnection.HTTP_OK) {
+                throw new IOException("HTTP " + responseCode + ": " + connection.getResponseMessage());
+            }
+
+            // 获取Content-Type
+            String contentType = connection.getContentType();
+            if (contentType == null || !contentType.startsWith("image/")) {
+                throw new IOException("非图片Content-Type: " + contentType);
+            }
+
+            // 获取Content-Length
+            int contentLength = connection.getContentLength();
+            if (contentLength > 10 * 1024 * 1024) { // 10MB限制
+                throw new IOException("图片太大: " + contentLength + " bytes");
+            }
+
+            // 读取数据
+            inputStream = connection.getInputStream();
+            outputStream = new ByteArrayOutputStream();
+
+            byte[] buffer = new byte[8192];
+            int bytesRead;
+            long totalBytes = 0;
+
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                outputStream.write(buffer, 0, bytesRead);
+                totalBytes += bytesRead;
+
+                // 进度监控(可选)
+                if (contentLength > 0) {
+                    int progress = (int) ((totalBytes * 100) / contentLength);
+                    logger.debug("下载进度: {}%", progress);
+                }
+            }
+
+            // 转换为Base64
+            byte[] imageBytes = outputStream.toByteArray();
+            String base64Image = Base64.getEncoder().encodeToString(imageBytes);
+
+            // 获取MIME类型
+            String mimeType = extractMimeType(contentType);
+
+            long endTime = System.currentTimeMillis();
+            logger.info("图片获取完成: {} bytes, 耗时: {}ms",
+                    imageBytes.length, (endTime - startTime));
+
+            return "data:" + mimeType + ";base64," + base64Image;
+
+        } finally {
+            // 清理资源
+            closeQuietly(inputStream);
+            closeQuietly(outputStream);
+
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    /**
+     * 从Content-Type中提取MIME类型
+     */
+    private static String extractMimeType(String contentType) {
+        if (contentType == null) {
+            return "application/octet-stream";
+        }
+
+        // 移除charset等参数
+        int semicolonIndex = contentType.indexOf(';');
+        if (semicolonIndex > 0) {
+            return contentType.substring(0, semicolonIndex).trim();
+        }
+
+        return contentType.trim();
+    }
+
+    /**
+     * 获取图片信息(不下载完整图片)
+     */
+    public static ImageInfo getImageInfo(String imageUrl) throws IOException {
+        HttpURLConnection connection = null;
+
+        try {
+            URL url = new URL(imageUrl);
+            connection = (HttpURLConnection) url.openConnection();
+
+            connection.setRequestMethod("HEAD");
+            connection.setConnectTimeout(5000);
+            connection.setReadTimeout(5000);
+
+            connection.connect();
+
+            int responseCode = connection.getResponseCode();
+            if (responseCode != HttpURLConnection.HTTP_OK) {
+                throw new IOException("HTTP " + responseCode);
+            }
+
+            String contentType = connection.getContentType();
+            String contentLengthStr = connection.getHeaderField("Content-Length");
+            long contentLength = contentLengthStr != null ? Long.parseLong(contentLengthStr) : -1;
+
+            return new ImageInfo(contentType, contentLength);
+
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    /**
+     * 关闭资源
+     */
+    private static void closeQuietly(InputStream is) {
+        if (is != null) {
+            try {
+                is.close();
+            } catch (IOException e) {
+                logger.warn("关闭输入流失败", e);
+            }
+        }
+    }
+
+    private static void closeQuietly(ByteArrayOutputStream os) {
+        if (os != null) {
+            try {
+                os.close();
+            } catch (IOException e) {
+                logger.warn("关闭输出流失败", e);
+            }
+        }
+    }
+
+    /**
+     * 图片信息类
+     */
+    public static class ImageInfo {
+        private final String contentType;
+        private final long contentLength;
+
+        public ImageInfo(String contentType, long contentLength) {
+            this.contentType = contentType;
+            this.contentLength = contentLength;
+        }
+
+        public String getContentType() { return contentType; }
+        public long getContentLength() { return contentLength; }
+        public boolean isImage() {
+            return contentType != null && contentType.startsWith("image/");
+        }
+    }
+
+    /**
+     * 关闭线程池
+     */
+    public static void shutdown() {
+        executorService.shutdown();
+        try {
+            if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
+                executorService.shutdownNow();
+            }
+        } catch (InterruptedException e) {
+            executorService.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+    }
+}

+ 17 - 0
hcp-core/src/main/java/com/yingyangfly/core/vo/AsrVo.java

@@ -0,0 +1,17 @@
+package com.yingyangfly.core.vo;
+
+import lombok.Data;
+
+@Data
+public class AsrVo {
+
+    private short[] content;
+
+    public short[] getContent() {
+        return content;
+    }
+
+    public void setContent(short[] content) {
+        this.content = content;
+    }
+}

+ 53 - 0
hcp-platform/src/main/java/com/yingyangfly/platform/controller/FaceContrller.java

@@ -0,0 +1,53 @@
+package com.yingyangfly.platform.controller;
+
+
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.yingyangfly.common.dto.ResultResponse;
+import com.yingyangfly.common.log.annotation.TraceLog;
+import com.yingyangfly.core.annotation.Log;
+import com.yingyangfly.core.api.impl.FaceContrastServer;
+import com.yingyangfly.core.domain.Face;
+import com.yingyangfly.core.service.FaceService;
+import com.yingyangfly.file.api.FileClient;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.List;
+
+@Slf4j
+@RestController
+@Api(tags = "人脸照片")
+@RequestMapping("/face")
+public class FaceContrller {
+
+    @Resource
+    private FaceService faceService;
+
+
+    @PostMapping("/selectByUserId")
+    @ApiOperation("根据用户id查询人脸照片列表")
+    @TraceLog
+    public ResultResponse selectByUserId(Long UserId) {
+        return ResultResponse.success(faceService.selectByUserId(UserId));
+    }
+
+    @ApiOperation("新增人脸照片")
+    @PostMapping("/save")
+    @TraceLog
+    public ResultResponse saveFave(@RequestBody Face face) throws TencentCloudSDKException {
+        return ResultResponse.success(faceService.saveFace(face.getFaceBase(),face.getUserId(),face.getFaceName()));
+    }
+
+    @ApiOperation("删除人脸照片")
+    @PostMapping("delete")
+    @TraceLog
+    public ResultResponse delete(@RequestBody Face face) throws TencentCloudSDKException {
+        return ResultResponse.success(faceService.deleteFace(face.getUserId(),face.getFid()));
+    }
+}

+ 34 - 0
hcp-platform/src/main/java/com/yingyangfly/platform/controller/LoginRecordController.java

@@ -0,0 +1,34 @@
+package com.yingyangfly.platform.controller;
+
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.yingyangfly.common.dto.ResultResponse;
+import com.yingyangfly.core.domain.LoginRecord;
+import com.yingyangfly.core.service.LoginRecordService;
+import io.swagger.annotations.Api;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+@Api(tags = "患者登录记录")
+@RestController
+@RequestMapping("/loginRecord")
+public class LoginRecordController {
+
+
+    @Resource
+    private LoginRecordService loginRecordService;
+
+    @PreAuthorize("@SSPermissionChecker.hasPermission('login_record')")
+    @PostMapping("/list")
+    public ResultResponse list(@RequestBody LoginRecord loginRecord){
+        IPage<LoginRecord> sysOperLogIPage = loginRecordService.selectByPage(loginRecord);
+
+        return ResultResponse.success(sysOperLogIPage);
+    }
+
+}

+ 9 - 0
hcp-platform/src/main/resources/application-dev.yml

@@ -72,6 +72,15 @@ oss:
   bucket-name: hcp-yaorong
   endpoint:  oss-cn-beijing.aliyuncs.com #填写自己oss endpoint
   outDomain: https://yaorongoss.yaorongmedical.com #填写自己oss 外网域名
+
+tencent:
+  cloudapi:
+    secretid: AKIDPo5KNCoCLFoJb1HMFvQirpfJj0nSvAA4
+    secretkey: 1whR4UimcDakfMo2gCW9bppaHm371kET
+    endpoint:  iai.tencentcloudapi.com
+    region: ap-beijing
+    groupid: 1
+
 wx:
   app-id: wxe6dbe98e8c6d1a4b
   app-secret: 9012fccacb9a71d5d39ff2881b35388c

+ 9 - 0
hcp-platform/src/main/resources/application-prod.yml

@@ -72,6 +72,15 @@ oss:
   bucket-name: hcp-yaorong
   endpoint:  oss-cn-beijing.aliyuncs.com #填写自己oss endpoint
   outDomain: https://yaorongoss.yaorongmedical.com #填写自己oss 外网域名
+
+tencent:
+  cloudapi:
+    secretid: AKIDPo5KNCoCLFoJb1HMFvQirpfJj0nSvAA4
+    secretkey: 1whR4UimcDakfMo2gCW9bppaHm371kET
+    endpoint:  iai.tencentcloudapi.com
+    region: ap-beijing
+    groupid: prod
+
 wx:
   app-id: wxe6dbe98e8c6d1a4b
   app-secret: 9012fccacb9a71d5d39ff2881b35388c