飞书接入 chaptgpt

参考1:https://github.com/PlexPt/chatgpt-java
参考2:https://www.51cto.com/article/747923.html

配置

  • FastJson2JsonRedisSerializer
import java.nio.charset.Charset;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;

/**
 * Redis使用FastJson序列化
 *
 * @author gxsldl
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    public FastJson2JsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
    }
}

  • RedisConfig
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * redis配置
 *
 * @author gxsldl
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    @SuppressWarnings(value = {"unchecked", "rawtypes"})
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public DefaultRedisScript<Long> limitScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(limitScriptText());
        redisScript.setResultType(Long.class);
        return redisScript;
    }

    /**
     * 限流脚本
     */
    private String limitScriptText() {
        return "local key = KEYS[1]\n" +
                "local count = tonumber(ARGV[1])\n" +
                "local time = tonumber(ARGV[2])\n" +
                "local current = redis.call('get', key);\n" +
                "if current and tonumber(current) > count then\n" +
                "    return tonumber(current);\n" +
                "end\n" +
                "current = redis.call('incr', key)\n" +
                "if tonumber(current) == 1 then\n" +
                "    redis.call('expire', key, time)\n" +
                "end\n" +
                "return tonumber(current);";
    }
}

  • application.yml

server:
  port: 12300
  servlet:
    context-path: /
  tomcat:
    uri-encoding: UTF-8
    accept-count: 1000
    threads:
      max: 800
      min-spare: 100

feishu:
  messages-reply: https://open.feishu.cn/open-apis/im/v1/messages/%s/reply
  token-url: https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal/

openai:
  token: sk-MMiKbgBZMbGIwPkgc6*********** # 填写自己的 API Key

proxy:
  port: 7890

spring:
  redis:
    host: localhost
    port: 6379
    database: 15
    password:
    timeout: 10s
    lettuce:
      pool:
        min-idle: 0
        max-idle: 8
        max-active: 8
        max-wait: -1ms

logging:
  level:
    root: info
  • maven
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>3.0.5</version>
        </dependency>

        <!-- fastjson2 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.26</version>
        </dependency>

        <dependency>
            <groupId>com.github.plexpt</groupId>
            <artifactId>chatgpt</artifactId>
            <version>4.0.5</version>
        </dependency>

        <!-- hutool-http -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-http</artifactId>
            <version>5.8.18</version>
        </dependency>
        
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
            <scope>provided</scope>
        </dependency>

常量类

public class RedisConstant {

    public static final Integer MESSAGE_EVENT_ID_EXPIRE = 30;
}
public class SystemConstant {
    public static final String TENANT_ACCESS_TOKEN = "tenant:access:token:";
}

controller 类

package com.feishu.chatgpt.controller;


import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.feishu.chatgpt.constant.RedisConstant;
import com.feishu.chatgpt.entity.FeishuEventDTO;
import com.feishu.chatgpt.entity.FeishuEventParams;
import com.feishu.chatgpt.entity.FeishuResponse;
import com.feishu.chatgpt.utils.FeishuUtils;
import com.feishu.chatgpt.utils.RedisCache;
import com.plexpt.chatgpt.util.Proxys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.net.ConnectException;
import java.net.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@RestController
@RequestMapping(value = "/query")
public class QueryController {

    @Value("${feishu.messages-reply}")
    private String messagesReply;

    @Value("${openai.token}")
    private String openaiToken;

    @Value("${proxy.port}")
    private Integer proxyPort;

    @Autowired
    private RedisCache redisCache;
    @Autowired
    private FeishuUtils feishuUtils;

    @PostMapping(value = "/message")
    public String message(@RequestBody String body) {
        log.info("收到飞书消息:{}", body);
        JSONObject jsonObject = JSONObject.parseObject(body);
        JSONObject header = jsonObject.getJSONObject("header");
        //请头为空,或消息已经回复过,则不再回复
        if(eventCheck(header)){
            return null;
        }
        String eventType = header.getString("event_type");
        if ("im.message.receive_v1".equals(eventType)) {
            JSONObject event = jsonObject.getJSONObject("event");
            JSONObject message = event.getJSONObject("message");
            String messageType = message.getString("message_type");
            if ("text".equals(messageType)) {
                String messageId = message.getString("message_id");
                String content = message.getString("content");
                JSONObject contentJson = JSONObject.parseObject(content);
                String text = contentJson.getString("text");

                FeishuResponse feishuResponse = new FeishuResponse();
                feishuResponse.setMessageId(messageId);
                feishuResponse.setQuery(text);
                log.debug("投递用户消息,{}", JSON.toJSONString(feishuResponse));

                //String query = queryCompletions(content); //text-davinci-003 模型
                String query = queryChat(content); // gpt-3.5-turbo 模型
                reply(feishuResponse, query);

            } else {
                log.debug("非文本消息");
            }
        }
        return "success";
    }

    /**
     * 回复飞书机器人
     * @param poll
     * @param rs
     * @return
     */
    private String reply(FeishuResponse poll, String rs) {
        JSONObject params = new JSONObject();
        params.put("uuid", RandomUtil.randomNumbers(10));
        params.put("msg_type", "text");

        JSONObject content = new JSONObject();
        content.put("text", rs);
        params.put("content", content.toJSONString());

        String url = String.format(messagesReply, poll.getMessageId());
        String tenantAccessToken = feishuUtils.getTenantAccessToken();
        String body = null;
        try (HttpResponse authorization = HttpUtil.createPost(url)
                .header("Authorization", "Bearer " + tenantAccessToken)
                .body(params.toJSONString())
                .execute()) {
            body = authorization.body();
        }
        return body;
    }

    /**
     * v1/chat/completions 下的模型调用方法
     */
    public String queryChat(String queryMsg){
        Map<String,String> headers = new HashMap<String,String>();
        headers.put("Content-Type","application/json");

        JSONObject json = new JSONObject();
        //选择模型
        json.put("model","gpt-3.5-turbo");
        //添加我们需要输入的内容
        JSONObject msg = new JSONObject();
        msg.put("role", "user");
        msg.put("content", queryMsg);
        JSONArray array = new JSONArray();
        array.add(msg);
        json.put("messages", array);
        json.put("temperature",0);
        json.put("max_tokens",2048);
        json.put("top_p",1);
        json.put("frequency_penalty",0.0);
        json.put("presence_penalty",0.0);
        try{
            Proxy proxy = Proxys.http("127.0.0.1", proxyPort);
            HttpResponse response = HttpRequest.post("https://api.openai.com/v1/chat/completions") //gpt-3.5-turbo
                    .headerMap(headers, false)
                    .bearerAuth(openaiToken)
                    .setProxy(proxy)
                    .body(String.valueOf(json))
                    .timeout(600000)
                    .execute();
            String result = response.body();
            log.debug("query:{},获取响应:{}", queryMsg, JSON.toJSONString(result));
            JSONObject parseObject = JSONObject.parseObject(result);
            JSONArray choices = parseObject.getJSONArray("choices");
            JSONObject resMsg = choices.getJSONObject(0).getJSONObject("message");
            return resMsg.getString("content");
        } catch (IORuntimeException e){
            log.error("openai 接口调用异常:{}", e.getMessage());
            if(e.getMessage().contains("Connection timed out")){
                return "Error: 连接超时,请检查vpn节点是否正常";
            }
            return "Error: 连接被拒绝,请检查vpn是否已开启";
        } catch (Exception e){
            e.printStackTrace();
            log.error("openai 接口调用异常:{}", e.getMessage());
            return "openai API 调用异常";
        }
    }


    /**
     * v1/completions 下的模型调用方法
     */
    public String queryCompletions(String query){
        Map<String,String> headers = new HashMap<String,String>();
        headers.put("Content-Type","application/json");

        JSONObject json = new JSONObject();
        //选择模型
        json.put("model","text-davinci-003");
        //添加我们需要输入的内容
        json.put("prompt", query);
        json.put("temperature",0);
        json.put("max_tokens",2048);
        json.put("top_p",1);
        json.put("frequency_penalty",0.0);
        json.put("presence_penalty",0.0);
        try{
            Proxy proxy = Proxys.http("127.0.0.1", proxyPort); // 代码中设置代理,端口号从代理软件中获取
            HttpResponse response = HttpRequest.post("https://api.openai.com/v1/completions")
                    .headerMap(headers, false)
                    .bearerAuth(openaiToken)
                    .setProxy(proxy)
                    .body(String.valueOf(json))
                    .timeout(600000)
                    .execute();
            JSONObject resultJson = JSONObject.parseObject(response.body());
            JSONObject message = resultJson.getJSONArray("choices").getJSONObject(0);
            String content = message.getString("text");
            System.err.println(message);
            if(content.indexOf("\n\n") == 0){
                content = content.substring(2);
            }
            return content;
        } catch (IORuntimeException e){
            log.error("openai 接口调用异常:{}", e.getMessage());
            if(e.getMessage().contains("Connection timed out")){
                return "Error: 连接超时,请检查vpn节点是否正常";
            }
            return "Error: 连接被拒绝,请检查vpn是否已开启";
        } catch (Exception e){
            e.printStackTrace();
            log.error("openai 接口调用异常:{}", e.getMessage());
            return "openai API 调用异常";
        }
    }

    /**
     * 检查请求头是否为空或消息事件是否重复
     * @param header
     * @return
     */
    public boolean eventCheck(JSONObject header){
        if (header == null){
            return true;
        }

        String eventId = header.getString("event_id");
        if(redisCache.hasKey(eventId)){
            //消息事件重复
            return true;
        }else{
            //设置30天过期,飞书机器人不定时读取到已回复过的消息,导致重复调用接口重复回复已回复过的消息
            redisCache.setCacheObject(eventId, 1, RedisConstant.MESSAGE_EVENT_ID_EXPIRE, TimeUnit.DAYS);
        }
        return false;
    }

    /**
     * 飞书开放平台上配置【事件订阅】时请求地址配置填写使用
     * 与收消息用同一个地址,在平台上验证成功后注释掉即可
     * @param body
     * @return
     */
    //@PostMapping(value = "/message")
    public FeishuEventDTO messageFeishuCheck(@RequestBody String body) {
        log.debug("收到消息:{}", body);
        FeishuEventParams feishuEventParams = JSON.parseObject(body, FeishuEventParams.class);
        FeishuEventDTO eventDTO = new FeishuEventDTO();
        eventDTO.setChallenge(feishuEventParams.getChallenge());
        return eventDTO;
    }
}

  • 飞书平台验证,接口需要外网能访问
    在这里插入图片描述

entity类

import lombok.Data;

@Data
public class FeishuEventDTO {
    private String challenge;
}
import lombok.Data;

@Data
public class FeishuEventParams {
    private String challenge;
    private String token;
    private String type;
}

import lombok.Data;

@Data
public class FeishuResponse {

    private String messageId;
    private String query;
}

工具类

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.feishu.chatgpt.constant.SystemConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class FeishuUtils {

    @Autowired
    private RedisCache redisCache;

    @Value("${feishu.token-url}")
    private String tokenUrl;
    //这个是飞书应用的appid和key,可以在创建的飞书应用中找到
    public static final String appId = "cli_a4cf4*********";
    public static final String appKey = "0ib7gAmiJjs5Hnm****************";

    public String getTenantAccessToken() {
        String token = null;
        if(redisCache.hasKey(SystemConstant.TENANT_ACCESS_TOKEN)){
            token = redisCache.getCacheObject(SystemConstant.TENANT_ACCESS_TOKEN);
        }else{
            JSONObject params = new JSONObject();
            params.put("app_id", appId);
            params.put("app_secret", appKey);
            String body;
            HttpResponse execute = HttpUtil.createPost(tokenUrl).body(params.toJSONString()).execute();
            body = execute.body();
            log.debug("获取飞书token:{}", body);
            if (StrUtil.isNotBlank(body)) {
                String tenantAccessToken = JSON.parseObject(body).getString("tenant_access_token");
                redisCache.setCacheObject("tenantAccessToken", tenantAccessToken);
                return tenantAccessToken;
            }
        }
        return token;
    }
}
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

/**
 * spring redis 工具类
 *
 * @author gxsldl
 **/
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisCache {
    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key   缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key      缓存的键值
     * @param value    缓存的值
     * @param timeout  时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout) {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @param unit    时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获取有效时间
     *
     * @param key Redis键
     * @return 有效时间
     */
    public long getExpire(final String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 判断 key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key) {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public boolean deleteObject(final Collection collection) {
        return redisTemplate.delete(collection) > 0;
    }

    /**
     * 缓存List数据
     *
     * @param key      缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList) {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key) {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key     缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext()) {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key   Redis键
     * @param hKey  Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key  Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey) {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key   Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 删除Hash中的某条数据
     *
     * @param key  Redis键
     * @param hKey Hash键
     * @return 是否成功
     */
    public boolean deleteCacheMapValue(final String key, final String hKey) {
        return redisTemplate.opsForHash().delete(key, hKey) > 0;
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern) {
        return redisTemplate.keys(pattern);
    }
}

日志

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 日志存放路径 -->
<!--    	<property name="log.path" value="/data/feishu-chatgpt/logs" />-->
    <property name="log.path" value="D:\\logs\\feishu-chatgpt"/>
    <!-- 日志输出格式 -->
    <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n"/>

    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>

    <!-- 系统日志输出 -->
    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/sys-info.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志最大的历史 60天 -->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 -->
            <level>INFO</level>
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/sys-error.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志最大的历史 60天 -->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 -->
            <level>ERROR</level>
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 用户访问日志输出  -->
    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/sys-user.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天回滚 daily -->
            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志最大的历史 60天 -->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>

    <!-- 系统模块日志级别控制  -->
    <logger name="com.gxsldl" level="info"/>
    <!-- Spring日志级别控制  -->
    <logger name="org.springframework" level="warn"/>

    <root level="info">
        <appender-ref ref="console"/>
    </root>

    <!--系统操作日志-->
    <root level="info">
        <appender-ref ref="file_info"/>
        <appender-ref ref="file_error"/>
    </root>

    <!--系统用户操作日志-->
    <logger name="sys-user" level="info">
        <appender-ref ref="sys-user"/>
    </logger>
</configuration> 

2023-05-31:修改为可根据会话上下文回答问题

  • QueryController 中的 message() 方法
    获取飞消息中的 openId,并在 queryChat()中将openId作为redis缓存键值,保存用户的提示的内容数组
 /**
     * 接收消息
     * @param body
     * @return
     */
    @PostMapping(value = "/message")
    public String message(@RequestBody String body) {
        log.info("收到飞书消息:{}", body);
        JSONObject jsonObject = JSONObject.parseObject(body);
        String challenge = jsonObject.getString("challenge");

        if(challenge != null){
            //飞书开放平台上配置【事件订阅】时请求验证
            JSONObject rJson = new JSONObject();
            rJson.put("challenge", challenge);
            return rJson.toString();
        }

        JSONObject header = jsonObject.getJSONObject("header");
        //请头为空,或消息已经回复过,则不再回复
        if(eventCheck(header)){
            return null;
        }
        String eventType = header.getString("event_type");
        if ("im.message.receive_v1".equals(eventType)) {
            JSONObject event = jsonObject.getJSONObject("event");
            JSONObject message = event.getJSONObject("message");
            String messageType = message.getString("message_type");
            if ("text".equals(messageType)) {
                String messageId = message.getString("message_id");
                String content = message.getString("content");
                JSONObject contentJson = JSONObject.parseObject(content);
                String text = contentJson.getString("text");

                FeishuResponse feishuResponse = new FeishuResponse();
                feishuResponse.setMessageId(messageId);
                feishuResponse.setQuery(text);

                log.debug("投递用户消息,{}", JSON.toJSONString(feishuResponse));

                JSONObject sender = event.getJSONObject("sender");
                JSONObject senderId = sender.getJSONObject("sender_id");
                String openId = senderId.getString("open_id");

//                String query = queryCompletions(content, openId); //text-davinci-003 模型
                String query = queryChat(content, openId); // gpt-3.5-turbo 模型
                reply(feishuResponse, query);

            } else {
                log.error("非文本消息");
            }
        }
        return "success";
    }
  • QueryController 中的 queryChat() 方法
    • 将用户提问的每个问题添加到数组中,角色 role = user
    • 将chatgpt回答的内容保存到数组中,角色 role = assistant
    • 将问答内容数组保存到redis中,每次提问将整个数组作为提问参数传递
/**
     * v1/chat/completions 下的模型调用方法
     */
    public String queryChat(String queryMsg, String openId){
        Map<String,String> headers = new HashMap<String,String>();
        headers.put("Content-Type","application/json");

        JSONObject json = new JSONObject();
        //选择模型
        json.put("model","gpt-3.5-turbo");
        //添加我们需要输入的内容
        JSONObject msg = new JSONObject();
        msg.put("role", "user");
        msg.put("content", queryMsg);

        JSONArray array = null;
        String key = RedisConstant.OPENID_MODEL_GPT_35_TURBO + openId;
        if(redisCache.hasKey(key)){
            array = redisCache.getCacheObject(key);
            //只保留10条历史提问内容
            if (array.size() >= 10){
                array.remove(0);
            }
        }else{
            array = new JSONArray();
        }
        array.add(msg);

        json.put("messages", array);
        json.put("temperature",0);
        json.put("max_tokens",2048);
        json.put("top_p",0.5);
        json.put("frequency_penalty",0.8);
        json.put("presence_penalty",0.5);
        try{
            Proxy proxy = Proxys.http("127.0.0.1", proxyPort);
            HttpResponse response = HttpRequest.post("https://api.openai.com/v1/chat/completions") //gpt-3.5-turbo
                    .headerMap(headers, false)
                    .bearerAuth(openaiToken)
                    .setProxy(proxy)
                    .body(String.valueOf(json))
                    .timeout(600000)
                    .execute();
            String result = response.body();
            log.debug("query:{},获取响应:{}", queryMsg, JSON.toJSONString(result));
            JSONObject parseObject = JSONObject.parseObject(result);
            JSONArray choices = parseObject.getJSONArray("choices");
            JSONObject resMsg = choices.getJSONObject(0).getJSONObject("message");


            //助理角色
            JSONObject res = new JSONObject();
            res.put("role", "assistant");
            res.put("content", resMsg.getString("content"));
            array.add(res);

            //存入缓存
            redisCache.setCacheObject(key, array);
            return resMsg.getString("content");
        } catch (IORuntimeException e){
            log.error("openai 接口调用异常:{}", e.getMessage());
            if(e.getMessage().contains("Connection timed out")){
                return "Error: 连接超时,请检查vpn节点是否正常";
            }
            return "Error: 连接被拒绝,请检查vpn是否已开启";
        } catch (Exception e){
            e.printStackTrace();
            log.error("openai 接口调用异常:{}", e.getMessage());
            return "openai API 调用异常";
        }
    }
  • RedisConstant 类添加属性:OPENID_MODEL_GPT_35_TURBO
    OPENID_MODEL_GPT_35_TURBO 用做redis缓存键
public class RedisConstant {

    public static final Integer MESSAGE_EVENT_ID_EXPIRE = 30;

    public static final String OPENID_MODEL_GPT_35_TURBO = "openid:gpt35turbo:";

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值