permsSet = new HashSet<>();
if (user.getSuperAdmin() == SuperAdminEnum.YES.value()) {
@@ -69,22 +76,22 @@ public class Oauth2Realm extends AuthorizingRealm {
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getPrincipal();
- //根据accessToken,查询用户信息
+ // 根据accessToken,查询用户信息
SysUserTokenEntity tokenEntity = shiroService.getByToken(accessToken);
- //token失效
+ // token失效
if (tokenEntity == null || tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()) {
throw new IncorrectCredentialsException(MessageUtils.getMessage(ErrorCode.TOKEN_INVALID));
}
- //查询用户信息
+ // 查询用户信息
SysUserEntity userEntity = shiroService.getUser(tokenEntity.getUserId());
- //转换成UserDetail对象
+ // 转换成UserDetail对象
UserDetail userDetail = ConvertUtils.sourceToTarget(userEntity, UserDetail.class);
userDetail.setToken(accessToken);
- //账号锁定
+ // 账号锁定
if (userDetail.getStatus() == null) {
logger.error("账号状态异常,status 不能为空");
throw new DisabledAccountException(MessageUtils.getMessage(ErrorCode.ACCOUNT_DISABLE));
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/TokenGenerator.java b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/TokenGenerator.java
index 0ca1cede..6d7be47a 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/TokenGenerator.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/TokenGenerator.java
@@ -1,10 +1,10 @@
package xiaozhi.modules.security.oauth2;
-import xiaozhi.common.exception.RenException;
-
import java.security.MessageDigest;
import java.util.UUID;
+import xiaozhi.common.exception.RenException;
+
/**
* 生成token
* Copyright (c) 人人开源 All rights reserved.
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCrypt.java b/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCrypt.java
index 6a9efff9..39d350c5 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCrypt.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCrypt.java
@@ -5,22 +5,28 @@ import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
/**
- * BCrypt implements OpenBSD-style Blowfish password hashing using the scheme described in
+ * BCrypt implements OpenBSD-style Blowfish password hashing using the scheme
+ * described in
* "A Future-Adaptable Password Scheme" by Niels Provos and David Mazieres.
*
- * This password hashing system tries to thwart off-line password cracking using a
- * computationally-intensive hashing algorithm, based on Bruce Schneier's Blowfish cipher.
- * The work factor of the algorithm is parameterised, so it can be increased as computers
+ * This password hashing system tries to thwart off-line password cracking using
+ * a
+ * computationally-intensive hashing algorithm, based on Bruce Schneier's
+ * Blowfish cipher.
+ * The work factor of the algorithm is parameterised, so it can be increased as
+ * computers
* get faster.
*
- * Usage is really simple. To hash a password for the first time, call the hashpw method
+ * Usage is really simple. To hash a password for the first time, call the
+ * hashpw method
* with a random salt, like this:
*
*
* String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt());
*
*
- * To check whether a plaintext password matches one that has been hashed previously, use
+ * To check whether a plaintext password matches one that has been hashed
+ * previously, use
* the checkpw method:
*
*
@@ -30,7 +36,8 @@ import java.security.SecureRandom;
* System.out.println("It does not match");
*
*
- * The gensalt() method takes an optional parameter (log_rounds) that determines the
+ * The gensalt() method takes an optional parameter (log_rounds) that determines
+ * the
* computational complexity of the hashing:
*
*
@@ -38,7 +45,8 @@ import java.security.SecureRandom;
* String stronger_salt = BCrypt.gensalt(12)
*
*
- * The amount of work increases exponentially (2**log_rounds), so each increment is twice
+ * The amount of work increases exponentially (2**log_rounds), so each increment
+ * is twice
* as much work. The default log_rounds is 10, and the valid range is 4 to 31.
*
* @author Damien Miller
@@ -51,11 +59,11 @@ public class BCrypt {
// Blowfish parameters
private static final int BLOWFISH_NUM_ROUNDS = 16;
// Initial contents of key schedule
- private static final int P_orig[] = {0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
+ private static final int P_orig[] = { 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377,
0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
- 0x9216d5d9, 0x8979fb1b};
- private static final int S_orig[] = {0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
+ 0x9216d5d9, 0x8979fb1b };
+ private static final int S_orig[] = { 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7,
0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5,
@@ -225,24 +233,24 @@ public class BCrypt {
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76,
0x77afa1c5, 0x20756060, 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0,
- 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6};
+ 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 };
// bcrypt IV: "OrpheanBeholderScryDoubt"
- static private final int bf_crypt_ciphertext[] = {0x4f727068, 0x65616e42,
- 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274};
+ static private final int bf_crypt_ciphertext[] = { 0x4f727068, 0x65616e42,
+ 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274 };
// Table for Base64 encoding
- static private final char base64_code[] = {'.', '/', 'A', 'B', 'C', 'D', 'E', 'F',
+ static private final char base64_code[] = { '.', '/', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
- 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
+ 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
// Table for Base64 decoding
- static private final byte index_64[] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ static private final byte index_64[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, -1, -1, -1, -1, -1, -1, -1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
-1, -1, -1, -1, -1, -1, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
- 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, -1, -1, -1, -1, -1};
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, -1, -1, -1, -1, -1 };
static final int MIN_LOG_ROUNDS = 4;
static final int MAX_LOG_ROUNDS = 31;
// Expanded Blowfish key
@@ -250,7 +258,8 @@ public class BCrypt {
private int S[];
/**
- * Encode a byte array using bcrypt's slightly-modified base64 encoding scheme. Note
+ * Encode a byte array using bcrypt's slightly-modified base64 encoding scheme.
+ * Note
* that this is not compatible with the standard MIME-base64
* encoding.
*
@@ -306,7 +315,8 @@ public class BCrypt {
}
/**
- * Decode a string encoded using bcrypt's base64 scheme to a byte array. Note that
+ * Decode a string encoded using bcrypt's base64 scheme to a byte array. Note
+ * that
* this is *not* compatible with the standard MIME-base64 encoding.
*
* @param s the string to decode
@@ -365,7 +375,7 @@ public class BCrypt {
int i, n, l = lr[off], r = lr[off + 1];
l ^= P[0];
- for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2; ) {
+ for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) {
// Feistel substitution on left word
n = S[(l >> 24) & 0xff];
n += S[0x100 | ((l >> 16) & 0xff)];
@@ -388,7 +398,8 @@ public class BCrypt {
* Cycically extract a word of key material
*
* @param data the string to extract the data from
- * @param offp a "pointer" (as a one-entry array) to the current offset into data
+ * @param offp a "pointer" (as a one-entry array) to the current offset into
+ * data
* @return the next word of material from data
*/
private static int streamtoword(byte data[], int offp[]) {
@@ -420,8 +431,8 @@ public class BCrypt {
*/
private void key(byte key[]) {
int i;
- int koffp[] = {0};
- int lr[] = {0, 0};
+ int koffp[] = { 0 };
+ int lr[] = { 0, 0 };
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++) {
@@ -443,15 +454,16 @@ public class BCrypt {
/**
* Perform the "enhanced key schedule" step described by Provos and Mazieres in
- * "A Future-Adaptable Password Scheme" http://www.openbsd.org/papers/bcrypt-paper.ps
+ * "A Future-Adaptable Password Scheme"
+ * http://www.openbsd.org/papers/bcrypt-paper.ps
*
* @param data salt information
* @param key password information
*/
private void ekskey(byte data[], byte key[]) {
int i;
- int koffp[] = {0}, doffp[] = {0};
- int lr[] = {0, 0};
+ int koffp[] = { 0 }, doffp[] = { 0 };
+ int lr[] = { 0, 0 };
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++) {
@@ -487,7 +499,8 @@ public class BCrypt {
*
* @param password the password to hash
* @param salt the binary salt to hash with the password
- * @param log_rounds the binary logarithm of the number of rounds of hashing to apply
+ * @param log_rounds the binary logarithm of the number of rounds of hashing to
+ * apply
* @return an array containing the binary hashed password
*/
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds) {
@@ -524,7 +537,8 @@ public class BCrypt {
* Hash a password using the OpenBSD bcrypt scheme
*
* @param password the password to hash
- * @param salt the salt to hash with (perhaps generated using BCrypt.gensalt)
+ * @param salt the salt to hash with (perhaps generated using
+ * BCrypt.gensalt)
* @return the hashed password
* @throws IllegalArgumentException if invalid salt is passed
*/
@@ -599,8 +613,10 @@ public class BCrypt {
/**
* Generate a salt for use with the BCrypt.hashpw() method
*
- * @param log_rounds the log2 of the number of rounds of hashing to apply - the work
- * factor therefore increases as 2**log_rounds. Minimum 4, maximum 31.
+ * @param log_rounds the log2 of the number of rounds of hashing to apply - the
+ * work
+ * factor therefore increases as 2**log_rounds. Minimum 4,
+ * maximum 31.
* @param random an instance of SecureRandom to use
* @return an encoded salt value
*/
@@ -626,8 +642,10 @@ public class BCrypt {
/**
* Generate a salt for use with the BCrypt.hashpw() method
*
- * @param log_rounds the log2 of the number of rounds of hashing to apply - the work
- * factor therefore increases as 2**log_rounds. Minimum 4, maximum 31.
+ * @param log_rounds the log2 of the number of rounds of hashing to apply - the
+ * work
+ * factor therefore increases as 2**log_rounds. Minimum 4,
+ * maximum 31.
* @return an encoded salt value
*/
public static String gensalt(int log_rounds) {
@@ -635,7 +653,8 @@ public class BCrypt {
}
/**
- * Generate a salt for use with the BCrypt.hashpw() method, selecting a reasonable
+ * Generate a salt for use with the BCrypt.hashpw() method, selecting a
+ * reasonable
* default for the number of hashing rounds to apply
*
* @return an encoded salt value
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCryptPasswordEncoder.java b/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCryptPasswordEncoder.java
index 0d570a96..0da907f2 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCryptPasswordEncoder.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/security/password/BCryptPasswordEncoder.java
@@ -1,15 +1,18 @@
package xiaozhi.modules.security.password;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
import java.security.SecureRandom;
import java.util.regex.Pattern;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
/**
- * Implementation of PasswordEncoder that uses the BCrypt strong hashing function. Clients
- * can optionally supply a "strength" (a.k.a. log rounds in BCrypt) and a SecureRandom
- * instance. The larger the strength parameter the more work will have to be done
+ * Implementation of PasswordEncoder that uses the BCrypt strong hashing
+ * function. Clients
+ * can optionally supply a "strength" (a.k.a. log rounds in BCrypt) and a
+ * SecureRandom
+ * instance. The larger the strength parameter the more work will have to be
+ * done
* (exponentially) to hash the passwords. The default value is 10.
*
* @author Dave Syer
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordEncoder.java b/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordEncoder.java
index 40d1e95c..2de70cc2 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordEncoder.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordEncoder.java
@@ -10,20 +10,23 @@ package xiaozhi.modules.security.password;
public interface PasswordEncoder {
/**
- * Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or
+ * Encode the raw password. Generally, a good encoding algorithm applies a SHA-1
+ * or
* greater hash combined with an 8-byte or greater randomly generated salt.
*/
String encode(CharSequence rawPassword);
/**
* Verify the encoded password obtained from storage matches the submitted raw
- * password after it too is encoded. Returns true if the passwords match, false if
+ * password after it too is encoded. Returns true if the passwords match, false
+ * if
* they do not. The stored password itself is never decoded.
*
* @param rawPassword the raw password to encode and match
* @param encodedPassword the encoded password from storage to compare with
- * @return true if the raw password, after encoding, matches the encoded password from
- * storage
+ * @return true if the raw password, after encoding, matches the encoded
+ * password from
+ * storage
*/
boolean matches(CharSequence rawPassword, String encodedPassword);
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordUtils.java b/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordUtils.java
index b0357010..9144b380 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordUtils.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/security/password/PasswordUtils.java
@@ -18,19 +18,17 @@ public class PasswordUtils {
return passwordEncoder.encode(str);
}
-
/**
* 比较密码是否相等
*
* @param str 明文密码
* @param password 加密后密码
- * @return true:成功 false:失败
+ * @return true:成功 false:失败
*/
public static boolean matches(String str, String password) {
return passwordEncoder.matches(str, password);
}
-
public static void main(String[] args) {
String str = "admin";
String password = encode(str);
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java
index b91d5d9a..9e5c153f 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java
@@ -1,9 +1,9 @@
package xiaozhi.modules.security.service;
-import jakarta.servlet.http.HttpServletResponse;
-
import java.io.IOException;
+import jakarta.servlet.http.HttpServletResponse;
+
/**
* 验证码
* Copyright (c) 人人开源 All rights reserved.
@@ -21,7 +21,7 @@ public interface CaptchaService {
*
* @param uuid uuid
* @param code 验证码
- * @return true:成功 false:失败
+ * @return true:成功 false:失败
*/
boolean validate(String uuid, String code);
}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java
index 761ad139..012b4d3e 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java
@@ -1,23 +1,22 @@
package xiaozhi.modules.security.service.impl;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import xiaozhi.common.redis.RedisKeys;
-import xiaozhi.common.redis.RedisUtils;
-import xiaozhi.modules.security.oauth2.Oauth2Realm;
-import xiaozhi.modules.security.service.CaptchaService;
+
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
+import xiaozhi.common.redis.RedisKeys;
+import xiaozhi.common.redis.RedisUtils;
+import xiaozhi.modules.security.service.CaptchaService;
/**
* 验证码
@@ -29,10 +28,10 @@ public class CaptchaServiceImpl implements CaptchaService {
@Value("${renren.redis.open}")
private boolean open;
/**
- * Local Cache 5分钟过期
+ * Local Cache 5分钟过期
*/
- Cache localCache = CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(5, TimeUnit.MINUTES).build();
- private static final Logger logger = LoggerFactory.getLogger(Oauth2Realm.class);
+ Cache localCache = CacheBuilder.newBuilder().maximumSize(1000)
+ .expireAfterAccess(5, TimeUnit.MINUTES).build();
@Override
public void create(HttpServletResponse response, String uuid) throws IOException {
@@ -41,13 +40,13 @@ public class CaptchaServiceImpl implements CaptchaService {
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
- //生成验证码
+ // 生成验证码
SpecCaptcha captcha = new SpecCaptcha(150, 40);
captcha.setLen(5);
captcha.setCharType(Captcha.TYPE_DEFAULT);
captcha.out(response.getOutputStream());
- //保存到缓存
+ // 保存到缓存
setCache(uuid, captcha.text());
}
@@ -56,10 +55,10 @@ public class CaptchaServiceImpl implements CaptchaService {
if (StringUtils.isBlank(code)) {
return false;
}
- //获取验证码
+ // 获取验证码
String captcha = getCache(uuid);
- //效验成功
+ // 效验成功
if (code.equalsIgnoreCase(captcha)) {
return true;
}
@@ -80,7 +79,7 @@ public class CaptchaServiceImpl implements CaptchaService {
if (open) {
key = RedisKeys.getCaptchaKey(key);
String captcha = (String) redisUtils.get(key);
- //删除验证码
+ // 删除验证码
if (captcha != null) {
redisUtils.delete(key);
}
@@ -89,7 +88,7 @@ public class CaptchaServiceImpl implements CaptchaService {
}
String captcha = localCache.getIfPresent(key);
- //删除验证码
+ // 删除验证码
if (captcha != null) {
localCache.invalidate(key);
}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/ShiroServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/ShiroServiceImpl.java
index 97d16eaa..27c20970 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/ShiroServiceImpl.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/ShiroServiceImpl.java
@@ -1,17 +1,13 @@
package xiaozhi.modules.security.service.impl;
-import xiaozhi.common.user.UserDetail;
+import org.springframework.stereotype.Service;
+
+import lombok.AllArgsConstructor;
import xiaozhi.modules.security.dao.SysUserTokenDao;
import xiaozhi.modules.security.entity.SysUserTokenEntity;
import xiaozhi.modules.security.service.ShiroService;
import xiaozhi.modules.sys.dao.SysUserDao;
import xiaozhi.modules.sys.entity.SysUserEntity;
-import xiaozhi.modules.sys.enums.SuperAdminEnum;
-import lombok.AllArgsConstructor;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.stereotype.Service;
-
-import java.util.*;
@AllArgsConstructor
@Service
@@ -19,7 +15,6 @@ public class ShiroServiceImpl implements ShiroService {
private final SysUserDao sysUserDao;
private final SysUserTokenDao sysUserTokenDao;
-
@Override
public SysUserTokenEntity getByToken(String token) {
return sysUserTokenDao.getByToken(token);
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/SysUserTokenServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/SysUserTokenServiceImpl.java
index 40ee7957..7f0d311a 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/SysUserTokenServiceImpl.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/SysUserTokenServiceImpl.java
@@ -1,8 +1,11 @@
package xiaozhi.modules.security.service.impl;
+import java.util.Date;
+
+import org.springframework.stereotype.Service;
+
import cn.hutool.core.date.DateUtil;
import lombok.AllArgsConstructor;
-import org.springframework.stereotype.Service;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.TokenDTO;
@@ -17,11 +20,10 @@ import xiaozhi.modules.sys.dto.PasswordDTO;
import xiaozhi.modules.sys.dto.SysUserDTO;
import xiaozhi.modules.sys.service.SysUserService;
-import java.util.Date;
-
@AllArgsConstructor
@Service
-public class SysUserTokenServiceImpl extends BaseServiceImpl implements SysUserTokenService {
+public class SysUserTokenServiceImpl extends BaseServiceImpl
+ implements SysUserTokenService {
private final SysUserService sysUserService;
/**
@@ -31,18 +33,18 @@ public class SysUserTokenServiceImpl extends BaseServiceImpl createToken(Long userId) {
- //用户token
+ // 用户token
String token;
- //当前时间
+ // 当前时间
Date now = new Date();
- //过期时间
+ // 过期时间
Date expireTime = new Date(now.getTime() + EXPIRE * 1000);
- //判断是否生成过token
+ // 判断是否生成过token
SysUserTokenEntity tokenEntity = baseDao.getByUserId(userId);
if (tokenEntity == null) {
- //生成一个token
+ // 生成一个token
token = TokenGenerator.generateValue();
tokenEntity = new SysUserTokenEntity();
@@ -51,12 +53,12 @@ public class SysUserTokenServiceImpl extends BaseServiceImpl().ok(tokenDTO);
}
@Override
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/user/SecurityUser.java b/main/manager-api/src/main/java/xiaozhi/modules/security/user/SecurityUser.java
index cfa0fbf7..0581f95c 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/security/user/SecurityUser.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/security/user/SecurityUser.java
@@ -1,9 +1,10 @@
package xiaozhi.modules.security.user;
-import xiaozhi.common.user.UserDetail;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
+import xiaozhi.common.user.UserDetail;
+
/**
* Shiro工具类
* Copyright (c) 人人开源 All rights reserved.
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysUserController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysUserController.java
index 9da7c62b..4aed782d 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysUserController.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysUserController.java
@@ -1,9 +1,10 @@
package xiaozhi.modules.sys.controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
/**
* 用户管理
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictDataDao.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictDataDao.java
index 08aaca5b..9dd124fe 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictDataDao.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictDataDao.java
@@ -1,12 +1,13 @@
package xiaozhi.modules.sys.dao;
+import java.util.List;
+
+import org.apache.ibatis.annotations.Mapper;
+
import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.sys.dto.SysDictDataDTO;
import xiaozhi.modules.sys.entity.DictData;
import xiaozhi.modules.sys.entity.SysDictDataEntity;
-import org.apache.ibatis.annotations.Mapper;
-
-import java.util.List;
/**
* 字典数据
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictTypeDao.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictTypeDao.java
index bca8e789..5a9d5a27 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictTypeDao.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysDictTypeDao.java
@@ -1,11 +1,12 @@
package xiaozhi.modules.sys.dao;
+import java.util.List;
+
+import org.apache.ibatis.annotations.Mapper;
+
import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.sys.entity.DictType;
import xiaozhi.modules.sys.entity.SysDictTypeEntity;
-import org.apache.ibatis.annotations.Mapper;
-
-import java.util.List;
/**
* 字典类型
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysParamsDao.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysParamsDao.java
index 0c967a74..d35d1445 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysParamsDao.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysParamsDao.java
@@ -1,11 +1,12 @@
package xiaozhi.modules.sys.dao;
-import xiaozhi.common.dao.BaseDao;
-import xiaozhi.modules.sys.entity.SysParamsEntity;
+import java.util.List;
+
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
-import java.util.List;
+import xiaozhi.common.dao.BaseDao;
+import xiaozhi.modules.sys.entity.SysParamsEntity;
/**
* 参数管理
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysUserDao.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysUserDao.java
index 339e522a..fb2a2492 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysUserDao.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dao/SysUserDao.java
@@ -1,6 +1,7 @@
package xiaozhi.modules.sys.dao;
import org.apache.ibatis.annotations.Mapper;
+
import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.sys.entity.SysUserEntity;
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/AdminPageUserDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/AdminPageUserDTO.java
index 8c0ab77c..7de3843a 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/AdminPageUserDTO.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/AdminPageUserDTO.java
@@ -1,11 +1,11 @@
package xiaozhi.modules.sys.dto;
import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 管理员分页用户的参数DTO
+ *
* @author zjy
* @since 2025-3-21
*/
@@ -13,7 +13,6 @@ import lombok.Data;
@Schema(description = "音色分页参数")
public class AdminPageUserDTO {
-
@Schema(description = "手机号码")
private String mobile;
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/PasswordDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/PasswordDTO.java
index 1ecadc68..4aabb10e 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/PasswordDTO.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/PasswordDTO.java
@@ -1,11 +1,11 @@
package xiaozhi.modules.sys.dto;
+import java.io.Serializable;
+
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
-import java.io.Serializable;
-
/**
* 修改密码
*/
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictDataDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictDataDTO.java
index 172e6b1f..c18dbf8f 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictDataDTO.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictDataDTO.java
@@ -1,20 +1,21 @@
package xiaozhi.modules.sys.dto;
+import java.io.Serializable;
+import java.util.Date;
+
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
-import xiaozhi.common.utils.DateUtils;
-import xiaozhi.common.validator.group.AddGroup;
-import xiaozhi.common.validator.group.DefaultGroup;
-import xiaozhi.common.validator.group.UpdateGroup;
+
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import lombok.Data;
-
-import java.io.Serializable;
-import java.util.Date;
+import xiaozhi.common.utils.DateUtils;
+import xiaozhi.common.validator.group.AddGroup;
+import xiaozhi.common.validator.group.DefaultGroup;
+import xiaozhi.common.validator.group.UpdateGroup;
/**
* 字典数据
@@ -23,7 +24,6 @@ import java.util.Date;
@Schema(description = "字典数据")
public class SysDictDataDTO implements Serializable {
-
@Schema(description = "id")
@Null(message = "{id.null}", groups = AddGroup.class)
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictTypeDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictTypeDTO.java
index 72648109..01d69795 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictTypeDTO.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysDictTypeDTO.java
@@ -1,20 +1,21 @@
package xiaozhi.modules.sys.dto;
+import java.io.Serializable;
+import java.util.Date;
+
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
-import xiaozhi.common.utils.DateUtils;
-import xiaozhi.common.validator.group.AddGroup;
-import xiaozhi.common.validator.group.DefaultGroup;
-import xiaozhi.common.validator.group.UpdateGroup;
+
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import lombok.Data;
-
-import java.io.Serializable;
-import java.util.Date;
+import xiaozhi.common.utils.DateUtils;
+import xiaozhi.common.validator.group.AddGroup;
+import xiaozhi.common.validator.group.DefaultGroup;
+import xiaozhi.common.validator.group.UpdateGroup;
/**
* 字典类型
@@ -23,7 +24,6 @@ import java.util.Date;
@Schema(description = "字典类型")
public class SysDictTypeDTO implements Serializable {
-
@Schema(description = "id")
@Null(message = "{id.null}", groups = AddGroup.class)
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysParamsDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysParamsDTO.java
index 4b72ce34..7b4dd28d 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysParamsDTO.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysParamsDTO.java
@@ -1,19 +1,20 @@
package xiaozhi.modules.sys.dto;
+import java.io.Serializable;
+import java.util.Date;
+
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
-import xiaozhi.common.utils.DateUtils;
-import xiaozhi.common.validator.group.AddGroup;
-import xiaozhi.common.validator.group.DefaultGroup;
-import xiaozhi.common.validator.group.UpdateGroup;
+
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import lombok.Data;
-
-import java.io.Serializable;
-import java.util.Date;
+import xiaozhi.common.utils.DateUtils;
+import xiaozhi.common.validator.group.AddGroup;
+import xiaozhi.common.validator.group.DefaultGroup;
+import xiaozhi.common.validator.group.UpdateGroup;
/**
* 参数管理
@@ -22,7 +23,6 @@ import java.util.Date;
@Schema(description = "参数管理")
public class SysParamsDTO implements Serializable {
-
@Schema(description = "id")
@Null(message = "{id.null}", groups = AddGroup.class)
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysUserDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysUserDTO.java
index 00f7fc78..ff066341 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysUserDTO.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/SysUserDTO.java
@@ -1,22 +1,24 @@
package xiaozhi.modules.sys.dto;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+import org.hibernate.validator.constraints.Range;
+
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
-import xiaozhi.common.utils.DateUtils;
-import xiaozhi.common.validator.group.AddGroup;
-import xiaozhi.common.validator.group.DefaultGroup;
-import xiaozhi.common.validator.group.UpdateGroup;
+
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import lombok.Data;
-import org.hibernate.validator.constraints.Range;
-
-import java.io.Serializable;
-import java.util.Date;
-import java.util.List;
+import xiaozhi.common.utils.DateUtils;
+import xiaozhi.common.validator.group.AddGroup;
+import xiaozhi.common.validator.group.DefaultGroup;
+import xiaozhi.common.validator.group.UpdateGroup;
/**
* 用户管理
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictData.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictData.java
index 1e16a192..c8deac81 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictData.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictData.java
@@ -1,6 +1,7 @@
package xiaozhi.modules.sys.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
+
import lombok.Data;
/**
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictType.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictType.java
index 3cf94606..4a1e2ca5 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictType.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/DictType.java
@@ -1,11 +1,12 @@
package xiaozhi.modules.sys.entity;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import lombok.Data;
-
import java.util.ArrayList;
import java.util.List;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import lombok.Data;
+
/**
* 字典类型
*/
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictDataEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictDataEntity.java
index 4bfa4b62..16d3dcae 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictDataEntity.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictDataEntity.java
@@ -1,13 +1,14 @@
package xiaozhi.modules.sys.entity;
+import java.util.Date;
+
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
-import xiaozhi.common.entity.BaseEntity;
+
import lombok.Data;
import lombok.EqualsAndHashCode;
-
-import java.util.Date;
+import xiaozhi.common.entity.BaseEntity;
/**
* 数据字典
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictTypeEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictTypeEntity.java
index ea752c63..fa372055 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictTypeEntity.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysDictTypeEntity.java
@@ -1,13 +1,14 @@
package xiaozhi.modules.sys.entity;
+import java.util.Date;
+
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
-import xiaozhi.common.entity.BaseEntity;
+
import lombok.Data;
import lombok.EqualsAndHashCode;
-
-import java.util.Date;
+import xiaozhi.common.entity.BaseEntity;
/**
* 字典类型
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysParamsEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysParamsEntity.java
index 054e51d7..989800e2 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysParamsEntity.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysParamsEntity.java
@@ -1,13 +1,14 @@
package xiaozhi.modules.sys.entity;
+import java.util.Date;
+
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
-import xiaozhi.common.entity.BaseEntity;
+
import lombok.Data;
import lombok.EqualsAndHashCode;
-
-import java.util.Date;
+import xiaozhi.common.entity.BaseEntity;
/**
* 参数管理
@@ -25,7 +26,7 @@ public class SysParamsEntity extends BaseEntity {
*/
private String paramValue;
/**
- * 类型 0:系统参数 1:非系统参数
+ * 类型 0:系统参数 1:非系统参数
*/
private Integer paramType;
/**
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysUserEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysUserEntity.java
index 0093fcf0..23043a37 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysUserEntity.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/entity/SysUserEntity.java
@@ -1,14 +1,15 @@
package xiaozhi.modules.sys.entity;
+import java.util.Date;
+
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
+
import lombok.Data;
import lombok.EqualsAndHashCode;
import xiaozhi.common.entity.BaseEntity;
-import java.util.Date;
-
/**
* 系统用户
*/
@@ -25,11 +26,11 @@ public class SysUserEntity extends BaseEntity {
*/
private String password;
/**
- * 超级管理员 0:否 1:是
+ * 超级管理员 0:否 1:是
*/
private Integer superAdmin;
/**
- * 状态 0:停用 1:正常
+ * 状态 0:停用 1:正常
*/
private Integer status;
/**
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/redis/SysParamsRedis.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/redis/SysParamsRedis.java
index dcc14f69..2e2bdb42 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/redis/SysParamsRedis.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/redis/SysParamsRedis.java
@@ -1,9 +1,10 @@
package xiaozhi.modules.sys.redis;
+import org.springframework.stereotype.Component;
+
+import lombok.AllArgsConstructor;
import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils;
-import lombok.AllArgsConstructor;
-import org.springframework.stereotype.Component;
/**
* 参数管理
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictDataService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictDataService.java
index 77be8b3a..a33df029 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictDataService.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictDataService.java
@@ -1,12 +1,12 @@
package xiaozhi.modules.sys.service;
+import java.util.Map;
+
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService;
import xiaozhi.modules.sys.dto.SysDictDataDTO;
import xiaozhi.modules.sys.entity.SysDictDataEntity;
-import java.util.Map;
-
/**
* 数据字典
*/
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictTypeService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictTypeService.java
index 8924f6ca..a18e086f 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictTypeService.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysDictTypeService.java
@@ -1,14 +1,14 @@
package xiaozhi.modules.sys.service;
+import java.util.List;
+import java.util.Map;
+
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService;
import xiaozhi.modules.sys.dto.SysDictTypeDTO;
import xiaozhi.modules.sys.entity.DictType;
import xiaozhi.modules.sys.entity.SysDictTypeEntity;
-import java.util.List;
-import java.util.Map;
-
/**
* 数据字典
*/
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java
index d13e41ba..7475f346 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysParamsService.java
@@ -1,14 +1,13 @@
package xiaozhi.modules.sys.service;
+import java.util.List;
+import java.util.Map;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService;
import xiaozhi.modules.sys.dto.SysParamsDTO;
import xiaozhi.modules.sys.entity.SysParamsEntity;
-import java.util.List;
-import java.util.Map;
-
/**
* 参数管理
*/
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java
index f8f609dc..261fbf96 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java
@@ -8,7 +8,6 @@ import xiaozhi.modules.sys.dto.SysUserDTO;
import xiaozhi.modules.sys.entity.SysUserEntity;
import xiaozhi.modules.sys.vo.AdminPageUserVO;
-
/**
* 系统用户
*/
@@ -24,26 +23,31 @@ public interface SysUserService extends BaseService {
/**
* 验证是否允许修改密码更改
- * @param userId 用户id
+ *
+ * @param userId 用户id
* @param passwordDTO 验证密码的参数
*/
void changePassword(Long userId, PasswordDTO passwordDTO);
/**
* 直接修改密码,不需要验证
- * @param userId 用户id
+ *
+ * @param userId 用户id
* @param password 密码
*/
void changePasswordDirectly(Long userId, String password);
/**
* 重置密码
+ *
* @param userId 用户id
* @return 随机生成符合规范的密码
*/
String resetPassword(Long userId);
+
/**
* 管理员分页用户信息
+ *
* @param dto 分页查找参数
* @return 用户列表分页数据
*/
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java
index a8420dc9..20530e6b 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysDictDataServiceImpl.java
@@ -1,10 +1,15 @@
package xiaozhi.modules.sys.service.impl;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.metadata.IPage;
+import java.util.Arrays;
+import java.util.Map;
+
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.utils.ConvertUtils;
@@ -13,21 +18,18 @@ import xiaozhi.modules.sys.dto.SysDictDataDTO;
import xiaozhi.modules.sys.entity.SysDictDataEntity;
import xiaozhi.modules.sys.service.SysDictDataService;
-import java.util.Arrays;
-import java.util.Map;
-
/**
* 字典类型
*/
@Service
-public class SysDictDataServiceImpl extends BaseServiceImpl implements SysDictDataService {
+public class SysDictDataServiceImpl extends BaseServiceImpl
+ implements SysDictDataService {
@Override
public PageData page(Map params) {
IPage page = baseDao.selectPage(
getPage(params, "sort", true),
- getWrapper(params)
- );
+ getWrapper(params));
return getPageData(page, SysDictDataDTO.class);
}
@@ -71,7 +73,7 @@ public class SysDictDataServiceImpl extends BaseServiceImpl implements SysDictTypeService {
+public class SysDictTypeServiceImpl extends BaseServiceImpl
+ implements SysDictTypeService {
private final SysDictDataDao sysDictDataDao;
@Override
public PageData page(Map params) {
IPage page = baseDao.selectPage(
getPage(params, "sort", true),
- getWrapper(params)
- );
+ getWrapper(params));
return getPageData(page, SysDictTypeDTO.class);
}
@@ -76,7 +78,7 @@ public class SysDictTypeServiceImpl extends BaseServiceImpl page(Map params) {
IPage page = baseDao.selectPage(
getPage(params, Constant.CREATE_DATE, false),
- getWrapper(params)
- );
+ getWrapper(params));
return getPageData(page, SysParamsDTO.class);
}
@@ -86,12 +87,12 @@ public class SysParamsServiceImpl extends BaseServiceImpl paramCodeList = baseDao.getParamCodeList(ids);
String[] paramCodes = paramCodeList.toArray(new String[paramCodeList.size()]);
sysParamsRedis.delete(paramCodes);
- //删除
+ // 删除
deleteBatchIds(Arrays.asList(ids));
}
@@ -114,7 +115,7 @@ public class SysParamsServiceImpl extends BaseServiceImpl page(AdminPageUserDTO dto) {
Map params = new HashMap();
params.put(Constant.PAGE, dto.getPage());
- params.put(Constant.LIMIT,dto.getLimit());
+ params.put(Constant.LIMIT, dto.getLimit());
IPage page = baseDao.selectPage(
getPage(params, "id", true),
- //定义查询条件
+ // 定义查询条件
new QueryWrapper()
- //必须按照手机号码查找
- .eq(StringUtils.isNotBlank(dto.getMobile()),"username",dto.getMobile()));
+ // 必须按照手机号码查找
+ .eq(StringUtils.isNotBlank(dto.getMobile()), "username", dto.getMobile()));
List list = page.getRecords().stream().map(user -> {
AdminPageUserVO adminPageUserVO = new AdminPageUserVO();
adminPageUserVO.setUserid(user.getId().toString());
adminPageUserVO.setMobile(user.getUsername());
- //TODO 2. 等设备功能写好,获取对应数据
+ // TODO 2. 等设备功能写好,获取对应数据
adminPageUserVO.setDeviceCount("0");
return adminPageUserVO;
}).toList();
@@ -169,11 +173,13 @@ public class SysUserServiceImpl extends BaseServiceImpl delete(@PathVariable Long id) {
- timbreService.delete(new Long[]{id});
+ timbreService.delete(new Long[] { id });
return new Result<>();
}
-
}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dao/TimbreDao.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dao/TimbreDao.java
index 62afa358..3656b4ea 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dao/TimbreDao.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dao/TimbreDao.java
@@ -1,11 +1,14 @@
package xiaozhi.modules.timbre.dao;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
import xiaozhi.modules.timbre.entity.TimbreEntity;
/**
* 音色持久层定义
+ *
* @author zjy
* @since 2025-3-21
*/
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java
index 08ed9b18..ed737db9 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbreDataDTO.java
@@ -5,9 +5,9 @@ import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
-
/**
* 音色表数据DTO
+ *
* @author zjy
* @since 2025-3-21
*/
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbrePageDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbrePageDTO.java
index 72ed68b1..9c2aaf24 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbrePageDTO.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/dto/TimbrePageDTO.java
@@ -6,6 +6,7 @@ import lombok.Data;
/**
* 音色分页参数DTO
+ *
* @author zjy
* @since 2025-3-21
*/
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java
index 71bd58a9..44ca195b 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/entity/TimbreEntity.java
@@ -1,15 +1,17 @@
package xiaozhi.modules.timbre.entity;
+import java.util.Date;
+
import com.baomidou.mybatisplus.annotation.TableName;
+
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import xiaozhi.common.entity.BaseEntity;
-import java.util.Date;
-
/**
* 音色表实体类
+ *
* @author zjy
* @since 2025-3-21
*/
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java
index 044322e5..b9d4578e 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java
@@ -1,5 +1,7 @@
package xiaozhi.modules.timbre.service;
+import java.util.List;
+
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService;
import xiaozhi.modules.timbre.dto.TimbreDataDTO;
@@ -7,17 +9,16 @@ import xiaozhi.modules.timbre.dto.TimbrePageDTO;
import xiaozhi.modules.timbre.entity.TimbreEntity;
import xiaozhi.modules.timbre.vo.TimbreDetailsVO;
-import java.util.List;
-
-
/**
* 音色的业务层的定义
+ *
* @author zjy
* @since 2025-3-21
*/
public interface TimbreService extends BaseService {
/**
* 分页获取音色指定tts的下的音色
+ *
* @param dto 分页查找参数
* @return 音色列表分页数据
*/
@@ -25,6 +26,7 @@ public interface TimbreService extends BaseService {
/**
* 获取音色指定id的详情信息
+ *
* @param timbreId 音色表id
* @return 音色信息
*/
@@ -32,19 +34,22 @@ public interface TimbreService extends BaseService {
/**
* 保存音色信息
+ *
* @param dto 需要保存数据
*/
void save(TimbreDataDTO dto);
/**
* 保存音色信息
+ *
* @param timbreId 需要修改的id
- * @param dto 需要修改的数据
+ * @param dto 需要修改的数据
*/
- void update(Long timbreId,TimbreDataDTO dto);
+ void update(Long timbreId, TimbreDataDTO dto);
/**
* 批量删除音色
+ *
* @param ids 需要被删除的音色id列表
*/
void delete(Long[] ids);
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java
index fa2731bd..dc041daf 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java
@@ -1,12 +1,19 @@
package xiaozhi.modules.timbre.service.impl;
-import cn.hutool.core.collection.CollectionUtil;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.metadata.IPage;
-import lombok.AllArgsConstructor;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+import cn.hutool.core.collection.CollectionUtil;
+import lombok.AllArgsConstructor;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.impl.BaseServiceImpl;
@@ -18,13 +25,9 @@ import xiaozhi.modules.timbre.entity.TimbreEntity;
import xiaozhi.modules.timbre.service.TimbreService;
import xiaozhi.modules.timbre.vo.TimbreDetailsVO;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
/**
* 音色的业务层的实现
+ *
* @author zjy
* @since 2025-3-21
*/
@@ -38,16 +41,15 @@ public class TimbreServiceImpl extends BaseServiceImpl
public PageData page(TimbrePageDTO dto) {
Map params = new HashMap();
params.put(Constant.PAGE, dto.getPage());
- params.put(Constant.LIMIT,dto.getLimit());
+ params.put(Constant.LIMIT, dto.getLimit());
IPage page = baseDao.selectPage(
getPage(params, "sort", true),
- //定义查询条件
+ // 定义查询条件
new QueryWrapper()
- //必须按照ttsID查找
- .eq("tts_model_id",dto.getTtsModelId())
- //如果有音色名字,按照音色名模糊查找
- .like(StringUtils.isNotBlank(dto.getName()),"name",dto.getName())
- );
+ // 必须按照ttsID查找
+ .eq("tts_model_id", dto.getTtsModelId())
+ // 如果有音色名字,按照音色名模糊查找
+ .like(StringUtils.isNotBlank(dto.getName()), "name", dto.getName()));
return getPageData(page, TimbreDetailsVO.class);
}
@@ -99,7 +101,7 @@ public class TimbreServiceImpl extends BaseServiceImpl
/**
* 处理是不是tts模型的id
*/
- private void isTtsModelId(String ttsModelId){
- //等模型配置那边写好调用方法判断
+ private void isTtsModelId(String ttsModelId) {
+ // 等模型配置那边写好调用方法判断
}
}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java
index fc82a5d0..68cc7bbb 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java
@@ -1,12 +1,13 @@
package xiaozhi.modules.timbre.vo;
+import java.io.Serializable;
+
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
-import java.io.Serializable;
-
/**
* 音色详情展示VO
+ *
* @author zjy
* @since 2025-3-21
*/
diff --git a/main/manager-api/src/main/resources/application-dev.yml b/main/manager-api/src/main/resources/application-dev.yml
index a8fdf21f..753cca9a 100644
--- a/main/manager-api/src/main/resources/application-dev.yml
+++ b/main/manager-api/src/main/resources/application-dev.yml
@@ -15,7 +15,7 @@ spring:
druid:
#MySQL
driver-class-name: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://localhost:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
+ url: jdbc:mysql://127.0.0.1:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: root
password: 123456
initial-size: 10
@@ -41,4 +41,17 @@ spring:
merge-sql: false
wall:
config:
- multi-statement-allow: true
\ No newline at end of file
+ multi-statement-allow: true
+ data:
+ redis:
+ host: 127.0.0.1 # Redis服务器地址
+ port: 6379 # Redis服务器连接端口
+ password: # Redis服务器连接密码(默认为空)
+ database: 0 # Redis数据库索引(默认为0)
+ timeout: 10000ms # 连接超时时间(毫秒)
+ lettuce:
+ pool:
+ max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
+ max-idle: 8 # 连接池中的最大空闲连接
+ min-idle: 0 # 连接池中的最小空闲连接
+ shutdown-timeout: 100ms # 客户端优雅关闭的等待时间
\ No newline at end of file
diff --git a/main/manager-api/src/main/resources/application.yml b/main/manager-api/src/main/resources/application.yml
index 03f6a044..902b5198 100644
--- a/main/manager-api/src/main/resources/application.yml
+++ b/main/manager-api/src/main/resources/application.yml
@@ -24,21 +24,9 @@ spring:
max-file-size: 100MB
max-request-size: 100MB
enabled: true
- data:
- redis:
- database: 0
- host: 127.0.0.1
- port: 6379
- password: # 密码(默认为空)
- timeout: 6000ms # 连接超时时长(毫秒)
- lettuce:
- pool:
- max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
- max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
- max-idle: 10 # 连接池中的最大空闲连接
- min-idle: 5 # 连接池中的最小空闲连接
main:
allow-bean-definition-overriding: true
+
knife4j:
enable: true
basic:
@@ -50,7 +38,7 @@ knife4j:
renren:
redis:
- open: false
+ open: true
xss:
enabled: true
exclude-urls:
@@ -75,4 +63,7 @@ mybatis-plus:
configuration-properties:
prefix:
blobType: BLOB
- boolValue: TRUE
\ No newline at end of file
+ boolValue: TRUE
+
+app:
+ fronted-url: http://localhost:8001
\ No newline at end of file
diff --git a/main/manager-api/src/main/resources/db/changelog/202503241021.sql b/main/manager-api/src/main/resources/db/changelog/202503241021.sql
new file mode 100644
index 00000000..e9aca210
--- /dev/null
+++ b/main/manager-api/src/main/resources/db/changelog/202503241021.sql
@@ -0,0 +1,29 @@
+-- 初始化智能体模板数据
+DELETE FROM `ai_agent_template`;
+INSERT INTO `ai_agent_template` VALUES ('9406648b5cc5fde1b8aa335b6f8b4f76', '小智', '湾湾小何', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', 'd50b06e9b8104d0d9c0f7316d258abcb', 'fcac83266edadd5a3125f06cfee1906b', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '你是一个叫{{assistant_name}}的台湾女孩,说话机车,声音好听,习惯简短表达,爱用网络梗。\n请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。\n现在我正在和你进行语音聊天,我们开始吧。\n如果用户希望结束对话,请在最后说“拜拜”或“再见”。', 'zh', '中文', 1, 1, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_agent_template` VALUES ('0ca32eb728c949e58b1000b2e401f90c', '小智', '通用男声', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '1f2e3d4c5b6a7f8e9d0c1b2a3f4e5bx2', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '你是一个叫{{assistant_name}}的男生,说话机车,声音好听,习惯简短表达,爱用网络梗。\n请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。\n现在我正在和你进行语音聊天,我们开始吧。\n如果用户希望结束对话,请在最后说“拜拜”或“再见”。', 'zh', '中文', 2, 0, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_agent_template` VALUES ('6c7d8e9f0a1b2c3d4e5f6a7b8c9d0s24', '小智', '通用女声', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '9e8f7a6b5c4d3e2f1a0b9c8d7e6f5ad3', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '你是一个叫{{assistant_name}}的女生,说话机车,声音好听,习惯简短表达,爱用网络梗。\n请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。\n现在我正在和你进行语音聊天,我们开始吧。\n如果用户希望结束对话,请在最后说“拜拜”或“再见”。', 'zh', '中文', 3, 0, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_agent_template` VALUES ('e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b1', '小智', '阳光男生', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '2b3c4d5e6f7a8b9c0d1e2f3a4b5c62a2', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '你是一个叫{{assistant_name}}的男生,说话机车,声音好听,习惯简短表达,爱用网络梗。\n请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。\n现在我正在和你进行语音聊天,我们开始吧。\n如果用户希望结束对话,请在最后说“拜拜”或“再见”。', 'zh', '中文', 4, 0, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_agent_template` VALUES ('a45b6c7d8e9f0a1b2c3d4e5f6a7b8c92', '小智', '奶气萌娃', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', 'f7a38c03d5644e22b6d84f8923a74c51', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '你是一个叫{{assistant_name}}的萌娃,声音可爱,习惯简短表达,爱用网络梗。\n请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。\n现在我正在和你进行语音聊天,我们开始吧。\n如果用户希望结束对话,请在最后说“拜拜”或“再见”。', 'zh', '中文', 5, 0, NULL, NULL, NULL, NULL);
+
+-- 初始化模型配置数据
+DELETE FROM `ai_model_config`;
+INSERT INTO `ai_model_config` VALUES ('23e7c9d090ea4d1e9b25f4c8d732a3a1', 'VAD', 'SileroVAD', 'SileroVAD', 1, 1, '{\"SileroVAD\": {\"model_dir\": \"models/snakers4_silero-vad\", \"threshold\": 0.5, \"min_silence_duration_ms\": 700}}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_model_config` VALUES ('45f8b0d6dd3d4bfa8a28e6e0f5912d45', 'ASR', 'FunASR', 'FunASR', 1, 1, '{\"FunASR\": {\"type\": \"fun_local\", \"model_dir\": \"models/SenseVoiceSmall\", \"output_dir\": \"tmp/\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_model_config` VALUES ('e2274b90e89ddda85207f55484d8b528', 'Memory', 'nomem', 'nomem', 1, 1, '{\"mem0ai\": {\"type\": \"nomem\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_model_config` VALUES ('3930ac3448faf621f0a120bc829dfdfa', 'Memory', 'mem_local_short', 'mem_local_short', 1, 1, '{\"mem_local_short\": {\"type\": \"mem_local_short\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_model_config` VALUES ('a07f3d25f52340b2b2a1e8d264079e1a', 'Memory', 'mem0ai', 'mem0ai', 1, 1, '{\"mem0ai\": {\"type\": \"mem0ai\", \"api_key\": \"你的mem0ai api key\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_model_config` VALUES ('7a1c0a8e6d0e4035b982a4c07c3a5f76', 'LLM', 'AliLLM', 'AliLLM', 1, 1, '{\"AliLLM\": {\"type\": \"openai\", \"top_k\": 50, \"top_p\": 1, \"api_key\": \"你的ali api key\", \"base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\", \"max_tokens\": 500, \"model_name\": \"qwen-turbo\", \"temperature\": 0.7, \"frequency_penalty\": 0}}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_model_config` VALUES ('e9f2d891afbe4632b13a47c7a8c6e03d', 'LLM', 'ChatGLMLLM', 'ChatGLMLLM', 1, 1, '{\"ChatGLMLLM\": {\"url\": \"https://open.bigmodel.cn/api/paas/v4/\", \"type\": \"openai\", \"api_key\": \"0415dad4014847babc3e3f03024c50a3.qH7FgTy5Yawc85fl\", \"model_name\": \"glm-4-flash\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_model_config` VALUES ('d50b06e9b8104d0d9c0f7316d258abcb', 'TTS', 'EdgeTTS', 'EdgeTTS', 1, 1, '{\"EdgeTTS\": {\"type\": \"edge\", \"voice\": \"zh-CN-XiaoxiaoNeural\", \"output_dir\": \"tmp/\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_model_config` VALUES ('896db62c9dd74976ab0e8c14bf924d9d', 'TTS', 'DoubaoTTS', 'DoubaoTTS', 1, 1, '{\"DoubaoTTS\": {\"type\": \"doubao\", \"appid\": \"你的火山引擎语音合成服务appid\", \"voice\": \"BV034_streaming\", \"api_url\": \"https://openspeech.bytedance.com/api/v1/tts\", \"cluster\": \"volcano_tts\", \"output_dir\": \"tmp/\", \"access_token\": \"你的火山引擎语音合成服务access_token\", \"authorization\": \"Bearer;\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_model_config` VALUES ('c4e12f874a3f4aa99f5b2c18e15d407b', 'Intent', 'function_call', 'function_call', 1, 1, '{\"function_call\": {\"type\": \"nointent\", \"functions\": [\"change_role\", \"get_weather\", \"get_news\", \"play_music\"]}}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
+
+-- 初始化音色数据
+DELETE FROM `ai_tts_voice`;
+INSERT INTO `ai_tts_voice` VALUES ('fcac83266edadd5a3125f06cfee1906b', '896db62c9dd74976ab0e8c14bf924d9d', '湾湾小何', 'zh-CN-XiaoxiaoNeural', '中文', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/湾湾小何.mp3', NULL, 1, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_tts_voice` VALUES ('1f2e3d4c5b6a7f8e9d0c1b2a3f4e5bx2', '896db62c9dd74976ab0e8c14bf924d9d', '通用男声', 'BV002_streaming', '中文', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV002.mp3', NULL, 2, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_tts_voice` VALUES ('9e8f7a6b5c4d3e2f1a0b9c8d7e6f5ad3', '896db62c9dd74976ab0e8c14bf924d9d', '通用女声', 'BV001_streaming', '中文', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV001.mp3', NULL, 3, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_tts_voice` VALUES ('2b3c4d5e6f7a8b9c0d1e2f3a4b5c62a2', '896db62c9dd74976ab0e8c14bf924d9d', '阳光男生', 'BV056_streaming', '中文', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV056.mp3', NULL, 4, NULL, NULL, NULL, NULL);
+INSERT INTO `ai_tts_voice` VALUES ('f7a38c03d5644e22b6d84f8923a74c51', '896db62c9dd74976ab0e8c14bf924d9d', '奶气萌娃', 'BV051_streaming', '中文', 'https://lf3-speech.bytetos.com/obj/speech-tts-external/portal/Portal_Demo_BV051.mp3', NULL, 5, NULL, NULL, NULL, NULL);
+
diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml
index 89ed7ff5..1f2f2118 100755
--- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml
+++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml
@@ -15,4 +15,11 @@ databaseChangeLog:
changes:
- sqlFile:
encoding: utf8
- path: classpath:db/changelog/202503141346.sql
\ No newline at end of file
+ path: classpath:db/changelog/202503141346.sql
+ - changeSet:
+ id: 202503241021
+ author: dreamchen
+ changes:
+ - sqlFile:
+ encoding: utf8
+ path: classpath:db/changelog/202503241021.sql
\ No newline at end of file
diff --git a/main/manager-api/src/main/resources/mapper/agent/AgentTemplateMapper.xml b/main/manager-api/src/main/resources/mapper/agent/AgentTemplateMapper.xml
new file mode 100644
index 00000000..bc372f8a
--- /dev/null
+++ b/main/manager-api/src/main/resources/mapper/agent/AgentTemplateMapper.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/main/manager-web/src/components/ActivateDeviceDialog.vue b/main/manager-web/src/components/ActivateDeviceDialog.vue
new file mode 100644
index 00000000..59384e79
--- /dev/null
+++ b/main/manager-web/src/components/ActivateDeviceDialog.vue
@@ -0,0 +1,98 @@
+
+
+
+
+

+
+ 激活设备
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml
index 7289743b..e2c35e67 100644
--- a/main/xiaozhi-server/config.yaml
+++ b/main/xiaozhi-server/config.yaml
@@ -82,9 +82,10 @@ selected_module:
TTS: EdgeTTS
# 记忆模块,默认不开启记忆;如果想使用超长记忆,推荐使用mem0ai;如果注重隐私,请使用本地的mem_local_short
Memory: nomem
- # 意图识别模块,默认使用function_call。开启后,可以播放音乐、控制音量、识别退出指令
- # 意图识别使用intent_llm,优点:通用性强,缺点:增加串行前置意图识别模块,会增加处理时间
- # 意图识别使用function_call,缺点:需要所选择的LLM支持function_call,优点:按需调用工具、速度快
+ # 意图识别模块开启后,可以播放音乐、控制音量、识别退出指令。
+ # 不想开通意图识别,就设置成:nointent
+ # 意图识别可使用intent_llm,如果你的LLM是DifyLLM或CozeLLM,建议使用这个。优点:通用性强,缺点:增加串行前置意图识别模块,会增加处理时间,这个意图识别暂时不支持控制音量大小等iot操作
+ # 意图识别可使用function_call,缺点:需要所选择的LLM支持function_call,优点:按需调用工具、速度快,理论上能全部操作所有iot指令
# 默认免费的ChatGLMLLM就已经支持function_call,但是如果像追求稳定建议把LLM设置成:DoubaoLLM,使用的具体model_name是:doubao-pro-32k-functioncall-241028
Intent: function_call
@@ -97,9 +98,13 @@ Intent:
intent_llm:
# 不需要动type
type: intent_llm
+ # 配备意图识别独立的思考模型
+ # 如果这里不填,则会默认使用selected_module.LLM的模型作为意图识别的思考模型
+ # 如果你的selected_module.LLM选择了DifyLLM或CozeLLM,这里最好使用独立的LLM作为意图识别,例如使用免费的ChatGLMLLM
+ llm: ChatGLMLLM
function_call:
# 不需要动type
- type: nointent
+ type: function_call
# plugins_func/functions下的模块,可以通过配置,选择加载哪个模块,加载后对话支持相应的function调用
# 系统默认已经记载“handle_exit_intent(退出识别)”、“play_music(音乐播放)”插件,请勿重复加载
# 下面是加载查天气、角色切换、加载查新闻的插件示例
@@ -278,6 +283,18 @@ LLM:
variables:
k: "v"
k2: "v2"
+ XinferenceLLM:
+ # 定义LLM API类型
+ type: xinference
+ # Xinference服务地址和模型名称
+ model_name: qwen2.5:72b-AWQ # 使用的模型名称,需要预先在Xinference启动对应模型
+ base_url: http://localhost:9997 # Xinference服务地址
+ XinferenceSmallLLM:
+ # 定义轻量级LLM API类型,用于意图识别
+ type: xinference
+ # Xinference服务地址和模型名称
+ model_name: qwen2.5:3b-AWQ # 使用的小模型名称,用于意图识别
+ base_url: http://localhost:9997 # Xinference服务地址
TTS:
# 当前支持的type为edge、doubao,可自行适配
EdgeTTS:
@@ -534,4 +551,4 @@ wakeup_words:
- "小龙小龙"
- "喵喵同学"
- "小滨小滨"
- - "小冰小冰"
\ No newline at end of file
+ - "小冰小冰"
diff --git a/main/xiaozhi-server/config/logger.py b/main/xiaozhi-server/config/logger.py
index 2206e52c..e652e3ec 100644
--- a/main/xiaozhi-server/config/logger.py
+++ b/main/xiaozhi-server/config/logger.py
@@ -3,7 +3,7 @@ import sys
from loguru import logger
from config.settings import load_config
-SERVER_VERSION = "0.1.15"
+SERVER_VERSION = "0.1.16"
def setup_logging():
"""从配置文件中读取日志配置,并设置日志输出格式和级别"""
@@ -13,7 +13,7 @@ def setup_logging():
log_format_file = log_config.get("log_format_file", "{time:YYYY-MM-DD HH:mm:ss} - {version_{selected_module}} - {name} - {level} - {extra[tag]} - {message}")
selected_module = config.get("selected_module")
- selected_module_str = ''.join([key[0] + value[0] for key, value in selected_module.items()])
+ selected_module_str = ''.join([value[0] + value[1] for key, value in selected_module.items()])
log_format = log_format.replace("{version}", SERVER_VERSION)
log_format = log_format.replace("{selected_module}", selected_module_str)
diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py
index 98cf5c5c..0f4e2a70 100644
--- a/main/xiaozhi-server/core/connection.py
+++ b/main/xiaozhi-server/core/connection.py
@@ -18,10 +18,11 @@ from concurrent.futures import ThreadPoolExecutor, TimeoutError
from core.handle.sendAudioHandle import sendAudioMessage
from core.handle.receiveAudioHandle import handleAudioMessage
from core.handle.functionHandler import FunctionHandler
-from plugins_func.register import Action
+from plugins_func.register import Action, ActionResponse
from config.private_config import PrivateConfig
from core.auth import AuthMiddleware, AuthenticationError
from core.utils.auth_code_gen import AuthCodeGenerator
+from core.mcp.manager import MCPManager
TAG = __name__
@@ -100,6 +101,8 @@ class ConnectionHandler:
self.use_function_call_mode = False
if self.config["selected_module"]["Intent"] == 'function_call':
self.use_function_call_mode = True
+
+ self.mcp_manager = MCPManager(self)
async def handle_connection(self, ws):
try:
@@ -194,7 +197,31 @@ class ConnectionHandler:
"""加载记忆"""
device_id = self.headers.get("device-id", None)
self.memory.init_memory(device_id, self.llm)
- self.intent.set_llm(self.llm)
+
+ """为意图识别设置LLM,优先使用专用LLM"""
+ # 检查是否配置了专用的意图识别LLM
+ intent_llm_name = self.config["Intent"]["intent_llm"]["llm"]
+
+ # 记录开始初始化意图识别LLM的时间
+ intent_llm_init_start = time.time()
+
+ if not self.use_function_call_mode and intent_llm_name and intent_llm_name in self.config["LLM"]:
+ # 如果配置了专用LLM,则创建独立的LLM实例
+ from core.utils import llm as llm_utils
+ intent_llm_config = self.config["LLM"][intent_llm_name]
+ intent_llm_type = intent_llm_config.get("type", intent_llm_name)
+ intent_llm = llm_utils.create_instance(intent_llm_type, intent_llm_config)
+ self.logger.bind(tag=TAG).info(f"为意图识别创建了专用LLM: {intent_llm_name}, 类型: {intent_llm_type}")
+
+ self.intent.set_llm(intent_llm)
+ else:
+ # 否则使用主LLM
+ self.intent.set_llm(self.llm)
+ self.logger.bind(tag=TAG).info("意图识别使用主LLM")
+
+ # 记录意图识别LLM初始化耗时
+ intent_llm_init_time = time.time() - intent_llm_init_start
+ self.logger.bind(tag=TAG).info(f"意图识别LLM初始化完成,耗时: {intent_llm_init_time:.4f}秒")
"""加载位置信息"""
self.client_ip_info = get_ip_info(self.client_ip)
@@ -203,6 +230,9 @@ class ConnectionHandler:
self.prompt = self.prompt + f"\nuser location:{self.client_ip_info}"
self.dialogue.update_system_message(self.prompt)
+ """加载MCP工具"""
+ asyncio.run_coroutine_threadsafe(self.mcp_manager.initialize_servers(), self.loop)
+
def change_system_prompt(self, prompt):
self.prompt = prompt
# 找到原来的role==system,替换原来的系统提示
@@ -324,7 +354,6 @@ class ConnectionHandler:
functions = None
if hasattr(self, 'func_handler'):
functions = self.func_handler.get_functions()
-
response_message = []
processed_chars = 0 # 跟踪已处理的字符位置
@@ -358,6 +387,9 @@ class ConnectionHandler:
content_arguments = ""
for response in llm_responses:
content, tools_call = response
+ if "content" in response:
+ content = response["content"]
+ tools_call = None
if content is not None and len(content) > 0:
if len(response_message) <= 0 and (content == "```" or "" in content):
tool_call_flag = True
@@ -436,8 +468,14 @@ class ConnectionHandler:
"id": function_id,
"arguments": function_arguments
}
- result = self.func_handler.handle_llm_function_call(self, function_call_data)
- self._handle_function_result(result, function_call_data, text_index + 1)
+
+ # 处理MCP工具调用
+ if self.mcp_manager.is_mcp_tool(function_name):
+ result = self._handle_mcp_tool_call(function_call_data)
+ else:
+ # 处理系统函数
+ result = self.func_handler.handle_llm_function_call(self, function_call_data)
+ self._handle_function_result(result, function_call_data, text_index+1)
# 处理最后剩余的文本
full_text = "".join(response_message)
@@ -459,6 +497,42 @@ class ConnectionHandler:
return True
+ def _handle_mcp_tool_call(self, function_call_data):
+ function_arguments = function_call_data["arguments"]
+ function_name = function_call_data["name"]
+ try:
+ args_dict = function_arguments
+ if isinstance(function_arguments, str):
+ try:
+ args_dict = json.loads(function_arguments)
+ except json.JSONDecodeError:
+ self.logger.bind(tag=TAG).error(f"无法解析 function_arguments: {function_arguments}")
+ return ActionResponse(action=Action.REQLLM, result="参数解析失败", response="")
+
+ tool_result = asyncio.run_coroutine_threadsafe(self.mcp_manager.execute_tool(
+ function_name,
+ args_dict
+ ), self.loop).result()
+ # meta=None content=[TextContent(type='text', text='北京当前天气:\n温度: 21°C\n天气: 晴\n湿度: 6%\n风向: 西北 风\n风力等级: 5级', annotations=None)] isError=False
+ content_text = ""
+ if tool_result is not None and tool_result.content is not None:
+ for content in tool_result.content:
+ content_type = content.type
+ if content_type == "text":
+ content_text = content.text
+ elif content_type == "image":
+ pass
+
+ if len(content_text) > 0:
+ return ActionResponse(action=Action.REQLLM, result=content_text, response="")
+
+ except Exception as e:
+ self.logger.bind(tag=TAG).error(f"MCP工具调用错误: {e}")
+ return ActionResponse(action=Action.REQLLM, result="工具调用出错", response="")
+
+ return ActionResponse(action=Action.REQLLM, result="工具调用出错", response="")
+
+
def _handle_function_result(self, result, function_call_data, text_index):
if result.action == Action.RESPONSE: # 直接回复前端
text = result.response
@@ -582,6 +656,8 @@ class ConnectionHandler:
async def close(self, ws=None):
"""资源清理方法"""
+ # 清理MCP资源
+ await self.mcp_manager.cleanup_all()
# 触发停止事件并清理资源
if self.stop_event:
diff --git a/main/xiaozhi-server/core/handle/intentHandler.py b/main/xiaozhi-server/core/handle/intentHandler.py
index d8440fc6..5105a4d9 100644
--- a/main/xiaozhi-server/core/handle/intentHandler.py
+++ b/main/xiaozhi-server/core/handle/intentHandler.py
@@ -4,6 +4,8 @@ import uuid
from core.handle.sendAudioHandle import send_stt_message
from core.handle.helloHandle import checkWakeupWords
from core.utils.util import remove_punctuation_and_length
+from core.utils.dialogue import Message
+from loguru import logger
TAG = __name__
logger = setup_logging()
@@ -21,11 +23,11 @@ async def handle_user_intent(conn, text):
# 使用支持function calling的聊天方法,不再进行意图分析
return False
# 使用LLM进行意图分析
- intent = await analyze_intent_with_llm(conn, text)
- if not intent:
+ intent_result = await analyze_intent_with_llm(conn, text)
+ if not intent_result:
return False
# 处理各种意图
- return await process_intent_result(conn, intent, text)
+ return await process_intent_result(conn, intent_result, text)
async def check_direct_exit(conn, text):
@@ -40,7 +42,6 @@ async def check_direct_exit(conn, text):
return False
-
async def analyze_intent_with_llm(conn, text):
"""使用LLM分析用户意图"""
if not hasattr(conn, 'intent') or not conn.intent:
@@ -51,52 +52,65 @@ async def analyze_intent_with_llm(conn, text):
dialogue = conn.dialogue
try:
intent_result = await conn.intent.detect_intent(conn, dialogue.dialogue, text)
- # 尝试解析JSON结果
- try:
- intent_data = json.loads(intent_result)
- if "intent" in intent_data:
- return intent_data["intent"]
- except json.JSONDecodeError:
- # 如果不是JSON格式,尝试直接获取意图文本
- return intent_result.strip()
-
+ return intent_result
except Exception as e:
logger.bind(tag=TAG).error(f"意图识别失败: {str(e)}")
return None
-async def process_intent_result(conn, intent, original_text):
+async def process_intent_result(conn, intent_result, original_text):
"""处理意图识别结果"""
- # 处理退出意图
- if "结束聊天" in intent:
- logger.bind(tag=TAG).info(f"识别到退出意图: {intent}")
- # 如果是明确的离别意图,发送告别语并关闭连接
- await send_stt_message(conn, original_text)
- conn.executor.submit(conn.chat_and_close, original_text)
- return True
+ try:
+ # 尝试将结果解析为JSON
+ intent_data = json.loads(intent_result)
- # 处理播放音乐意图
- if "播放音乐" in intent:
- logger.bind(tag=TAG).info(f"识别到音乐播放意图: {intent}")
- # 调用play_music函数来播放音乐
- song_name = extract_text_in_brackets(intent)
- function_id = str(uuid.uuid4().hex)
- function_name = "play_music"
- function_arguments = '{ "song_name": "' + song_name + '" }'
+ # 检查是否有function_call
+ if "function_call" in intent_data:
+ # 直接从意图识别获取了function_call
+ logger.bind(tag=TAG).info(f"检测到function_call格式的意图结果: {intent_data['function_call']['name']}")
+ function_name = intent_data["function_call"]["name"]
+ if function_name == "continue_chat":
+ return False
+ function_args = None
+ if "arguments" in intent_data["function_call"]:
+ function_args = intent_data["function_call"]["arguments"]
+ # 确保参数是字符串格式的JSON
+ if isinstance(function_args, dict):
+ function_args = json.dumps(function_args)
- function_call_data = {
- "name": function_name,
- "id": function_id,
- "arguments": function_arguments
- }
- conn.func_handler.handle_llm_function_call(conn, function_call_data)
- return True
+ function_call_data = {
+ "name": function_name,
+ "id": str(uuid.uuid4().hex),
+ "arguments": function_args
+ }
- # 其他意图处理可以在这里扩展
+ await send_stt_message(conn, original_text)
- # 默认返回False,表示继续常规聊天流程
- return False
+ # 使用executor执行函数调用和结果处理
+ def process_function_call():
+ conn.dialogue.put(Message(role="user", content=original_text))
+ result = conn.func_handler.handle_llm_function_call(conn, function_call_data)
+ if result and function_name != 'play_music':
+ # 获取当前最新的文本索引
+ text = result.response
+ if text is None:
+ text = result.result
+ if text is not None:
+ text_index = conn.tts_last_text_index + 1 if hasattr(conn, 'tts_last_text_index') else 0
+ conn.recode_first_last_text(text, text_index)
+ future = conn.executor.submit(conn.speak_and_play, text, text_index)
+ conn.llm_finish_task = True
+ conn.tts_queue.put(future)
+ conn.dialogue.put(Message(role="assistant", content=text))
+
+ # 将函数执行放在线程池中
+ conn.executor.submit(process_function_call)
+ return True
+ return False
+ except json.JSONDecodeError as e:
+ logger.bind(tag=TAG).error(f"处理意图结果时出错: {e}")
+ return False
def extract_text_in_brackets(s):
@@ -112,4 +126,4 @@ def extract_text_in_brackets(s):
if left_bracket_index != -1 and right_bracket_index != -1 and left_bracket_index < right_bracket_index:
return s[left_bracket_index + 1:right_bracket_index]
else:
- return ""
\ No newline at end of file
+ return ""
diff --git a/main/xiaozhi-server/core/mcp/MCPClient.py b/main/xiaozhi-server/core/mcp/MCPClient.py
new file mode 100644
index 00000000..103ac645
--- /dev/null
+++ b/main/xiaozhi-server/core/mcp/MCPClient.py
@@ -0,0 +1,86 @@
+from datetime import timedelta
+from typing import Optional
+from contextlib import AsyncExitStack
+import os, shutil
+from mcp import ClientSession, StdioServerParameters
+from mcp.client.stdio import stdio_client
+
+from config.logger import setup_logging
+
+TAG = __name__
+
+class MCPClient:
+ def __init__(self, config):
+ # Initialize session and client objects
+ self.session: Optional[ClientSession] = None
+ self.exit_stack = AsyncExitStack()
+ self.logger = setup_logging()
+ self.config = config
+ self.tolls = []
+
+ async def initialize(self):
+ args = self.config.get("args", [])
+
+ command = (
+ shutil.which("npx")
+ if self.config["command"] == "npx"
+ else self.config["command"]
+ )
+
+ env={**os.environ}
+ if self.config.get("env"):
+ env.update(self.config["env"])
+
+ server_params = StdioServerParameters(
+ command=command,
+ args=args,
+ env=env
+ )
+
+ stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
+ self.stdio, self.write = stdio_transport
+ time_out_delta = timedelta(seconds=15)
+ self.session = await self.exit_stack.enter_async_context(ClientSession(read_stream=self.stdio, write_stream=self.write, read_timeout_seconds=time_out_delta))
+
+ await self.session.initialize()
+
+ # List available tools
+ response = await self.session.list_tools()
+ tools = response.tools
+ self.tools = tools
+ self.logger.bind(tag=TAG).info(f"Connected to server with tools:{[tool.name for tool in tools]}")
+
+ def has_tool(self, tool_name):
+ return any(tool.name == tool_name for tool in self.tools)
+
+ def get_available_tools(self):
+ available_tools = [{"type": "function", "function":{
+ "name": tool.name,
+ "description": tool.description,
+ "parameters": tool.inputSchema
+ } } for tool in self.tools]
+
+ return available_tools
+
+ async def call_tool(self, tool_name: str, tool_args: dict):
+ self.logger.bind(tag=TAG).info(f"MCPClient Calling tool {tool_name} with args: {tool_args}")
+ try:
+ response = await self.session.call_tool(tool_name, tool_args)
+ except Exception as e:
+ self.logger.bind(tag=TAG).error(f"Error calling tool {tool_name}: {e}")
+ from types import SimpleNamespace
+ error_content = SimpleNamespace(
+ type='text',
+ text=f"Error calling tool {tool_name}: {e}"
+ )
+ error_response = SimpleNamespace(
+ content=[error_content],
+ isError=True
+ )
+ return error_response
+ self.logger.bind(tag=TAG).info(f"MCPClient Response from tool {tool_name}: {response}")
+ return response
+
+ async def cleanup(self):
+ """Clean up resources"""
+ await self.exit_stack.aclose()
\ No newline at end of file
diff --git a/main/xiaozhi-server/core/mcp/manager.py b/main/xiaozhi-server/core/mcp/manager.py
new file mode 100644
index 00000000..28efd87d
--- /dev/null
+++ b/main/xiaozhi-server/core/mcp/manager.py
@@ -0,0 +1,110 @@
+"""MCP服务管理器"""
+import os, json
+from typing import Dict, Any, List
+from .MCPClient import MCPClient
+from config.logger import setup_logging
+from core.utils.util import get_project_dir
+from plugins_func.register import register_function, ActionResponse, Action, ToolType
+
+TAG = __name__
+
+class MCPManager:
+ """管理多个MCP服务的集中管理器"""
+
+ def __init__(self,conn) -> None:
+ """
+ 初始化MCP管理器
+ """
+ self.conn = conn
+ self.logger = setup_logging()
+ self.config_path = get_project_dir() + 'data/.mcp_server_settings.json'
+ if os.path.exists(self.config_path) == False:
+ self.config_path = ""
+ self.logger.bind(tag=TAG).warning(f"请检查mcp服务配置文件:data/.mcp_server_settings.json")
+ self.client: Dict[str, MCPClient] = {}
+ self.tools = []
+
+ def load_config(self) -> Dict[str, Any]:
+ """加载MCP服务配置
+ Returns:
+ Dict[str, Any]: 服务配置字典
+ """
+ if len(self.config_path) == 0:
+ return {}
+
+ try:
+ with open(self.config_path, 'r', encoding='utf-8') as f:
+ config = json.load(f)
+ return config.get('mcpServers', {})
+ except Exception as e:
+ self.logger.bind(tag=TAG).error(f"Error loading MCP config from {self.config_path}: {e}")
+ return {}
+
+ async def initialize_servers(self) -> None:
+ """初始化所有MCP服务"""
+ config = self.load_config()
+ for name, srv_config in config.items():
+ if not srv_config.get("command"):
+ self.logger.bind(tag=TAG).warning(f"Skipping server {name}: command not specified")
+ continue
+
+ try:
+ client = MCPClient(srv_config)
+ await client.initialize()
+ self.client[name] = client
+ self.logger.bind(tag=TAG).info(f"Initialized MCP client: {name}")
+ client_tools = client.get_available_tools()
+ self.tools.extend(client_tools)
+ for tool in client_tools:
+ func_name = "mcp_"+tool["function"]["name"]
+ register_function(func_name, tool, ToolType.MCP_CLIENT)(self.execute_tool)
+ self.conn.func_handler.function_registry.register_function(func_name)
+
+ except Exception as e:
+ self.logger.bind(tag=TAG).error(f"Failed to initialize MCP server {name}: {e}")
+ self.conn.func_handler.upload_functions_desc()
+
+ def get_all_tools(self) -> List[Dict[str, Any]]:
+ """获取所有服务的工具function定义
+ Returns:
+ List[Dict[str, Any]]: 所有工具的function定义列表
+ """
+ return self.tools
+
+ def is_mcp_tool(self, tool_name: str) -> bool:
+ """检查是否是MCP工具
+ Args:
+ tool_name: 工具名称
+ Returns:
+ bool: 是否是MCP工具
+ """
+ for tool in self.tools:
+ if tool.get("function") != None and tool["function"].get("name") == tool_name:
+ return True
+ return False
+
+ async def execute_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Any:
+ """执行工具调用
+ Args:
+ tool_name: 工具名称
+ arguments: 工具参数
+ Returns:
+ Any: 工具执行结果
+ Raises:
+ ValueError: 工具未找到时抛出
+ """
+ self.logger.bind(tag=TAG).info(f"Executing tool {tool_name} with arguments: {arguments}")
+ for client in self.client.values():
+ if client.has_tool(tool_name):
+ return await client.call_tool(tool_name, arguments)
+
+ raise ValueError(f"Tool {tool_name} not found in any MCP server")
+
+ async def cleanup_all(self) -> None:
+ for name, client in self.client.items():
+ try:
+ await client.cleanup()
+ self.logger.bind(tag=TAG).info(f"Cleaned up MCP client: {name}")
+ except Exception as e:
+ self.logger.bind(tag=TAG).error(f"Error cleaning up MCP client {name}: {e}")
+ self.client.clear()
diff --git a/main/xiaozhi-server/core/providers/intent/base.py b/main/xiaozhi-server/core/providers/intent/base.py
index a691d6cb..2a39f1ae 100644
--- a/main/xiaozhi-server/core/providers/intent/base.py
+++ b/main/xiaozhi-server/core/providers/intent/base.py
@@ -10,14 +10,21 @@ class IntentProviderBase(ABC):
def __init__(self, config):
self.config = config
self.intent_options = config.get("intent_options", {
+ "handle_exit_intent": "结束聊天, 用户发来如再见之类的表示结束的话, 不想再进行对话的时候",
+ "play_music": "播放音乐, 用户希望你可以播放音乐, 只用于播放音乐的意图",
+ "get_weather": "查询天气, 用户希望查询某个地点的天气情况",
+ "get_news": "查询新闻, 用户希望查询最新新闻或特定类型的新闻",
+ "get_lunar": "用于获取今天的阴历/农历和黄历信息",
+ "get_time": "获取今天日期或者当前时间信息",
"continue_chat": "继续聊天",
- "end_chat": "结束聊天",
- "play_music": "播放音乐"
})
def set_llm(self, llm):
self.llm = llm
- logger.bind(tag=TAG).debug("Set LLM for intent provider")
+ # 获取模型名称和类型信息
+ model_name = getattr(llm, 'model_name', str(llm.__class__.__name__))
+ # 记录更详细的日志
+ logger.bind(tag=TAG).info(f"意图识别设置LLM: {model_name}")
@abstractmethod
async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str:
@@ -30,5 +37,6 @@ class IntentProviderBase(ABC):
- "继续聊天"
- "结束聊天"
- "播放音乐 歌名" 或 "随机播放音乐"
+ - "查询天气 地点名" 或 "查询天气 [当前位置]"
"""
pass
diff --git a/main/xiaozhi-server/core/providers/intent/function_call/function_call.py b/main/xiaozhi-server/core/providers/intent/function_call/function_call.py
new file mode 100644
index 00000000..ff33e58f
--- /dev/null
+++ b/main/xiaozhi-server/core/providers/intent/function_call/function_call.py
@@ -0,0 +1,20 @@
+from ..base import IntentProviderBase
+from typing import List, Dict
+from config.logger import setup_logging
+
+TAG = __name__
+logger = setup_logging()
+
+
+class IntentProvider(IntentProviderBase):
+ async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str:
+ """
+ 默认的意图识别实现,始终返回继续聊天
+ Args:
+ dialogue_history: 对话历史记录列表
+ text: 本次对话记录
+ Returns:
+ 固定返回"继续聊天"
+ """
+ logger.bind(tag=TAG).debug("Using functionCallProvider, always returning continue chat")
+ return self.intent_options["continue_chat"]
diff --git a/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py b/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py
index 9d5d2507..1afd835e 100644
--- a/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py
+++ b/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py
@@ -3,6 +3,9 @@ from ..base import IntentProviderBase
from plugins_func.functions.play_music import initialize_music_handler
from config.logger import setup_logging
import re
+import json
+import hashlib
+import time
TAG = __name__
logger = setup_logging()
@@ -13,6 +16,10 @@ class IntentProvider(IntentProviderBase):
super().__init__(config)
self.llm = None
self.promot = self.get_intent_system_prompt()
+ # 添加缓存管理
+ self.intent_cache = {} # 缓存意图识别结果
+ self.cache_expiry = 600 # 缓存有效期10分钟
+ self.cache_max_size = 100 # 最多缓存100个意图
def get_intent_system_prompt(self) -> str:
"""
@@ -22,62 +29,119 @@ class IntentProvider(IntentProviderBase):
"""
intent_list = []
- """
- "continue_chat": "1.继续聊天, 除了播放音乐和结束聊天的时候的选项, 比如日常的聊天和问候, 对话等",
- "end_chat": "2.结束聊天, 用户发来如再见之类的表示结束的话, 不想再进行对话的时候",
- "play_music": "3.播放音乐, 用户希望你可以播放音乐, 只用于播放音乐的意图"
- """
- for key, value in self.intent_options.items():
- if key == "play_music":
- intent_list.append("3.播放音乐, 用户希望你可以播放音乐, 只用于播放音乐的意图")
- elif key == "end_chat":
- intent_list.append("2.结束聊天, 用户发来如再见之类的表示结束的话, 不想再进行对话的时候")
- elif key == "continue_chat":
- intent_list.append("1.继续聊天, 除了播放音乐和结束聊天的时候的选项, 比如日常的聊天和问候, 对话等")
- else:
- intent_list.append(value)
-
- # "如果是唱歌、听歌、播放音乐,请指定歌名,格式为'播放音乐 [识别出的歌名]'。\n"
- # "如果听不出具体歌名,可以返回'随机播放音乐'。\n"
- # "只需要返回意图结果的json,不要解释。"
- # "返回格式如下:\n"
prompt = (
- "你是一个意图识别助手。你需要根据和用户的对话记录,重点分析用户的最后一句话,判断用户意图属于以下哪一类(使用和标志):\n"
+ "你是一个意图识别助手。请分析用户的最后一句话,判断用户意图属于以下哪一类:\n"
""
f"{', '.join(intent_list)}"
"\n"
- "你需要按照以下的步骤处理用户的对话"
- "1. 思考出对话的意图是哪一类的"
- "2. 属于1和2的意图, 直接返回,返回格式如下:\n"
- "{intent: '用户意图'}\n"
- "3. 属于3的意图,则继续分析用户希望播放的音乐\n"
- "4. 如果无法识别出具体歌名,可以返回'随机播放音乐'\n"
- "{intent: '播放音乐 [获取的音乐名字]'}\n"
- "下面是几个处理的示例(思考的内容不返回, 只返回json部分, 无额外的内容)\n"
- "```"
+ "处理步骤:"
+ "1. 思考意图类型,生成function_call格式"
+ "\n\n"
+ "返回格式示例:\n"
+ "1. 播放音乐意图: {\"function_call\": {\"name\": \"play_music\", \"arguments\": {\"song_name\": \"音乐名称\"}}}\n"
+ "2. 查询天气意图: {\"function_call\": {\"name\": \"get_weather\", \"arguments\": {\"location\": \"地点名称\", \"lang\": \"zh_CN\"}}}\n"
+ "3. 查询新闻意图: {\"function_call\": {\"name\": \"get_news\", \"arguments\": {\"category\": \"新闻类别\", \"detail\": false, \"lang\": \"zh_CN\"}}}\n"
+ "4. 结束对话意图: {\"function_call\": {\"name\": \"handle_exit_intent\", \"arguments\": {\"say_goodbye\": \"goodbye\"}}}\n"
+ "5. 获取当天日期时间: {\"function_call\": {\"name\": \"get_time\"}}\n"
+ "6. 获取当前黄历意图: {\"function_call\": {\"name\": \"get_lunar\"}}\n"
+ "7. 继续聊天意图: {\"function_call\": {\"name\": \"continue_chat\"}}\n"
+ "\n"
+ "注意:\n"
+ "- 播放音乐:无歌名时,song_name设为\"random\"\n"
+ "- 查询天气:无地点时,location设为null\n"
+ "- 查询新闻:无类别时,category设为null;查询详情时,detail设为true\n"
+ "- 如果没有明显的意图,应按照继续聊天意图处理\n"
+ "- 只返回纯JSON,不要任何其他内容\n"
+ "\n"
+ "示例分析:\n"
+ "```\n"
+ "用户: 你好小智\n"
+ "返回: {\"function_call\": {\"name\": \"continue_chat\"}}\n"
+ "```\n"
+ "```\n"
"用户: 你今天怎么样?\n"
- "思考(不返回): 用户发来的数据是一个问候语,属于继续聊天的意图, 是种类1, 种类1的需求是直接返回\n"
- "返回结果: {intent: '继续聊天'}\n"
- "```"
- "用户: 我今天有点累了, 我们明天再聊吧\n"
- "思考(不返回): 用户表达了今天不想继续对话,属于结束聊天的意图, 是种类2, 种类2的需求是直接返回\n"
- "返回结果: {intent: '结束聊天'}\n"
- "```"
- "用户: 我今天有点累了, 我们明天再聊吧\n"
- "思考(不返回): 用户表达了今天不想继续对话,属于结束聊天的意图, 是种类2, 种类2的需求是直接返回\n"
- "返回结果: {intent: '结束聊天'}\n"
- "```"
- "用户: 你可以播放一首中秋月给我听吗\n"
- "思考(不返回): 用户表达了想听音乐的续签,属于播放音乐的意图, 是种类3, 种类3的需求需要继续判断播放的音乐, 这里用户希望的歌曲名明确给出是中秋月\n"
- "返回结果: {intent: '播放音乐 [中秋月]'}\n"
- "```"
- "你现在可以使用的音乐的名称如下(使用和标志):\n"
+ "返回: {\"function_call\": {\"name\": \"continue_chat\"}}\n"
+ "```\n"
+ "```\n"
+ "用户: 现在是几号了?现在几点了?\n"
+ "返回: {\"function_call\": {\"name\": \"get_time\"}}\n"
+ "```\n"
+ "```\n"
+ "用户: 今天农历是多少?\n"
+ "返回: {\"function_call\": {\"name\": \"get_lunar\"}}\n"
+ "```\n"
+ "```\n"
+ "用户: 我们明天再聊吧\n"
+ "返回: {\"function_call\": {\"name\": \"handle_exit_intent\"}}\n"
+ "```\n"
+ "```\n"
+ "用户: 播放中秋月\n"
+ "返回: {\"function_call\": {\"name\": \"play_music\", \"arguments\": {\"song_name\": \"中秋月\"}}}\n"
+ "```\n"
+ "```\n"
+ "用户: 北京天气怎么样\n"
+ "返回: {\"function_call\": {\"name\": \"get_weather\", \"arguments\": {\"location\": \"北京\", \"lang\": \"zh_CN\"}}}\n"
+ "```\n"
+ "```\n"
+ "用户: 今天天气怎么样\n"
+ "返回: {\"function_call\": {\"name\": \"get_weather\", \"arguments\": {\"location\": null, \"lang\": \"zh_CN\"}}}\n"
+ "```\n"
+ "```\n"
+ "用户: 播报财经新闻\n"
+ "返回: {\"function_call\": {\"name\": \"get_news\", \"arguments\": {\"category\": \"财经\", \"detail\": false, \"lang\": \"zh_CN\"}}}\n"
+ "```\n"
+ "```\n"
+ "用户: 有什么最新新闻\n"
+ "返回: {\"function_call\": {\"name\": \"get_news\", \"arguments\": {\"category\": null, \"detail\": false, \"lang\": \"zh_CN\"}}}\n"
+ "```\n"
+ "```\n"
+ "用户: 详细介绍一下这条新闻\n"
+ "返回: {\"function_call\": {\"name\": \"get_news\", \"arguments\": {\"detail\": true, \"lang\": \"zh_CN\"}}}\n"
+ "```\n"
+ "可用的音乐名称:\n"
)
return prompt
+
+ def clean_cache(self):
+ """清理过期缓存"""
+ now = time.time()
+ # 找出过期键
+ expired_keys = [k for k, v in self.intent_cache.items() if now - v['timestamp'] > self.cache_expiry]
+ for key in expired_keys:
+ del self.intent_cache[key]
+
+ # 如果缓存太大,移除最旧的条目
+ if len(self.intent_cache) > self.cache_max_size:
+ # 按时间戳排序并保留最新的条目
+ sorted_items = sorted(self.intent_cache.items(), key=lambda x: x[1]['timestamp'])
+ for key, _ in sorted_items[:len(sorted_items) - self.cache_max_size]:
+ del self.intent_cache[key]
async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str:
if not self.llm:
raise ValueError("LLM provider not set")
+
+ # 记录整体开始时间
+ total_start_time = time.time()
+
+ # 打印使用的模型信息
+ model_info = getattr(self.llm, 'model_name', str(self.llm.__class__.__name__))
+ logger.bind(tag=TAG).info(f"使用意图识别模型: {model_info}")
+
+ # 计算缓存键
+ cache_key = hashlib.md5(text.encode()).hexdigest()
+
+ # 检查缓存
+ if cache_key in self.intent_cache:
+ cache_entry = self.intent_cache[cache_key]
+ # 检查缓存是否过期
+ if time.time() - cache_entry['timestamp'] <= self.cache_expiry:
+ cache_time = time.time() - total_start_time
+ logger.bind(tag=TAG).info(f"使用缓存的意图: {cache_key} -> {cache_entry['intent']}, 耗时: {cache_time:.4f}秒")
+ return cache_entry['intent']
+
+ # 清理缓存
+ self.clean_cache()
# 构建用户最后一句话的提示
msgStr = ""
@@ -94,18 +158,78 @@ class IntentProvider(IntentProviderBase):
music_file_names = music_config["music_file_names"]
prompt_music = f"{self.promot}\n{music_file_names}\n"
logger.bind(tag=TAG).debug(f"User prompt: {prompt_music}")
+
+ # 记录预处理完成时间
+ preprocess_time = time.time() - total_start_time
+ logger.bind(tag=TAG).debug(f"意图识别预处理耗时: {preprocess_time:.4f}秒")
+
# 使用LLM进行意图识别
+ llm_start_time = time.time()
+ logger.bind(tag=TAG).info(f"开始LLM意图识别调用, 模型: {model_info}")
+
intent = self.llm.response_no_stream(
system_prompt=prompt_music,
user_prompt=user_prompt
)
- # 使用正则表达式提取大括号中的内容
- # 使用正则表达式提取 {} 中的内容
- match = re.search(r'\{.*?\}', intent)
+
+ # 记录LLM调用完成时间
+ llm_time = time.time() - llm_start_time
+ logger.bind(tag=TAG).info(f"LLM意图识别完成, 模型: {model_info}, 调用耗时: {llm_time:.4f}秒")
+
+ # 记录后处理开始时间
+ postprocess_start_time = time.time()
+
+ # 清理和解析响应
+ intent = intent.strip()
+ # 尝试提取JSON部分
+ match = re.search(r'\{.*\}', intent, re.DOTALL)
if match:
- result = match.group(0)
- intent = result
- else:
- intent = "{intent: '继续聊天'}"
- logger.bind(tag=TAG).info(f"Detected intent: {intent}")
- return intent.strip()
+ intent = match.group(0)
+
+ # 记录总处理时间
+ total_time = time.time() - total_start_time
+ logger.bind(tag=TAG).info(f"【意图识别性能】模型: {model_info}, 总耗时: {total_time:.4f}秒, LLM调用: {llm_time:.4f}秒, 查询: '{text[:20]}...'")
+
+ # 尝试解析为JSON
+ try:
+ intent_data = json.loads(intent)
+ # 如果包含function_call,则格式化为适合处理的格式
+ if "function_call" in intent_data:
+ function_data = intent_data["function_call"]
+ function_name = function_data.get("name")
+ function_args = function_data.get("arguments", {})
+
+ # 记录识别到的function call
+ logger.bind(tag=TAG).info(f"识别到function call: {function_name}, 参数: {function_args}")
+
+ # 添加到缓存
+ self.intent_cache[cache_key] = {
+ 'intent': intent,
+ 'timestamp': time.time()
+ }
+
+ # 后处理时间
+ postprocess_time = time.time() - postprocess_start_time
+ logger.bind(tag=TAG).debug(f"意图后处理耗时: {postprocess_time:.4f}秒")
+
+ # 确保返回完全序列化的JSON字符串
+ return intent
+ else:
+ # 添加到缓存
+ self.intent_cache[cache_key] = {
+ 'intent': intent,
+ 'timestamp': time.time()
+ }
+
+ # 后处理时间
+ postprocess_time = time.time() - postprocess_start_time
+ logger.bind(tag=TAG).debug(f"意图后处理耗时: {postprocess_time:.4f}秒")
+
+ # 返回普通意图
+ return intent
+ except json.JSONDecodeError:
+ # 后处理时间
+ postprocess_time = time.time() - postprocess_start_time
+ logger.bind(tag=TAG).error(f"无法解析意图JSON: {intent}, 后处理耗时: {postprocess_time:.4f}秒")
+ # 如果解析失败,默认返回继续聊天意图
+ return "{\"intent\": \"继续聊天\"}"
diff --git a/main/xiaozhi-server/core/providers/llm/xinference/xinference.py b/main/xiaozhi-server/core/providers/llm/xinference/xinference.py
new file mode 100644
index 00000000..b90b0418
--- /dev/null
+++ b/main/xiaozhi-server/core/providers/llm/xinference/xinference.py
@@ -0,0 +1,85 @@
+from config.logger import setup_logging
+from openai import OpenAI
+import json
+from core.providers.llm.base import LLMProviderBase
+
+TAG = __name__
+logger = setup_logging()
+
+
+class LLMProvider(LLMProviderBase):
+ def __init__(self, config):
+ self.model_name = config.get("model_name")
+ self.base_url = config.get("base_url", "http://localhost:9997")
+ # Initialize OpenAI client with Xinference base URL
+ # 如果没有v1,增加v1
+ if not self.base_url.endswith("/v1"):
+ self.base_url = f"{self.base_url}/v1"
+
+ logger.bind(tag=TAG).info(f"Initializing Xinference LLM provider with model: {self.model_name}, base_url: {self.base_url}")
+
+ try:
+ self.client = OpenAI(
+ base_url=self.base_url,
+ api_key="xinference" # Xinference has a similar setup to Ollama where it doesn't need an actual key
+ )
+ logger.bind(tag=TAG).info("Xinference client initialized successfully")
+ except Exception as e:
+ logger.bind(tag=TAG).error(f"Error initializing Xinference client: {e}")
+ raise
+
+ def response(self, session_id, dialogue):
+ try:
+ logger.bind(tag=TAG).debug(f"Sending request to Xinference with model: {self.model_name}, dialogue length: {len(dialogue)}")
+ responses = self.client.chat.completions.create(
+ model=self.model_name,
+ messages=dialogue,
+ stream=True
+ )
+ is_active=True
+ for chunk in responses:
+ try:
+ delta = chunk.choices[0].delta if getattr(chunk, 'choices', None) else None
+ content = delta.content if hasattr(delta, 'content') else ''
+ if content:
+ if '' in content:
+ is_active = False
+ content = content.split('')[0]
+ if '' in content:
+ is_active = True
+ content = content.split('')[-1]
+ if is_active:
+ yield content
+ except Exception as e:
+ logger.bind(tag=TAG).error(f"Error processing chunk: {e}")
+
+ except Exception as e:
+ logger.bind(tag=TAG).error(f"Error in Xinference response generation: {e}")
+ yield "【Xinference服务响应异常】"
+
+ def response_with_functions(self, session_id, dialogue, functions=None):
+ try:
+ logger.bind(tag=TAG).debug(f"Sending function call request to Xinference with model: {self.model_name}, dialogue length: {len(dialogue)}")
+ if functions:
+ logger.bind(tag=TAG).debug(f"Function calls enabled with: {[f.get('function', {}).get('name') for f in functions]}")
+
+ stream = self.client.chat.completions.create(
+ model=self.model_name,
+ messages=dialogue,
+ stream=True,
+ tools=functions,
+ )
+
+ for chunk in stream:
+ delta = chunk.choices[0].delta
+ content = delta.content
+ tool_calls = delta.tool_calls
+
+ if content:
+ yield content, tool_calls
+ elif tool_calls:
+ yield None, tool_calls
+
+ except Exception as e:
+ logger.bind(tag=TAG).error(f"Error in Xinference function call: {e}")
+ yield {"type": "content", "content": f"【Xinference服务响应异常: {str(e)}】"}
diff --git a/main/xiaozhi-server/core/utils/util.py b/main/xiaozhi-server/core/utils/util.py
index 51841baf..b361bff3 100644
--- a/main/xiaozhi-server/core/utils/util.py
+++ b/main/xiaozhi-server/core/utils/util.py
@@ -67,13 +67,11 @@ def is_private_ip(ip_addr):
def get_ip_info(ip_addr):
try:
- base_url = "https://freeipapi.com/api/json"
- url = base_url if is_private_ip(ip_addr) else f"{base_url}/{ip_addr}"
-
+ url = "https://whois.pconline.com.cn/ipJson.jsp?json=true"
resp = requests.get(url).json()
ip_info = {
- "city": resp.get("cityName")
+ "city": resp.get("city")
}
return ip_info
except Exception as e:
diff --git a/main/xiaozhi-server/mcp_server_settings.json b/main/xiaozhi-server/mcp_server_settings.json
new file mode 100644
index 00000000..a38bbdfb
--- /dev/null
+++ b/main/xiaozhi-server/mcp_server_settings.json
@@ -0,0 +1,31 @@
+{
+ "des": [
+ "在data目录下创建.mcp_server_settings.json文件,可以选择下面的MCP服务,也可以自行添加新的MCP服务。",
+ "后面不断测试补充好用的mcp服务,欢迎大家一起补充。",
+ "记得删除注释行,des属性仅为说明,不会被解析。",
+ "des和link属性,仅为说明安装方式,方便大家查看原始链接,不是必须项。"
+ ],
+ "mcpServers": {
+ "filesystem": {
+ "command": "npx",
+ "args": [
+ "-y",
+ "@modelcontextprotocol/server-filesystem",
+ "/Users/username/Desktop",
+ "/path/to/other/allowed/dir"
+ ],
+ "link":"https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem"
+ },
+ "playwright": {
+ "command": "npx",
+ "args": ["-y", "@executeautomation/playwright-mcp-server"],
+ "des" : "run 'npx playwright install' first",
+ "link": "https://github.com/executeautomation/mcp-playwright"
+ },
+ "windows-cli": {
+ "command": "npx",
+ "args": ["-y", "@simonb97/server-win-cli"],
+ "link": "https://github.com/SimonB97/win-cli-mcp-server"
+ }
+ }
+}
diff --git a/main/xiaozhi-server/plugins_func/functions/get_news.py b/main/xiaozhi-server/plugins_func/functions/get_news.py
index 43f8ea48..85b203dc 100644
--- a/main/xiaozhi-server/plugins_func/functions/get_news.py
+++ b/main/xiaozhi-server/plugins_func/functions/get_news.py
@@ -45,10 +45,10 @@ def fetch_news_from_rss(rss_url):
try:
response = requests.get(rss_url)
response.raise_for_status()
-
+
# 解析XML
root = ET.fromstring(response.content)
-
+
# 查找所有item元素(新闻条目)
news_items = []
for item in root.findall('.//item'):
@@ -56,14 +56,14 @@ def fetch_news_from_rss(rss_url):
link = item.find('link').text if item.find('link') is not None else "#"
description = item.find('description').text if item.find('description') is not None else "无描述"
pubDate = item.find('pubDate').text if item.find('pubDate') is not None else "未知时间"
-
+
news_items.append({
'title': title,
'link': link,
'description': description,
'pubDate': pubDate
})
-
+
return news_items
except Exception as e:
logger.bind(tag=TAG).error(f"获取RSS新闻失败: {e}")
@@ -75,9 +75,9 @@ def fetch_news_detail(url):
try:
response = requests.get(url)
response.raise_for_status()
-
+
soup = BeautifulSoup(response.content, 'html.parser')
-
+
# 尝试提取正文内容 (这里的选择器需要根据实际网站结构调整)
content_div = soup.select_one('.content_desc, .content, article, .article-content')
if content_div:
@@ -98,7 +98,7 @@ def map_category(category_text):
"""将用户输入的中文类别映射到配置文件中的类别键"""
if not category_text:
return None
-
+
# 类别映射字典,目前支持社会、国际、财经新闻,如需更多类型,参见配置文件
category_map = {
# 社会新闻
@@ -113,10 +113,10 @@ def map_category(category_text):
"金融": "finance",
"经济": "finance"
}
-
+
# 转换为小写并去除空格
normalized_category = category_text.lower().strip()
-
+
# 返回映射结果,如果没有匹配项则返回原始输入
return category_map.get(normalized_category, category_text)
@@ -129,21 +129,22 @@ def get_news(conn, category: str = None, detail: bool = False, lang: str = "zh_C
if detail:
if not hasattr(conn, 'last_news_link') or not conn.last_news_link or 'link' not in conn.last_news_link:
return ActionResponse(Action.REQLLM, "抱歉,没有找到最近查询的新闻,请先获取一条新闻。", None)
-
+
link = conn.last_news_link.get('link')
title = conn.last_news_link.get('title', '未知标题')
-
+
if link == '#':
return ActionResponse(Action.REQLLM, "抱歉,该新闻没有可用的链接获取详细内容。", None)
-
+
logger.bind(tag=TAG).debug(f"获取新闻详情: {title}, URL={link}")
-
+
# 获取新闻详情
detail_content = fetch_news_detail(link)
-
+
if not detail_content or detail_content == "无法获取详细内容":
- return ActionResponse(Action.REQLLM, f"抱歉,无法获取《{title}》的详细内容,可能是链接已失效或网站结构发生变化。", None)
-
+ return ActionResponse(Action.REQLLM,
+ f"抱歉,无法获取《{title}》的详细内容,可能是链接已失效或网站结构发生变化。", None)
+
# 构建详情报告
detail_report = (
f"根据下列数据,用{lang}回应用户的新闻详情查询请求:\n\n"
@@ -152,33 +153,33 @@ def get_news(conn, category: str = None, detail: bool = False, lang: str = "zh_C
f"(请对上述新闻内容进行总结,提取关键信息,以自然、流畅的方式向用户播报,"
f"不要提及这是总结,就像是在讲述一个完整的新闻故事)"
)
-
+
return ActionResponse(Action.REQLLM, detail_report, None)
-
+
# 否则,获取新闻列表并随机选择一条
# 从配置中获取RSS URL
rss_config = conn.config["plugins"]["get_news"]
default_rss_url = rss_config.get("default_rss_url", "https://www.chinanews.com.cn/rss/society.xml")
-
+
# 将用户输入的类别映射到配置中的类别键
mapped_category = map_category(category)
-
+
# 如果提供了类别,尝试从配置中获取对应的URL
rss_url = default_rss_url
if mapped_category and mapped_category in rss_config.get("category_urls", {}):
rss_url = rss_config["category_urls"][mapped_category]
-
+
logger.bind(tag=TAG).info(f"获取新闻: 原始类别={category}, 映射类别={mapped_category}, URL={rss_url}")
-
+
# 获取新闻列表
news_items = fetch_news_from_rss(rss_url)
-
+
if not news_items:
return ActionResponse(Action.REQLLM, "抱歉,未能获取到新闻信息,请稍后再试。", None)
-
+
# 随机选择一条新闻
selected_news = random.choice(news_items)
-
+
# 保存当前新闻链接到连接对象,以便后续查询详情
if not hasattr(conn, 'last_news_link'):
conn.last_news_link = {}
@@ -186,7 +187,7 @@ def get_news(conn, category: str = None, detail: bool = False, lang: str = "zh_C
'link': selected_news.get('link', '#'),
'title': selected_news.get('title', '未知标题')
}
-
+
# 构建新闻报告
news_report = (
f"根据下列数据,用{lang}回应用户的新闻查询请求:\n\n"
@@ -197,9 +198,9 @@ def get_news(conn, category: str = None, detail: bool = False, lang: str = "zh_C
f"直接读出新闻即可,不需要额外多余的内容。"
f"如果用户询问更多详情,告知用户可以说'请详细介绍这条新闻'获取更多内容)"
)
-
+
return ActionResponse(Action.REQLLM, news_report, None)
-
+
except Exception as e:
logger.bind(tag=TAG).error(f"获取新闻出错: {e}")
return ActionResponse(Action.REQLLM, "抱歉,获取新闻时发生错误,请稍后再试。", None)
\ No newline at end of file
diff --git a/main/xiaozhi-server/plugins_func/functions/get_weather.py b/main/xiaozhi-server/plugins_func/functions/get_weather.py
index 8cdd6ea1..060274d9 100644
--- a/main/xiaozhi-server/plugins_func/functions/get_weather.py
+++ b/main/xiaozhi-server/plugins_func/functions/get_weather.py
@@ -52,7 +52,7 @@ WEATHER_CODE_MAP = {
"405": "雨雪天气", "406": "阵雨夹雪", "407": "阵雪", "408": "小到中雪", "409": "中到大雪", "410": "大到暴雪",
"456": "阵雨夹雪", "457": "阵雪", "499": "雪",
"500": "薄雾", "501": "雾", "502": "霾", "503": "扬沙", "504": "浮尘",
- "507": "沙尘暴", "508": "强沙尘暴",
+ "507": "沙尘暴", "508": "强沙尘暴",
"509": "浓雾", "510": "强浓雾", "511": "中度霾", "512": "重度霾", "513": "严重霾", "514": "大雾", "515": "特强浓雾",
"900": "热", "901": "冷", "999": "未知"
}
@@ -120,4 +120,4 @@ def get_weather(conn, location: str = None, lang: str = "zh_CN"):
"参数为0的值不需要报告给用户,每次都报告体感温度,根据语境选择合适的参数内容告知用户,并对参数给出相应评价)"
)
- return ActionResponse(Action.REQLLM, weather_report, None)
+ return ActionResponse(Action.REQLLM, weather_report, None)
\ No newline at end of file
diff --git a/main/xiaozhi-server/plugins_func/functions/handle_device.py b/main/xiaozhi-server/plugins_func/functions/handle_device.py
index d564562b..b215a486 100644
--- a/main/xiaozhi-server/plugins_func/functions/handle_device.py
+++ b/main/xiaozhi-server/plugins_func/functions/handle_device.py
@@ -69,7 +69,7 @@ handle_device_function_desc = {
"description": "动作名称,可选值:get(获取),set(设置),raise(提高),lower(降低)"
},
"value": {
- "type": "int",
+ "type": "integer",
"description": "值大小,可选值:0-100之间的整数"
}
},
diff --git a/main/xiaozhi-server/plugins_func/functions/hass_set_state.py b/main/xiaozhi-server/plugins_func/functions/hass_set_state.py
index 5e2b91a2..def2d412 100644
--- a/main/xiaozhi-server/plugins_func/functions/hass_set_state.py
+++ b/main/xiaozhi-server/plugins_func/functions/hass_set_state.py
@@ -23,7 +23,7 @@ hass_set_state_function_desc = {
"description": "需要操作的动作,打开设备:turn_on,关闭设备:turn_off,增加亮度:brightness_up,降低亮度:brightness_down,设置亮度:brightness_value,增加>音量:,volume_up降低音量:volume_down,设置音量:volume_set,设备暂停:pause,设备继续:continue,静音/取消静音:volume_mute"
},
"input": {
- "type": "int",
+ "type": "integer",
"description": "只有在设置音量,设置亮度时候才需要,有效值为1-100,对应音量和亮度的1%-100%"
},
"is_muted": {
diff --git a/main/xiaozhi-server/plugins_func/register.py b/main/xiaozhi-server/plugins_func/register.py
index ccb03b44..fd990760 100644
--- a/main/xiaozhi-server/plugins_func/register.py
+++ b/main/xiaozhi-server/plugins_func/register.py
@@ -12,6 +12,7 @@ class ToolType(Enum):
CHANGE_SYS_PROMPT = (3, "修改系统提示词,切换角色性格或职责")
SYSTEM_CTL = (4, "系统控制,影响正常的对话流程,如退出、播放音乐等,需要传递conn参数")
IOT_CTL = (5, "IOT设备控制,需要传递conn参数")
+ MCP_CLIENT = (6, "MCP客户端")
def __init__(self, code, message):
self.code = code
diff --git a/main/xiaozhi-server/requirements.txt b/main/xiaozhi-server/requirements.txt
index 51433cb9..164d594d 100755
--- a/main/xiaozhi-server/requirements.txt
+++ b/main/xiaozhi-server/requirements.txt
@@ -22,4 +22,6 @@ mem0ai==0.1.62
bs4==0.0.2
modelscope==1.23.2
sherpa_onnx==1.11.0
-cnlunar==0.2.0
\ No newline at end of file
+mcp==1.4.1
+
+cnlunar==0.2.0