mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-23 23:53:55 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07fe330e4f | ||
|
|
f98c3efef7 | ||
|
|
8b74dec910 | ||
|
|
dc4b8a5002 | ||
|
+2 |
dd5fecdf5a | ||
|
|
6c52754c8f | ||
|
|
e1ebcfa865 | ||
|
|
6464c81e72 | ||
|
|
0b7abfae3a | ||
|
|
76c16ad16e | ||
|
|
1f722477ce |
Executable → Regular
@@ -23,9 +23,7 @@
|
|||||||
<mybatisplus.version>3.5.5</mybatisplus.version>
|
<mybatisplus.version>3.5.5</mybatisplus.version>
|
||||||
<hutool.version>5.8.24</hutool.version>
|
<hutool.version>5.8.24</hutool.version>
|
||||||
<jsoup.version>1.19.1</jsoup.version>
|
<jsoup.version>1.19.1</jsoup.version>
|
||||||
<jasypt.version>3.0.5</jasypt.version>
|
|
||||||
<knife4j.version>4.6.0</knife4j.version>
|
<knife4j.version>4.6.0</knife4j.version>
|
||||||
<quartz.version>2.3.2</quartz.version>
|
|
||||||
<shiro.version>2.0.2</shiro.version>
|
<shiro.version>2.0.2</shiro.version>
|
||||||
<captcha.version>1.6.2</captcha.version>
|
<captcha.version>1.6.2</captcha.version>
|
||||||
<guava.version>33.0.0-jre</guava.version>
|
<guava.version>33.0.0-jre</guava.version>
|
||||||
@@ -34,21 +32,6 @@
|
|||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>org.quartz-scheduler</groupId>
|
|
||||||
<artifactId>quartz</artifactId>
|
|
||||||
<version>${quartz.version}</version>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>com.mchange</groupId>
|
|
||||||
<artifactId>c3p0</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>com.zaxxer</groupId>
|
|
||||||
<artifactId>HikariCP-java6</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.shiro</groupId>
|
<groupId>org.apache.shiro</groupId>
|
||||||
<artifactId>shiro-core</artifactId>
|
<artifactId>shiro-core</artifactId>
|
||||||
@@ -83,6 +66,7 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 验证码工具包 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.whvcse</groupId>
|
<groupId>com.github.whvcse</groupId>
|
||||||
<artifactId>easy-captcha</artifactId>
|
<artifactId>easy-captcha</artifactId>
|
||||||
|
|||||||
@@ -1,40 +1,40 @@
|
|||||||
package xiaozhi.common.aspect;
|
package xiaozhi.common.aspect;
|
||||||
|
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redis切面处理类
|
* Redis切面处理类
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
* Website: https://www.renren.io
|
* Website: https://www.renren.io
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Aspect
|
@Aspect
|
||||||
@Component
|
@Component
|
||||||
public class RedisAspect {
|
public class RedisAspect {
|
||||||
/**
|
/**
|
||||||
* 是否开启redis缓存 true开启 false关闭
|
* 是否开启redis缓存 true开启 false关闭
|
||||||
*/
|
*/
|
||||||
@Value("${renren.redis.open}")
|
@Value("${renren.redis.open}")
|
||||||
private boolean open;
|
private boolean open;
|
||||||
|
|
||||||
@Around("execution(* xiaozhi.common.redis.RedisUtils.*(..))")
|
@Around("execution(* xiaozhi.common.redis.RedisUtils.*(..))")
|
||||||
public Object around(ProceedingJoinPoint point) throws Throwable {
|
public Object around(ProceedingJoinPoint point) throws Throwable {
|
||||||
Object result = null;
|
Object result = null;
|
||||||
if (open) {
|
if (open) {
|
||||||
try {
|
try {
|
||||||
result = point.proceed();
|
result = point.proceed();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("redis error", e);
|
log.error("redis error", e);
|
||||||
throw new RenException(ErrorCode.REDIS_ERROR);
|
throw new RenException(ErrorCode.REDIS_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,4 +35,11 @@ public interface ErrorCode {
|
|||||||
int REDIS_ERROR = 10027;
|
int REDIS_ERROR = 10027;
|
||||||
int JOB_ERROR = 10028;
|
int JOB_ERROR = 10028;
|
||||||
int INVALID_SYMBOL = 10029;
|
int INVALID_SYMBOL = 10029;
|
||||||
|
|
||||||
|
|
||||||
|
int PASSWORD_LENGTH_ERROR = 10030;
|
||||||
|
int PASSWORD_WEAK_ERROR = 10031;
|
||||||
|
int DEL_MYSELF_ERROR = 10032;
|
||||||
|
// 验证码错误
|
||||||
|
int VERIFICATION_CODE = 10033;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,108 +1,108 @@
|
|||||||
package xiaozhi.common.service;
|
package xiaozhi.common.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础服务接口,所有Service接口都要继承
|
* 基础服务接口,所有Service接口都要继承
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
* Website: https://www.renren.io
|
* Website: https://www.renren.io
|
||||||
*/
|
*/
|
||||||
public interface BaseService<T> {
|
public interface BaseService<T> {
|
||||||
Class<T> currentModelClass();
|
Class<T> currentModelClass();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* 插入一条记录(选择字段,策略插入)
|
* 插入一条记录(选择字段,策略插入)
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param entity 实体对象
|
* @param entity 实体对象
|
||||||
*/
|
*/
|
||||||
boolean insert(T entity);
|
boolean insert(T entity);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* 插入(批量),该方法不支持 Oracle、SQL Server
|
* 插入(批量),该方法不支持 Oracle、SQL Server
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param entityList 实体对象集合
|
* @param entityList 实体对象集合
|
||||||
*/
|
*/
|
||||||
boolean insertBatch(Collection<T> entityList);
|
boolean insertBatch(Collection<T> entityList);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* 插入(批量),该方法不支持 Oracle、SQL Server
|
* 插入(批量),该方法不支持 Oracle、SQL Server
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param entityList 实体对象集合
|
* @param entityList 实体对象集合
|
||||||
* @param batchSize 插入批次数量
|
* @param batchSize 插入批次数量
|
||||||
*/
|
*/
|
||||||
boolean insertBatch(Collection<T> entityList, int batchSize);
|
boolean insertBatch(Collection<T> entityList, int batchSize);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* 根据 ID 选择修改
|
* 根据 ID 选择修改
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param entity 实体对象
|
* @param entity 实体对象
|
||||||
*/
|
*/
|
||||||
boolean updateById(T entity);
|
boolean updateById(T entity);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* 根据 whereEntity 条件,更新记录
|
* 根据 whereEntity 条件,更新记录
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param entity 实体对象
|
* @param entity 实体对象
|
||||||
* @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
|
* @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
|
||||||
*/
|
*/
|
||||||
boolean update(T entity, Wrapper<T> updateWrapper);
|
boolean update(T entity, Wrapper<T> updateWrapper);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* 根据ID 批量更新
|
* 根据ID 批量更新
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param entityList 实体对象集合
|
* @param entityList 实体对象集合
|
||||||
*/
|
*/
|
||||||
boolean updateBatchById(Collection<T> entityList);
|
boolean updateBatchById(Collection<T> entityList);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* 根据ID 批量更新
|
* 根据ID 批量更新
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param entityList 实体对象集合
|
* @param entityList 实体对象集合
|
||||||
* @param batchSize 更新批次数量
|
* @param batchSize 更新批次数量
|
||||||
*/
|
*/
|
||||||
boolean updateBatchById(Collection<T> entityList, int batchSize);
|
boolean updateBatchById(Collection<T> entityList, int batchSize);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* 根据 ID 查询
|
* 根据 ID 查询
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param id 主键ID
|
* @param id 主键ID
|
||||||
*/
|
*/
|
||||||
T selectById(Serializable id);
|
T selectById(Serializable id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* 根据 ID 删除
|
* 根据 ID 删除
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param id 主键ID
|
* @param id 主键ID
|
||||||
*/
|
*/
|
||||||
boolean deleteById(Serializable id);
|
boolean deleteById(Serializable id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* 删除(根据ID 批量删除)
|
* 删除(根据ID 批量删除)
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param idList 主键ID列表
|
* @param idList 主键ID列表
|
||||||
*/
|
*/
|
||||||
boolean deleteBatchIds(Collection<? extends Serializable> idList);
|
boolean deleteBatchIds(Collection<? extends Serializable> idList);
|
||||||
}
|
}
|
||||||
@@ -1,214 +1,214 @@
|
|||||||
package xiaozhi.common.service.impl;
|
package xiaozhi.common.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||||
import com.baomidou.mybatisplus.core.enums.SqlMethod;
|
import com.baomidou.mybatisplus.core.enums.SqlMethod;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
|
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
|
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.BaseService;
|
import xiaozhi.common.service.BaseService;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
import org.apache.ibatis.binding.MapperMethod;
|
import org.apache.ibatis.binding.MapperMethod;
|
||||||
import org.apache.ibatis.logging.Log;
|
import org.apache.ibatis.logging.Log;
|
||||||
import org.apache.ibatis.logging.LogFactory;
|
import org.apache.ibatis.logging.LogFactory;
|
||||||
import org.apache.ibatis.session.SqlSession;
|
import org.apache.ibatis.session.SqlSession;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础服务类,所有Service都要继承
|
* 基础服务类,所有Service都要继承
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
* Website: https://www.renren.io
|
* Website: https://www.renren.io
|
||||||
*/
|
*/
|
||||||
public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements BaseService<T> {
|
public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements BaseService<T> {
|
||||||
@Autowired
|
@Autowired
|
||||||
protected M baseDao;
|
protected M baseDao;
|
||||||
protected Log log = LogFactory.getLog(getClass());
|
protected Log log = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取分页对象
|
* 获取分页对象
|
||||||
*
|
*
|
||||||
* @param params 分页查询参数
|
* @param params 分页查询参数
|
||||||
* @param defaultOrderField 默认排序字段
|
* @param defaultOrderField 默认排序字段
|
||||||
* @param isAsc 排序方式
|
* @param isAsc 排序方式
|
||||||
*/
|
*/
|
||||||
protected IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) {
|
protected IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) {
|
||||||
//分页参数
|
//分页参数
|
||||||
long curPage = 1;
|
long curPage = 1;
|
||||||
long limit = 10;
|
long limit = 10;
|
||||||
|
|
||||||
if (params.get(Constant.PAGE) != null) {
|
if (params.get(Constant.PAGE) != null) {
|
||||||
curPage = Long.parseLong((String) params.get(Constant.PAGE));
|
curPage = Long.parseLong((String) params.get(Constant.PAGE));
|
||||||
}
|
}
|
||||||
if (params.get(Constant.LIMIT) != null) {
|
if (params.get(Constant.LIMIT) != null) {
|
||||||
limit = Long.parseLong((String) params.get(Constant.LIMIT));
|
limit = Long.parseLong((String) params.get(Constant.LIMIT));
|
||||||
}
|
}
|
||||||
|
|
||||||
//分页对象
|
//分页对象
|
||||||
Page<T> page = new Page<>(curPage, limit);
|
Page<T> page = new Page<>(curPage, limit);
|
||||||
|
|
||||||
//分页参数
|
//分页参数
|
||||||
params.put(Constant.PAGE, page);
|
params.put(Constant.PAGE, page);
|
||||||
|
|
||||||
//排序字段
|
//排序字段
|
||||||
String orderField = (String) params.get(Constant.ORDER_FIELD);
|
String orderField = (String) params.get(Constant.ORDER_FIELD);
|
||||||
String order = (String) params.get(Constant.ORDER);
|
String order = (String) params.get(Constant.ORDER);
|
||||||
|
|
||||||
//前端字段排序
|
//前端字段排序
|
||||||
if (StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(order)) {
|
if (StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(order)) {
|
||||||
if (Constant.ASC.equalsIgnoreCase(order)) {
|
if (Constant.ASC.equalsIgnoreCase(order)) {
|
||||||
return page.addOrder(OrderItem.asc(orderField));
|
return page.addOrder(OrderItem.asc(orderField));
|
||||||
} else {
|
} else {
|
||||||
return page.addOrder(OrderItem.desc(orderField));
|
return page.addOrder(OrderItem.desc(orderField));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//没有排序字段,则不排序
|
//没有排序字段,则不排序
|
||||||
if (StringUtils.isBlank(defaultOrderField)) {
|
if (StringUtils.isBlank(defaultOrderField)) {
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
//默认排序
|
//默认排序
|
||||||
if (isAsc) {
|
if (isAsc) {
|
||||||
page.addOrder(OrderItem.asc(defaultOrderField));
|
page.addOrder(OrderItem.asc(defaultOrderField));
|
||||||
} else {
|
} else {
|
||||||
page.addOrder(OrderItem.desc(defaultOrderField));
|
page.addOrder(OrderItem.desc(defaultOrderField));
|
||||||
}
|
}
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <T> PageData<T> getPageData(List<?> list, long total, Class<T> target) {
|
protected <T> PageData<T> getPageData(List<?> list, long total, Class<T> target) {
|
||||||
List<T> targetList = ConvertUtils.sourceToTarget(list, target);
|
List<T> targetList = ConvertUtils.sourceToTarget(list, target);
|
||||||
|
|
||||||
return new PageData<>(targetList, total);
|
return new PageData<>(targetList, total);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <T> PageData<T> getPageData(IPage page, Class<T> target) {
|
protected <T> PageData<T> getPageData(IPage page, Class<T> target) {
|
||||||
return getPageData(page.getRecords(), page.getTotal(), target);
|
return getPageData(page.getRecords(), page.getTotal(), target);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void paramsToLike(Map<String, Object> params, String... likes) {
|
protected void paramsToLike(Map<String, Object> params, String... likes) {
|
||||||
for (String like : likes) {
|
for (String like : likes) {
|
||||||
String val = (String) params.get(like);
|
String val = (String) params.get(like);
|
||||||
if (StringUtils.isNotBlank(val)) {
|
if (StringUtils.isNotBlank(val)) {
|
||||||
params.put(like, "%" + val + "%");
|
params.put(like, "%" + val + "%");
|
||||||
} else {
|
} else {
|
||||||
params.put(like, null);
|
params.put(like, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* 判断数据库操作是否成功
|
* 判断数据库操作是否成功
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* 注意!! 该方法为 Integer 判断,不可传入 int 基本类型
|
* 注意!! 该方法为 Integer 判断,不可传入 int 基本类型
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param result 数据库操作返回影响条数
|
* @param result 数据库操作返回影响条数
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
protected static boolean retBool(Integer result) {
|
protected static boolean retBool(Integer result) {
|
||||||
return SqlHelper.retBool(result);
|
return SqlHelper.retBool(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Class<M> currentMapperClass() {
|
protected Class<M> currentMapperClass() {
|
||||||
return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0);
|
return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<T> currentModelClass() {
|
public Class<T> currentModelClass() {
|
||||||
return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 1);
|
return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getSqlStatement(SqlMethod sqlMethod) {
|
protected String getSqlStatement(SqlMethod sqlMethod) {
|
||||||
return SqlHelper.getSqlStatement(this.currentMapperClass(), sqlMethod);
|
return SqlHelper.getSqlStatement(this.currentMapperClass(), sqlMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public boolean insert(T entity) {
|
public boolean insert(T entity) {
|
||||||
return BaseServiceImpl.retBool(baseDao.insert(entity));
|
return BaseServiceImpl.retBool(baseDao.insert(entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public boolean insertBatch(Collection<T> entityList) {
|
public boolean insertBatch(Collection<T> entityList) {
|
||||||
return insertBatch(entityList, 100);
|
return insertBatch(entityList, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量插入
|
* 批量插入
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public boolean insertBatch(Collection<T> entityList, int batchSize) {
|
public boolean insertBatch(Collection<T> entityList, int batchSize) {
|
||||||
String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
|
String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
|
||||||
return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
|
return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行批量操作
|
* 执行批量操作
|
||||||
*/
|
*/
|
||||||
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
|
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
|
||||||
return SqlHelper.executeBatch(this.currentModelClass(), this.log, list, batchSize, consumer);
|
return SqlHelper.executeBatch(this.currentModelClass(), this.log, list, batchSize, consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public boolean updateById(T entity) {
|
public boolean updateById(T entity) {
|
||||||
return BaseServiceImpl.retBool(baseDao.updateById(entity));
|
return BaseServiceImpl.retBool(baseDao.updateById(entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean update(T entity, Wrapper<T> updateWrapper) {
|
public boolean update(T entity, Wrapper<T> updateWrapper) {
|
||||||
return BaseServiceImpl.retBool(baseDao.update(entity, updateWrapper));
|
return BaseServiceImpl.retBool(baseDao.update(entity, updateWrapper));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public boolean updateBatchById(Collection<T> entityList) {
|
public boolean updateBatchById(Collection<T> entityList) {
|
||||||
return updateBatchById(entityList, 30);
|
return updateBatchById(entityList, 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public boolean updateBatchById(Collection<T> entityList, int batchSize) {
|
public boolean updateBatchById(Collection<T> entityList, int batchSize) {
|
||||||
String sqlStatement = getSqlStatement(SqlMethod.UPDATE_BY_ID);
|
String sqlStatement = getSqlStatement(SqlMethod.UPDATE_BY_ID);
|
||||||
return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
|
return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
|
||||||
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
|
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
|
||||||
param.put(Constants.ENTITY, entity);
|
param.put(Constants.ENTITY, entity);
|
||||||
sqlSession.update(sqlStatement, param);
|
sqlSession.update(sqlStatement, param);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T selectById(Serializable id) {
|
public T selectById(Serializable id) {
|
||||||
return baseDao.selectById(id);
|
return baseDao.selectById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteById(Serializable id) {
|
public boolean deleteById(Serializable id) {
|
||||||
return SqlHelper.retBool(baseDao.deleteById(id));
|
return SqlHelper.retBool(baseDao.deleteById(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteBatchIds(Collection<? extends Serializable> idList) {
|
public boolean deleteBatchIds(Collection<? extends Serializable> idList) {
|
||||||
return SqlHelper.retBool(baseDao.deleteBatchIds(idList));
|
return SqlHelper.retBool(baseDao.deleteBatchIds(idList));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Executable → Regular
@@ -1,59 +1,59 @@
|
|||||||
package xiaozhi.modules.sys.dto;
|
package xiaozhi.modules.sys.dto;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import xiaozhi.common.utils.DateUtils;
|
import xiaozhi.common.utils.DateUtils;
|
||||||
import xiaozhi.common.validator.group.AddGroup;
|
import xiaozhi.common.validator.group.AddGroup;
|
||||||
import xiaozhi.common.validator.group.DefaultGroup;
|
import xiaozhi.common.validator.group.DefaultGroup;
|
||||||
import xiaozhi.common.validator.group.UpdateGroup;
|
import xiaozhi.common.validator.group.UpdateGroup;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.Min;
|
import jakarta.validation.constraints.Min;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import jakarta.validation.constraints.Null;
|
import jakarta.validation.constraints.Null;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字典数据
|
* 字典数据
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Schema(description = "字典数据")
|
@Schema(description = "字典数据")
|
||||||
public class SysDictDataDTO implements Serializable {
|
public class SysDictDataDTO implements Serializable {
|
||||||
|
|
||||||
|
|
||||||
@Schema(description = "id")
|
@Schema(description = "id")
|
||||||
@Null(message = "{id.null}", groups = AddGroup.class)
|
@Null(message = "{id.null}", groups = AddGroup.class)
|
||||||
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
|
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Schema(description = "字典类型ID")
|
@Schema(description = "字典类型ID")
|
||||||
@NotNull(message = "{sysdict.type.require}", groups = DefaultGroup.class)
|
@NotNull(message = "{sysdict.type.require}", groups = DefaultGroup.class)
|
||||||
private Long dictTypeId;
|
private Long dictTypeId;
|
||||||
|
|
||||||
@Schema(description = "字典标签")
|
@Schema(description = "字典标签")
|
||||||
@NotBlank(message = "{sysdict.label.require}", groups = DefaultGroup.class)
|
@NotBlank(message = "{sysdict.label.require}", groups = DefaultGroup.class)
|
||||||
private String dictLabel;
|
private String dictLabel;
|
||||||
|
|
||||||
@Schema(description = "字典值")
|
@Schema(description = "字典值")
|
||||||
private String dictValue;
|
private String dictValue;
|
||||||
|
|
||||||
@Schema(description = "备注")
|
@Schema(description = "备注")
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
@Schema(description = "排序")
|
@Schema(description = "排序")
|
||||||
@Min(value = 0, message = "{sort.number}", groups = DefaultGroup.class)
|
@Min(value = 0, message = "{sort.number}", groups = DefaultGroup.class)
|
||||||
private Integer sort;
|
private Integer sort;
|
||||||
|
|
||||||
@Schema(description = "创建时间")
|
@Schema(description = "创建时间")
|
||||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
@JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
|
@JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
|
||||||
private Date createDate;
|
private Date createDate;
|
||||||
|
|
||||||
@Schema(description = "更新时间")
|
@Schema(description = "更新时间")
|
||||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
@JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
|
@JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
|
||||||
private Date updateDate;
|
private Date updateDate;
|
||||||
}
|
}
|
||||||
@@ -1,55 +1,55 @@
|
|||||||
package xiaozhi.modules.sys.dto;
|
package xiaozhi.modules.sys.dto;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import xiaozhi.common.utils.DateUtils;
|
import xiaozhi.common.utils.DateUtils;
|
||||||
import xiaozhi.common.validator.group.AddGroup;
|
import xiaozhi.common.validator.group.AddGroup;
|
||||||
import xiaozhi.common.validator.group.DefaultGroup;
|
import xiaozhi.common.validator.group.DefaultGroup;
|
||||||
import xiaozhi.common.validator.group.UpdateGroup;
|
import xiaozhi.common.validator.group.UpdateGroup;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.Min;
|
import jakarta.validation.constraints.Min;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import jakarta.validation.constraints.Null;
|
import jakarta.validation.constraints.Null;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字典类型
|
* 字典类型
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Schema(description = "字典类型")
|
@Schema(description = "字典类型")
|
||||||
public class SysDictTypeDTO implements Serializable {
|
public class SysDictTypeDTO implements Serializable {
|
||||||
|
|
||||||
|
|
||||||
@Schema(description = "id")
|
@Schema(description = "id")
|
||||||
@Null(message = "{id.null}", groups = AddGroup.class)
|
@Null(message = "{id.null}", groups = AddGroup.class)
|
||||||
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
|
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Schema(description = "字典类型")
|
@Schema(description = "字典类型")
|
||||||
@NotBlank(message = "{sysdict.type.require}", groups = DefaultGroup.class)
|
@NotBlank(message = "{sysdict.type.require}", groups = DefaultGroup.class)
|
||||||
private String dictType;
|
private String dictType;
|
||||||
|
|
||||||
@Schema(description = "字典名称")
|
@Schema(description = "字典名称")
|
||||||
@NotBlank(message = "{sysdict.name.require}", groups = DefaultGroup.class)
|
@NotBlank(message = "{sysdict.name.require}", groups = DefaultGroup.class)
|
||||||
private String dictName;
|
private String dictName;
|
||||||
|
|
||||||
@Schema(description = "备注")
|
@Schema(description = "备注")
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
@Schema(description = "排序")
|
@Schema(description = "排序")
|
||||||
@Min(value = 0, message = "{sort.number}", groups = DefaultGroup.class)
|
@Min(value = 0, message = "{sort.number}", groups = DefaultGroup.class)
|
||||||
private Integer sort;
|
private Integer sort;
|
||||||
|
|
||||||
@Schema(description = "创建时间")
|
@Schema(description = "创建时间")
|
||||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
@JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
|
@JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
|
||||||
private Date createDate;
|
private Date createDate;
|
||||||
|
|
||||||
@Schema(description = "更新时间")
|
@Schema(description = "更新时间")
|
||||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
private Date updateDate;
|
private Date updateDate;
|
||||||
}
|
}
|
||||||
@@ -1,52 +1,52 @@
|
|||||||
package xiaozhi.modules.sys.dto;
|
package xiaozhi.modules.sys.dto;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import xiaozhi.common.utils.DateUtils;
|
import xiaozhi.common.utils.DateUtils;
|
||||||
import xiaozhi.common.validator.group.AddGroup;
|
import xiaozhi.common.validator.group.AddGroup;
|
||||||
import xiaozhi.common.validator.group.DefaultGroup;
|
import xiaozhi.common.validator.group.DefaultGroup;
|
||||||
import xiaozhi.common.validator.group.UpdateGroup;
|
import xiaozhi.common.validator.group.UpdateGroup;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import jakarta.validation.constraints.Null;
|
import jakarta.validation.constraints.Null;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 参数管理
|
* 参数管理
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Schema(description = "参数管理")
|
@Schema(description = "参数管理")
|
||||||
public class SysParamsDTO implements Serializable {
|
public class SysParamsDTO implements Serializable {
|
||||||
|
|
||||||
|
|
||||||
@Schema(description = "id")
|
@Schema(description = "id")
|
||||||
@Null(message = "{id.null}", groups = AddGroup.class)
|
@Null(message = "{id.null}", groups = AddGroup.class)
|
||||||
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
|
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Schema(description = "参数编码")
|
@Schema(description = "参数编码")
|
||||||
@NotBlank(message = "{sysparams.paramcode.require}", groups = DefaultGroup.class)
|
@NotBlank(message = "{sysparams.paramcode.require}", groups = DefaultGroup.class)
|
||||||
private String paramCode;
|
private String paramCode;
|
||||||
|
|
||||||
@Schema(description = "参数值")
|
@Schema(description = "参数值")
|
||||||
@NotBlank(message = "{sysparams.paramvalue.require}", groups = DefaultGroup.class)
|
@NotBlank(message = "{sysparams.paramvalue.require}", groups = DefaultGroup.class)
|
||||||
private String paramValue;
|
private String paramValue;
|
||||||
|
|
||||||
@Schema(description = "备注")
|
@Schema(description = "备注")
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
@Schema(description = "创建时间")
|
@Schema(description = "创建时间")
|
||||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
@JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
|
@JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
|
||||||
private Date createDate;
|
private Date createDate;
|
||||||
|
|
||||||
@Schema(description = "更新时间")
|
@Schema(description = "更新时间")
|
||||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
@JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
|
@JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
|
||||||
private Date updateDate;
|
private Date updateDate;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-25
@@ -1,26 +1,30 @@
|
|||||||
package xiaozhi.modules.sys.service;
|
package xiaozhi.modules.sys.service;
|
||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.BaseService;
|
import xiaozhi.common.service.BaseService;
|
||||||
import xiaozhi.modules.sys.dto.SysDictTypeDTO;
|
import xiaozhi.modules.sys.dto.SysDictTypeDTO;
|
||||||
import xiaozhi.modules.sys.entity.DictType;
|
import xiaozhi.modules.sys.entity.DictType;
|
||||||
import xiaozhi.modules.sys.entity.SysDictTypeEntity;
|
import xiaozhi.modules.sys.entity.SysDictTypeEntity;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据字典
|
* 数据字典
|
||||||
*/
|
*/
|
||||||
public interface SysDictTypeService extends BaseService<SysDictTypeEntity> {
|
public interface SysDictTypeService extends BaseService<SysDictTypeEntity> {
|
||||||
|
|
||||||
PageData<SysDictTypeDTO> page(Map<String, Object> params);
|
PageData<SysDictTypeDTO> page(Map<String, Object> params);
|
||||||
|
|
||||||
SysDictTypeDTO get(Long id);
|
SysDictTypeDTO get(Long id);
|
||||||
|
|
||||||
void save(SysDictTypeDTO dto);
|
void save(SysDictTypeDTO dto);
|
||||||
|
|
||||||
void update(SysDictTypeDTO dto);
|
void update(SysDictTypeDTO dto);
|
||||||
|
|
||||||
void delete(Long[] ids);
|
void delete(Long[] ids);
|
||||||
|
|
||||||
|
List<DictType> getAllList();
|
||||||
|
|
||||||
|
List<DictType> getDictTypeList();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package xiaozhi.modules.sys.service;
|
||||||
|
|
||||||
|
public interface TokenService {
|
||||||
|
/**
|
||||||
|
* 生成token
|
||||||
|
*
|
||||||
|
* @param userId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String createToken(long userId);
|
||||||
|
}
|
||||||
+101
-101
@@ -1,102 +1,102 @@
|
|||||||
package xiaozhi.modules.sys.service.impl;
|
package xiaozhi.modules.sys.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
import xiaozhi.modules.sys.dao.SysDictDataDao;
|
import xiaozhi.modules.sys.dao.SysDictDataDao;
|
||||||
import xiaozhi.modules.sys.dao.SysDictTypeDao;
|
import xiaozhi.modules.sys.dao.SysDictTypeDao;
|
||||||
import xiaozhi.modules.sys.dto.SysDictTypeDTO;
|
import xiaozhi.modules.sys.dto.SysDictTypeDTO;
|
||||||
import xiaozhi.modules.sys.entity.DictData;
|
import xiaozhi.modules.sys.entity.DictData;
|
||||||
import xiaozhi.modules.sys.entity.DictType;
|
import xiaozhi.modules.sys.entity.DictType;
|
||||||
import xiaozhi.modules.sys.entity.SysDictTypeEntity;
|
import xiaozhi.modules.sys.entity.SysDictTypeEntity;
|
||||||
import xiaozhi.modules.sys.service.SysDictTypeService;
|
import xiaozhi.modules.sys.service.SysDictTypeService;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字典类型
|
* 字典类型
|
||||||
*/
|
*/
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Service
|
@Service
|
||||||
public class SysDictTypeServiceImpl extends BaseServiceImpl<SysDictTypeDao, SysDictTypeEntity> implements SysDictTypeService {
|
public class SysDictTypeServiceImpl extends BaseServiceImpl<SysDictTypeDao, SysDictTypeEntity> implements SysDictTypeService {
|
||||||
private final SysDictDataDao sysDictDataDao;
|
private final SysDictDataDao sysDictDataDao;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageData<SysDictTypeDTO> page(Map<String, Object> params) {
|
public PageData<SysDictTypeDTO> page(Map<String, Object> params) {
|
||||||
IPage<SysDictTypeEntity> page = baseDao.selectPage(
|
IPage<SysDictTypeEntity> page = baseDao.selectPage(
|
||||||
getPage(params, "sort", true),
|
getPage(params, "sort", true),
|
||||||
getWrapper(params)
|
getWrapper(params)
|
||||||
);
|
);
|
||||||
|
|
||||||
return getPageData(page, SysDictTypeDTO.class);
|
return getPageData(page, SysDictTypeDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryWrapper<SysDictTypeEntity> getWrapper(Map<String, Object> params) {
|
private QueryWrapper<SysDictTypeEntity> getWrapper(Map<String, Object> params) {
|
||||||
String dictType = (String) params.get("dictType");
|
String dictType = (String) params.get("dictType");
|
||||||
String dictName = (String) params.get("dictName");
|
String dictName = (String) params.get("dictName");
|
||||||
|
|
||||||
QueryWrapper<SysDictTypeEntity> wrapper = new QueryWrapper<>();
|
QueryWrapper<SysDictTypeEntity> wrapper = new QueryWrapper<>();
|
||||||
wrapper.like(StringUtils.isNotBlank(dictType), "dict_type", dictType);
|
wrapper.like(StringUtils.isNotBlank(dictType), "dict_type", dictType);
|
||||||
wrapper.like(StringUtils.isNotBlank(dictName), "dict_name", dictName);
|
wrapper.like(StringUtils.isNotBlank(dictName), "dict_name", dictName);
|
||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SysDictTypeDTO get(Long id) {
|
public SysDictTypeDTO get(Long id) {
|
||||||
SysDictTypeEntity entity = baseDao.selectById(id);
|
SysDictTypeEntity entity = baseDao.selectById(id);
|
||||||
|
|
||||||
return ConvertUtils.sourceToTarget(entity, SysDictTypeDTO.class);
|
return ConvertUtils.sourceToTarget(entity, SysDictTypeDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void save(SysDictTypeDTO dto) {
|
public void save(SysDictTypeDTO dto) {
|
||||||
SysDictTypeEntity entity = ConvertUtils.sourceToTarget(dto, SysDictTypeEntity.class);
|
SysDictTypeEntity entity = ConvertUtils.sourceToTarget(dto, SysDictTypeEntity.class);
|
||||||
|
|
||||||
insert(entity);
|
insert(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void update(SysDictTypeDTO dto) {
|
public void update(SysDictTypeDTO dto) {
|
||||||
SysDictTypeEntity entity = ConvertUtils.sourceToTarget(dto, SysDictTypeEntity.class);
|
SysDictTypeEntity entity = ConvertUtils.sourceToTarget(dto, SysDictTypeEntity.class);
|
||||||
|
|
||||||
updateById(entity);
|
updateById(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void delete(Long[] ids) {
|
public void delete(Long[] ids) {
|
||||||
//删除
|
//删除
|
||||||
deleteBatchIds(Arrays.asList(ids));
|
deleteBatchIds(Arrays.asList(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<DictType> getAllList() {
|
public List<DictType> getAllList() {
|
||||||
List<DictType> typeList = baseDao.getDictTypeList();
|
List<DictType> typeList = baseDao.getDictTypeList();
|
||||||
List<DictData> dataList = sysDictDataDao.getDictDataList();
|
List<DictData> dataList = sysDictDataDao.getDictDataList();
|
||||||
for (DictType type : typeList) {
|
for (DictType type : typeList) {
|
||||||
for (DictData data : dataList) {
|
for (DictData data : dataList) {
|
||||||
if (type.getId().equals(data.getDictTypeId())) {
|
if (type.getId().equals(data.getDictTypeId())) {
|
||||||
type.getDataList().add(data);
|
type.getDataList().add(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return typeList;
|
return typeList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<DictType> getDictTypeList() {
|
public List<DictType> getDictTypeList() {
|
||||||
return baseDao.getDictTypeList();
|
return baseDao.getDictTypeList();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package xiaozhi.modules.sys.service.impl;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
|
import xiaozhi.modules.security.oauth2.TokenGenerator;
|
||||||
|
import xiaozhi.modules.sys.service.TokenService;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Service
|
||||||
|
public class TokenServiceImpl implements TokenService {
|
||||||
|
private final RedisUtils redisUtils;
|
||||||
|
/**
|
||||||
|
* 3小时无操作过期
|
||||||
|
*/
|
||||||
|
private final static int EXPIRE = 60 * 60 * 3;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createToken(long userId) {
|
||||||
|
//生成一个token
|
||||||
|
String token = TokenGenerator.generateValue();
|
||||||
|
|
||||||
|
//当前时间
|
||||||
|
Date now = new Date();
|
||||||
|
//过期时间
|
||||||
|
Date expireTime = new Date(now.getTime() + EXPIRE * 1000);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,49 +1,44 @@
|
|||||||
knife4j:
|
knife4j:
|
||||||
production: false
|
production: false
|
||||||
enable: true
|
enable: true
|
||||||
basic:
|
basic:
|
||||||
enable: false
|
enable: false
|
||||||
username: renren
|
username: renren
|
||||||
password: 2ZABCDEUgF
|
password: 2ZABCDEUgF
|
||||||
setting:
|
setting:
|
||||||
enableFooter: false
|
enableFooter: false
|
||||||
jasypt:
|
jasypt:
|
||||||
encryptor:
|
encryptor:
|
||||||
password: P9Hx718z8L
|
password: P9Hx718z8L
|
||||||
spring:
|
spring:
|
||||||
datasource:
|
datasource:
|
||||||
druid:
|
druid:
|
||||||
#MySQL
|
#MySQL
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
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://localhost:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
|
||||||
username: root
|
username: root
|
||||||
password: 123456
|
password: 123456
|
||||||
initial-size: 10
|
initial-size: 10
|
||||||
max-active: 100
|
max-active: 100
|
||||||
min-idle: 10
|
min-idle: 10
|
||||||
max-wait: 6000
|
max-wait: 6000
|
||||||
pool-prepared-statements: true
|
pool-prepared-statements: true
|
||||||
max-pool-prepared-statement-per-connection-size: 20
|
max-pool-prepared-statement-per-connection-size: 20
|
||||||
time-between-eviction-runs-millis: 60000
|
time-between-eviction-runs-millis: 60000
|
||||||
min-evictable-idle-time-millis: 300000
|
min-evictable-idle-time-millis: 300000
|
||||||
test-while-idle: true
|
test-while-idle: true
|
||||||
test-on-borrow: false
|
test-on-borrow: false
|
||||||
test-on-return: false
|
test-on-return: false
|
||||||
stat-view-servlet:
|
stat-view-servlet:
|
||||||
enabled: true
|
enabled: true
|
||||||
url-pattern: /druid/*
|
url-pattern: /druid/*
|
||||||
login-username: admin
|
login-username: admin
|
||||||
login-password: D7Xj810i1C
|
login-password: D7Xj810i1C
|
||||||
filter:
|
filter:
|
||||||
stat:
|
stat:
|
||||||
log-slow-sql: true
|
log-slow-sql: true
|
||||||
slow-sql-millis: 1000
|
slow-sql-millis: 1000
|
||||||
merge-sql: false
|
merge-sql: false
|
||||||
wall:
|
wall:
|
||||||
config:
|
config:
|
||||||
multi-statement-allow: true
|
multi-statement-allow: true
|
||||||
|
|
||||||
logging:
|
|
||||||
level:
|
|
||||||
org.flowable.engine.impl.persistence.entity.*: debug
|
|
||||||
org.flowable.task.service.impl.persistence.entity.*: debug
|
|
||||||
@@ -1,50 +1,50 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
|
||||||
<mapper namespace="xiaozhi.modules.sys.dao.SysUserDao">
|
<mapper namespace="xiaozhi.modules.sys.dao.SysUserDao">
|
||||||
|
|
||||||
<select id="getList" resultType="xiaozhi.modules.sys.entity.SysUserEntity">
|
<select id="getList" resultType="xiaozhi.modules.sys.entity.SysUserEntity">
|
||||||
select t1.*, (select t2.name from sys_dept t2 where t2.id=t1.dept_id) deptName
|
select t1.*, (select t2.name from sys_dept t2 where t2.id=t1.dept_id) deptName
|
||||||
from sys_user t1 where t1.super_admin = 0
|
from sys_user t1 where t1.super_admin = 0
|
||||||
<if test="username != null and username.trim() != ''">
|
<if test="username != null and username.trim() != ''">
|
||||||
and t1.username like #{username}
|
and t1.username like #{username}
|
||||||
</if>
|
</if>
|
||||||
<if test="deptId != null and deptId.trim() != ''">
|
<if test="deptId != null and deptId.trim() != ''">
|
||||||
and t1.dept_id = #{deptId}
|
and t1.dept_id = #{deptId}
|
||||||
</if>
|
</if>
|
||||||
<if test="gender != null and gender.trim() != ''">
|
<if test="gender != null and gender.trim() != ''">
|
||||||
and t1.gender = #{gender}
|
and t1.gender = #{gender}
|
||||||
</if>
|
</if>
|
||||||
<if test="deptIdList != null">
|
<if test="deptIdList != null">
|
||||||
and t1.dept_id in
|
and t1.dept_id in
|
||||||
<foreach item="id" collection="deptIdList" open="(" separator="," close=")">
|
<foreach item="id" collection="deptIdList" open="(" separator="," close=")">
|
||||||
#{id}
|
#{id}
|
||||||
</foreach>
|
</foreach>
|
||||||
</if>
|
</if>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="getById" resultType="xiaozhi.modules.sys.entity.SysUserEntity">
|
<select id="getById" resultType="xiaozhi.modules.sys.entity.SysUserEntity">
|
||||||
select t1.*, (select t2.name from sys_dept t2 where t2.id=t1.dept_id) deptName from sys_user t1
|
select t1.*, (select t2.name from sys_dept t2 where t2.id=t1.dept_id) deptName from sys_user t1
|
||||||
where t1.id = #{value}
|
where t1.id = #{value}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="getByUsername" resultType="xiaozhi.modules.sys.entity.SysUserEntity">
|
<select id="getByUsername" resultType="xiaozhi.modules.sys.entity.SysUserEntity">
|
||||||
select * from sys_user where username = #{value}
|
select * from sys_user where username = #{value}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<update id="updatePassword">
|
<update id="updatePassword">
|
||||||
update sys_user set password = #{newPassword} where id = #{id}
|
update sys_user set password = #{newPassword} where id = #{id}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
<select id="getCountByDeptId" resultType="int">
|
<select id="getCountByDeptId" resultType="int">
|
||||||
select count(*) from sys_user where dept_id = #{value}
|
select count(*) from sys_user where dept_id = #{value}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="getUserIdListByDeptId" resultType="Long">
|
<select id="getUserIdListByDeptId" resultType="Long">
|
||||||
select id from sys_user where dept_id in
|
select id from sys_user where dept_id in
|
||||||
<foreach item="deptId" collection="list" open="(" separator="," close=")">
|
<foreach item="deptId" collection="list" open="(" separator="," close=")">
|
||||||
#{deptId}
|
#{deptId}
|
||||||
</foreach>
|
</foreach>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package xiaozhi;
|
|
||||||
|
|
||||||
import org.jasypt.encryption.StringEncryptor;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单元测试
|
|
||||||
*/
|
|
||||||
@RunWith(SpringRunner.class)
|
|
||||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
|
||||||
public class DbEncTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
StringEncryptor stringEncryptor;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void jiami() {
|
|
||||||
System.out.println("username:" + stringEncryptor.encrypt("07e43e8d669fb946e31ccd4ef5f32c9f2287619c79b766a5985d2c99ad7b7c7e"));
|
|
||||||
System.out.println("password:" + stringEncryptor.encrypt("042e94093fd2c2765ea45cf13ddbfd38e93026df4b6d5e4206ea5ac90956d63ab73e8b82c6daf7829f9aea7e27e1db5bb0a90944c4c4985af44db0ef49c46d6ad6"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package xiaozhi;
|
|
||||||
|
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
|
||||||
import xiaozhi.modules.sys.entity.SysUserEntity;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
|
||||||
|
|
||||||
@RunWith(SpringRunner.class)
|
|
||||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
|
||||||
public class RedisTest {
|
|
||||||
@Resource
|
|
||||||
private RedisUtils redisUtils;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void contextLoads() {
|
|
||||||
SysUserEntity user = new SysUserEntity();
|
|
||||||
user.setEmail("123456@qq.com");
|
|
||||||
redisUtils.set("user", user);
|
|
||||||
|
|
||||||
System.out.println(ToStringBuilder.reflectionToString(redisUtils.get("user")));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package xiaozhi.service;
|
|
||||||
|
|
||||||
import xiaozhi.modules.sys.dao.SysUserDao;
|
|
||||||
import xiaozhi.modules.sys.entity.SysUserEntity;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试多数据源
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class DynamicDataSourceTestService {
|
|
||||||
@Resource
|
|
||||||
private SysUserDao sysUserDao;
|
|
||||||
|
|
||||||
//@Transactional
|
|
||||||
public void updateUser(Long id) {
|
|
||||||
SysUserEntity user = new SysUserEntity();
|
|
||||||
user.setId(id);
|
|
||||||
user.setMobile("13500000000");
|
|
||||||
//sysUserDao.updateById(user);
|
|
||||||
System.out.println(sysUserDao.selectById(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
+1434
-286
File diff suppressed because it is too large
Load Diff
@@ -7,10 +7,13 @@
|
|||||||
"build": "vue-cli-service build"
|
"build": "vue-cli-service build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.8.1",
|
||||||
"element-ui": "^2.15.14",
|
"element-ui": "^2.15.14",
|
||||||
|
"flyio": "^0.6.14",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"vue": "^2.6.14",
|
"vue": "^2.6.14",
|
||||||
"vue-router": "^3.5.1",
|
"vue-axios": "^3.5.2",
|
||||||
|
"vue-router": "^3.6.5",
|
||||||
"vuex": "^3.6.2"
|
"vuex": "^3.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
// 引入各个模块的请求
|
||||||
|
import user from './module/user.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接口地址
|
||||||
|
* 在开发阶段,如果地址写的是相对路径,请与vue.config.js的devServer配置相结合,方便跨域请求
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const DEV_API_SERVICE = 'https://apifoxmock.com/m1/5931378-5618560-default'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据开发环境返回接口url
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function getServiceUrl() {
|
||||||
|
return DEV_API_SERVICE
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** request服务封装 */
|
||||||
|
export default {
|
||||||
|
getServiceUrl,
|
||||||
|
user,
|
||||||
|
}
|
||||||
Executable
+132
@@ -0,0 +1,132 @@
|
|||||||
|
import {goToPage, showDanger, showWarning} from '../utils/index'
|
||||||
|
import Constant from '../utils/constant'
|
||||||
|
import Fly from 'flyio/dist/npm/fly';
|
||||||
|
|
||||||
|
const fly = new Fly()
|
||||||
|
// 设置超时
|
||||||
|
fly.config.timeout = 30000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request服务封装
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
sendRequest,
|
||||||
|
reAjaxFun,
|
||||||
|
clearRequestTime
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendRequest() {
|
||||||
|
return {
|
||||||
|
_sucCallback: null,
|
||||||
|
_failCallback: null,
|
||||||
|
_method: 'GET',
|
||||||
|
_data: {},
|
||||||
|
_header: {'content-type': 'application/json; charset=utf-8'},
|
||||||
|
_url: '',
|
||||||
|
'send'() {
|
||||||
|
this._header.token = localStorage.getItem(Constant.STORAGE_KEY.TOKEN)
|
||||||
|
fly.request(this._url, this._data, {
|
||||||
|
method: this._method,
|
||||||
|
headers: this._header
|
||||||
|
}).then((res) => {
|
||||||
|
const error = httpHandlerError(res, this._failCallback)
|
||||||
|
if (error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this._sucCallback) {
|
||||||
|
this._sucCallback(res)
|
||||||
|
}
|
||||||
|
}).catch((res) => {
|
||||||
|
console.log(1111, res)
|
||||||
|
httpHandlerError(res, this._failCallback)
|
||||||
|
})
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
'success'(callback) {
|
||||||
|
this._sucCallback = callback
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
'fail'(callback) {
|
||||||
|
this._failCallback = callback
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
'url'(url) {
|
||||||
|
if (url) {
|
||||||
|
url = url.replaceAll('$', '/')
|
||||||
|
}
|
||||||
|
this._url = url
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
'data'(data) {
|
||||||
|
this._data = data
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
'method'(method) {
|
||||||
|
this._method = method
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
'header'(header) {
|
||||||
|
this._header = header
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
'showLoading'(showLoading) {
|
||||||
|
this._showLoading = showLoading
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
'async'(flag) {
|
||||||
|
this.async = flag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Info 请求完成后返回信息
|
||||||
|
* callBack 回调函数
|
||||||
|
* errTip 自定义错误信息
|
||||||
|
*/
|
||||||
|
function httpHandlerError(info, callBack) {
|
||||||
|
/** 请求成功,退出该函数 可以根据项目需求来判断是否请求成功。这里判断的是status为200的时候是成功 */
|
||||||
|
let networkError = false
|
||||||
|
if (info.status === 200) {
|
||||||
|
if (info.data.code === 'success' || info.data.code === 0) {
|
||||||
|
return networkError
|
||||||
|
} else if (info.data.code === 401) {
|
||||||
|
goToPage(Constant.PAGE.LOGIN, true)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
showDanger(info.data.msg)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (callBack) {
|
||||||
|
callBack(info)
|
||||||
|
} else {
|
||||||
|
showDanger(`网络请求出现了错误【${info.status}】`)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
let requestTime = 0
|
||||||
|
let reAjaxSec = 2
|
||||||
|
|
||||||
|
function reAjaxFun(fn) {
|
||||||
|
let nowTimeSec = new Date().getTime() / 1000
|
||||||
|
if (requestTime === 0) {
|
||||||
|
requestTime = nowTimeSec
|
||||||
|
}
|
||||||
|
let ajaxIndex = parseInt((nowTimeSec - requestTime) / reAjaxSec)
|
||||||
|
if (ajaxIndex > 10) {
|
||||||
|
showWarning('似乎无法连接服务器')
|
||||||
|
} else {
|
||||||
|
showWarning('正在连接服务器(' + ajaxIndex + ')')
|
||||||
|
}
|
||||||
|
if (fn) {
|
||||||
|
setTimeout(() => {
|
||||||
|
fn()
|
||||||
|
}, reAjaxSec * 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearRequestTime() {
|
||||||
|
requestTime = 0
|
||||||
|
}
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
import RequestService from '../httpRequest'
|
||||||
|
import {getServiceUrl} from '../api'
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// 登录
|
||||||
|
login(loginForm, callback) {
|
||||||
|
RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/login`).method('POST')
|
||||||
|
.data(loginForm)
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail(() => {
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.login(loginForm, callback)
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
},
|
||||||
|
// 获取用户信息
|
||||||
|
getUserInfo(callback) {
|
||||||
|
RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/info`).method('GET')
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail(() => {
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.getUserInfo()
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
},
|
||||||
|
// 获取设备信息
|
||||||
|
getHomeList(callback) {
|
||||||
|
RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/device/bind`).method('GET')
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail(() => {
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.getUserInfo()
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
},
|
||||||
|
}
|
||||||
Executable
+19
@@ -0,0 +1,19 @@
|
|||||||
|
const HAVE_NO_RESULT = '暂无'
|
||||||
|
export default {
|
||||||
|
HAVE_NO_RESULT, // 项目的配置信息
|
||||||
|
STORAGE_KEY: {
|
||||||
|
TOKEN: 'TOKEN',
|
||||||
|
PUBLIC_KEY: 'PUBLIC_KEY',
|
||||||
|
USER_TYPE: 'USER_TYPE'
|
||||||
|
},
|
||||||
|
Lang: {
|
||||||
|
'zh_cn': 'zh_cn', 'zh_tw': 'zh_tw', 'en': 'en'
|
||||||
|
},
|
||||||
|
FONT_SIZE: {
|
||||||
|
'big': 'big',
|
||||||
|
'normal': 'normal',
|
||||||
|
}, // 获取map中的某key
|
||||||
|
get(map, key) {
|
||||||
|
return map[key] || HAVE_NO_RESULT
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+60
@@ -0,0 +1,60 @@
|
|||||||
|
export const toDate = (date) => {
|
||||||
|
return isDate(date) ? new Date(date) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isDate = (date) => {
|
||||||
|
if (date === null || date === undefined) return false
|
||||||
|
if (isNaN(new Date(date).getTime())) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isDateObject = (val) => {
|
||||||
|
return val instanceof Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatAddDate = (date, format, addDay) => {
|
||||||
|
date = toDate(date)
|
||||||
|
if (!date) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (!addDay) {
|
||||||
|
date.setDate(date.getDate() + addDay)
|
||||||
|
}
|
||||||
|
return formatDateTool(date, format || 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatDate = (date, format) => {
|
||||||
|
date = toDate(date)
|
||||||
|
if (!date) return ''
|
||||||
|
return formatDateTool(date, format || 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateTool(date, fmt) {
|
||||||
|
if (/(y+)/.test(fmt)) {
|
||||||
|
fmt = fmt.replace(
|
||||||
|
RegExp.$1,
|
||||||
|
(date.getFullYear() + '').substr(4 - RegExp.$1.length)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const o = {
|
||||||
|
'M+': date.getMonth() + 1,
|
||||||
|
'd+': date.getDate(),
|
||||||
|
'h+': date.getHours(),
|
||||||
|
'm+': date.getMinutes(),
|
||||||
|
's+': date.getSeconds()
|
||||||
|
}
|
||||||
|
for (const k in o) {
|
||||||
|
if (new RegExp(`(${k})`).test(fmt)) {
|
||||||
|
const str = o[k] + ''
|
||||||
|
fmt = fmt.replace(
|
||||||
|
RegExp.$1,
|
||||||
|
RegExp.$1.length === 1 ? str : padLeftZero(str)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt
|
||||||
|
}
|
||||||
|
|
||||||
|
function padLeftZero(str) {
|
||||||
|
return ('00' + str).substr(str.length)
|
||||||
|
}
|
||||||
Executable
+135
@@ -0,0 +1,135 @@
|
|||||||
|
import router from '../router'
|
||||||
|
import Constant from '../utils/constant'
|
||||||
|
import { Message } from 'element-ui'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断用户是否登录
|
||||||
|
*/
|
||||||
|
export function checkUserLogin(fn) {
|
||||||
|
let token = localStorage.getItem(Constant.STORAGE_KEY.TOKEN)
|
||||||
|
let userType = localStorage.getItem(Constant.STORAGE_KEY.USER_TYPE)
|
||||||
|
if (isNull(token) || isNull(userType)) {
|
||||||
|
goToPage('console', true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (fn) {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为空
|
||||||
|
* @param data
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isNull(data) {
|
||||||
|
if (data === undefined) {
|
||||||
|
return true
|
||||||
|
} else if (data === null) {
|
||||||
|
return true
|
||||||
|
} else if (typeof data === 'string' && (data.length === 0 || data === '' || data === 'undefined' || data === 'null')) {
|
||||||
|
return true
|
||||||
|
} else if ((data instanceof Array) && data.length === 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断不为空
|
||||||
|
* @param data
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isNotNull(data) {
|
||||||
|
return !isNull(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示顶部红色通知
|
||||||
|
* @param msg
|
||||||
|
*/
|
||||||
|
export function showDanger(msg) {
|
||||||
|
if (isNull(msg)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Message({
|
||||||
|
message: msg,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示顶部橙色通知
|
||||||
|
* @param msg
|
||||||
|
*/
|
||||||
|
export function showWarning(msg) {
|
||||||
|
if (isNull(msg)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Message({
|
||||||
|
message: msg,
|
||||||
|
type: 'warning'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示顶部绿色通知
|
||||||
|
* @param msg
|
||||||
|
*/
|
||||||
|
export function showSuccess(msg) {
|
||||||
|
Message({
|
||||||
|
message: msg,
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面跳转
|
||||||
|
* @param path
|
||||||
|
* @param isRepalce
|
||||||
|
*/
|
||||||
|
export function goToPage(path, isRepalce) {
|
||||||
|
if (isRepalce) {
|
||||||
|
router.replace(path)
|
||||||
|
} else {
|
||||||
|
router.push(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前vue页面名称
|
||||||
|
* @param path
|
||||||
|
* @param isRepalce
|
||||||
|
*/
|
||||||
|
export function getCurrentPage() {
|
||||||
|
let hash = location.hash.replace('#', '')
|
||||||
|
if (hash.indexOf('?') > 0) {
|
||||||
|
hash = hash.substring(0, hash.indexOf('?'))
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成从[min,max]的随机数
|
||||||
|
* @param min
|
||||||
|
* @param max
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function randomNum(min, max) {
|
||||||
|
return Math.round(Math.random() * (max - min) + min)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取uuid
|
||||||
|
*/
|
||||||
|
export function getUUID() {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
||||||
|
return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<el-container style="height: 100%;">
|
<el-container style="height: 100%;">
|
||||||
<el-header class="header">
|
<el-header class="header">
|
||||||
<div style="display: flex;justify-content: space-between;">
|
<div style="display: flex;justify-content: space-between;">
|
||||||
<div style="display: flex;align-items: center;gap: 8px;">
|
<div style="display: flex;align-items: center;gap: 8px;margin-top: 10px;">
|
||||||
<img src="@/assets/xiaozhi-logo.png" alt="" style="width: 45px;height: 45px;" />
|
<img src="@/assets/xiaozhi-logo.png" alt="" style="width: 45px;height: 45px;" />
|
||||||
<img src="@/assets/xiaozhi-ai.png" alt="" style="width: 70px;height: 13px;" />
|
<img src="@/assets/xiaozhi-ai.png" alt="" style="width: 70px;height: 13px;" />
|
||||||
<div class="equipment-management" @click="settingDevice=false">
|
<div class="equipment-management" @click="settingDevice=false">
|
||||||
@@ -19,15 +19,16 @@
|
|||||||
<img src="@/assets/home/close.png" alt="" style="width: 6px;height: 6px;" />
|
<img src="@/assets/home/close.png" alt="" style="width: 6px;height: 6px;" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex;align-items: center;gap: 8px;">
|
<div style="display: flex;align-items: center;gap: 8px;margin-top: 10px">
|
||||||
<div class="serach-box">
|
<div class="serach-box">
|
||||||
<el-input placeholder="输入名称搜索.." v-model="serach" />
|
<el-input placeholder="输入名称搜索.." v-model="serach" style="border: none; background: transparent;" />
|
||||||
<img src="@/assets/home/search.png" alt=""
|
<img src="@/assets/home/search.png" alt=""
|
||||||
style="width: 12px;height: 12px;margin-right: 11px;cursor: pointer;" />
|
style="width: 12px;height: 12px;margin-right: 11px;cursor: pointer;" />
|
||||||
</div>
|
</div>
|
||||||
<img src="@/assets/home/avatar.png" alt="" style="width: 21px;height: 21px;" />
|
<img src="@/assets/home/avatar.png" alt="" style="width: 21px;height: 21px;" />
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
158 3632 4642</div>
|
{{ userInfo.mobile }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-header>
|
</el-header>
|
||||||
@@ -56,10 +57,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="display: flex;flex-wrap: wrap;margin-top: 15px;gap: 15px;justify-content: space-between;box-sizing: border-box;">
|
style="display: flex;flex-wrap: wrap;margin-top: 15px;gap: 15px;justify-content: space-between;box-sizing: border-box;">
|
||||||
<div class="device-item" v-for="(item,index) in 10" :key="index">
|
<div class="device-item" v-for="(item,index) in deviceList" :key="index">
|
||||||
<div style="display: flex;justify-content: space-between;">
|
<div style="display: flex;justify-content: space-between;">
|
||||||
<div style="font-weight: 700;font-size: 18px;text-align: left;color: #3d4566;">
|
<div style="font-weight: 700;font-size: 18px;text-align: left;color: #3d4566;">
|
||||||
CC:ba:97:11:a6:ac
|
<!-- CC:ba:97:11:a6:ac-->
|
||||||
|
{{item.list[0]?.mac_address}}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<img src="@/assets/home/delete.png" alt=""
|
<img src="@/assets/home/delete.png" alt=""
|
||||||
@@ -68,7 +70,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="device-name">
|
<div class="device-name">
|
||||||
设备型号:esp32-s3-touch-amoled-1.8
|
设备型号:{{item.list[0]?.device_type}}
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex;gap: 8px;align-items: center;">
|
<div style="display: flex;gap: 8px;align-items: center;">
|
||||||
<div class="settings-btn" @click="clickSettingDevice">
|
<div class="settings-btn" @click="clickSettingDevice">
|
||||||
@@ -77,30 +79,30 @@
|
|||||||
声纹识别</div>
|
声纹识别</div>
|
||||||
<div class="settings-btn">
|
<div class="settings-btn">
|
||||||
历史对话</div>
|
历史对话</div>
|
||||||
<el-switch v-model="switchValue" inactive-text="OTA升级:" :width="32"
|
<el-switch :value="item.list[0]?.ota_upgrade && true || false" inactive-text="OTA升级:" :width="32"
|
||||||
style="margin-left: auto;" />
|
style="margin-left: auto;" />
|
||||||
</div>
|
</div>
|
||||||
<div class="version-info">
|
<div class="version-info">
|
||||||
<div>最近对话:6天前</div>
|
<div>最近对话:{{item.list[0]?.recent_chat_time}}</div>
|
||||||
<div>APP版本:1.1.0</div>
|
<div>APP版本:{{item.list[0]?.app_version}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="settingDevice" style="border-radius: 20px;background: #fafcfe;">
|
<div v-show="settingDevice" style="border-radius: 18px;background: #fafcfe;">
|
||||||
<div
|
<div
|
||||||
style="padding: 19px 30px;font-weight: 700;font-size: 24px;text-align: left;color: #3d4566;display: flex;gap: 16px;align-items: center;">
|
style="padding: 17px 27px;font-weight: 700;font-size: 21px;text-align: left;color: #3d4566;display: flex;gap: 14px;align-items: center;">
|
||||||
<div
|
<div
|
||||||
style="width: 46px;height: 46px;background: #5778ff;border-radius: 50%;display: flex;align-items: center;justify-content: center;">
|
style="width: 41px;height: 41px;background: #5778ff;border-radius: 50%;display: flex;align-items: center;justify-content: center;">
|
||||||
<img src="@/assets/home/setting-user.png" alt="" style="width: 24px;height: 24px;" />
|
<img src="@/assets/home/setting-user.png" alt="" style="width: 21px;height: 21px;" />
|
||||||
</div>
|
</div>
|
||||||
CC:ba:97:11:a6:ac
|
CC:ba:97:11:a6:ac
|
||||||
</div>
|
</div>
|
||||||
<div style="height: 1px;background: #e8f0ff;" />
|
<div style="height: 1px;background: #e8f0ff;" />
|
||||||
<el-form ref="form" :model="form" label-width="90px">
|
<el-form ref="form" :model="form" label-width="81px">
|
||||||
<div style="padding: 20px 30px;max-width: 990px;">
|
<div style="padding: 18px 28px;max-width: 890px;">
|
||||||
<el-form-item label="助手昵称:">
|
<el-form-item label="助手昵称:">
|
||||||
<div class="input-46">
|
<div class="input-46" style="width: 57.5%;">
|
||||||
<el-input v-model="form.name" />
|
<el-input v-model="form.name" />
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -119,7 +121,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="角色音色:">
|
<el-form-item label="角色音色:">
|
||||||
<div style="display: flex;gap: 10px;align-items: center;">
|
<div style="display: flex;gap: 9px;align-items: center;">
|
||||||
<div class="input-46" style="flex:1.4;">
|
<div class="input-46" style="flex:1.4;">
|
||||||
<el-select v-model="form.timbre" placeholder="请选择" style="width: 100%;">
|
<el-select v-model="form.timbre" placeholder="请选择" style="width: 100%;">
|
||||||
<el-option v-for="item in options" :key="item.value" :label="item.label"
|
<el-option v-for="item in options" :key="item.value" :label="item.label"
|
||||||
@@ -135,28 +137,28 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="角色介绍:">
|
<el-form-item label="角色介绍:">
|
||||||
<div class="textarea-box">
|
<div class="textarea-box">
|
||||||
<el-input type="textarea" rows="6" resize="none" placeholder="请输入内容"
|
<el-input type="textarea" rows="5.4" resize="none" placeholder="请输入内容"
|
||||||
v-model="form.introduction" maxlength="2000" show-word-limit />
|
v-model="form.introduction" maxlength="2000" show-word-limit />
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="记忆体:">
|
<el-form-item label="记忆体:">
|
||||||
<div class="textarea-box">
|
<div class="textarea-box">
|
||||||
<el-input type="textarea" rows="6" resize="none" placeholder="请输入内容"
|
<el-input type="textarea" rows="5.4" resize="none" placeholder="请输入内容"
|
||||||
v-model="form.prompt" maxlength="1000" />
|
v-model="form.prompt" maxlength="1000" />
|
||||||
<div class="prompt-bottom">
|
<div class="prompt-bottom">
|
||||||
<div style="display: flex;gap: 10px;align-items: center;">
|
<div style="display: flex;gap: 10px;align-items: center;">
|
||||||
<div style="color: #979db1;font-size: 14px;">当前记忆(每次对话后重新生成)</div>
|
<div style="color: #979db1;font-size: 12px;">当前记忆(每次对话后重新生成)</div>
|
||||||
<div class="clear-btn">
|
<div class="clear-btn">
|
||||||
<i class="el-icon-delete-solid" style="font-size: 14px;" />
|
<i class="el-icon-delete-solid" style="font-size: 12px;" />
|
||||||
清除
|
清除
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="color: #979db1;font-size:14px;">{{form.prompt.length}}/1000</div>
|
<div style="color: #979db1;font-size:12px;">{{form.prompt.length}}/1000</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="语言模型(内测):" class="lh-form-item">
|
<el-form-item label="语言模型(内测):" class="lh-form-item">
|
||||||
<div style="display: flex;gap: 10px;">
|
<div style="display: flex;gap: 9px;">
|
||||||
<div class="input-46" style="width: 100%;">
|
<div class="input-46" style="width: 100%;">
|
||||||
<el-select v-model="form.model" placeholder="请选择" style="width: 100%;">
|
<el-select v-model="form.model" placeholder="请选择" style="width: 100%;">
|
||||||
<el-option v-for="item in options" :key="item.value" :label="item.label"
|
<el-option v-for="item in options" :key="item.value" :label="item.label"
|
||||||
@@ -172,23 +174,24 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
<div style="display: flex;padding: 20px;gap: 10px;align-items: center;">
|
<div style="display: flex;padding: 18px;gap: 9px;align-items: center;">
|
||||||
<div class="save-btn">
|
<div class="save-btn">
|
||||||
保存配置</div>
|
保存配置</div>
|
||||||
<div class="reset-btn">
|
<div class="reset-btn">
|
||||||
重制</div>
|
重制</div>
|
||||||
<div class="clear-text">
|
<div class="clear-text">
|
||||||
<img src="@/assets/home/red-info.png" alt="" style="width: 24px;height: 24px;" />
|
<img src="@/assets/home/red-info.png" alt="" style="width: 21px;height: 21px;" />
|
||||||
保存配置后,需要重启设备,新的配置才会生效。
|
保存配置后,需要重启设备,新的配置才会生效。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="font-size: 12px;font-weight: 400;margin-top: auto;padding-top: 30px;color: #979db1;">
|
style="font-size: 12px;font-weight: 400;margin-top: auto;padding-top: 30px;color: #979db1;">
|
||||||
©2025 xiaozhi-esp32-server</div>
|
©2025 xiaozhi-esp32-server
|
||||||
|
</div>
|
||||||
</el-main>
|
</el-main>
|
||||||
</el-container>
|
</el-container>
|
||||||
<el-dialog :visible.sync="addDeviceDialogVisible" width="480px" center>
|
<el-dialog :visible.sync="addDeviceDialogVisible" width="400px" center>
|
||||||
<div
|
<div
|
||||||
style="margin: 0 20px 20px;display: flex;align-items: center;gap: 10px;font-weight: 700;font-size: 20px;text-align: left;color: #3d4566;;">
|
style="margin: 0 20px 20px;display: flex;align-items: center;gap: 10px;font-weight: 700;font-size: 20px;text-align: left;color: #3d4566;;">
|
||||||
<div
|
<div
|
||||||
@@ -220,6 +223,8 @@
|
|||||||
<script>
|
<script>
|
||||||
// @ is an alias to /src
|
// @ is an alias to /src
|
||||||
|
|
||||||
|
import Api from '@/apis/api';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'home',
|
name: 'home',
|
||||||
data() {
|
data() {
|
||||||
@@ -242,8 +247,12 @@ export default {
|
|||||||
}, {
|
}, {
|
||||||
value: '选项2',
|
value: '选项2',
|
||||||
label: '双皮奶'
|
label: '双皮奶'
|
||||||
}]
|
}],
|
||||||
}
|
userInfo: {
|
||||||
|
mobile: '' // 初始化用户信息
|
||||||
|
},
|
||||||
|
deviceList:[]
|
||||||
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showAddDialog() {
|
showAddDialog() {
|
||||||
@@ -251,10 +260,28 @@ export default {
|
|||||||
},
|
},
|
||||||
clickSettingDevice() {
|
clickSettingDevice() {
|
||||||
this.settingDevice = true
|
this.settingDevice = true
|
||||||
|
},
|
||||||
|
// 获取用户信息
|
||||||
|
fetchUserInfo() {
|
||||||
|
Api.user.getUserInfo(({data}) => {
|
||||||
|
this.userInfo = data.data
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 获取已绑设备
|
||||||
|
getList(){
|
||||||
|
Api.user.getHomeList(({data})=>{
|
||||||
|
console.log(data.data)
|
||||||
|
this.deviceList = data.data
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchUserInfo(); // 组件加载时获取用户信息
|
||||||
|
this.getList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.welcome {
|
.welcome {
|
||||||
min-width: 900px;
|
min-width: 900px;
|
||||||
@@ -356,11 +383,14 @@ export default {
|
|||||||
}
|
}
|
||||||
.serach-box {
|
.serach-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 230px;
|
width: 250px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
background-color: #e2f5f7;
|
background-color: #f6fcfe66;
|
||||||
|
border: 1px solid #e4e6ef;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding: 0 10px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
.user-info {
|
.user-info {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -389,7 +419,7 @@ export default {
|
|||||||
}
|
}
|
||||||
.template-item {
|
.template-item {
|
||||||
height: 35px;
|
height: 35px;
|
||||||
width: 75px;
|
width: 85px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: #e6ebff;
|
background: #e6ebff;
|
||||||
line-height: 35px;
|
line-height: 35px;
|
||||||
@@ -461,7 +491,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.device-item {
|
.device-item {
|
||||||
width: 341px;
|
width: 345px;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
background: #fafcfe;
|
background: #fafcfe;
|
||||||
padding: 22px;
|
padding: 22px;
|
||||||
@@ -504,6 +534,7 @@ audio::-webkit-media-controls-panel {
|
|||||||
line-height: 34px;
|
line-height: 34px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
.save-btn {
|
.save-btn {
|
||||||
border-radius: 23px;
|
border-radius: 23px;
|
||||||
@@ -526,6 +557,14 @@ audio::-webkit-media-controls-panel {
|
|||||||
border: none !important;
|
border: none !important;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
}
|
}
|
||||||
|
// 搜索输入框的样式调整
|
||||||
|
.serach-box .el-input__inner {
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0 5px 0 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #3d4566;
|
||||||
|
}
|
||||||
.el-textarea .el-input__count {
|
.el-textarea .el-input__count {
|
||||||
color: #979db1;
|
color: #979db1;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@@ -533,13 +572,14 @@ audio::-webkit-media-controls-panel {
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
.el-input__inner {
|
.el-input__inner {
|
||||||
border: none;
|
//border: none;
|
||||||
background-color: transparent;
|
//background-color: transparent;
|
||||||
padding: 0 5px 0 15px;
|
padding: 0 5px 0 15px;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
.input-46 .el-input__inner {
|
.input-46 .el-input__inner {
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
height: 46px;
|
height: 38px;
|
||||||
}
|
}
|
||||||
.lh-form-item {
|
.lh-form-item {
|
||||||
.el-form-item__label {
|
.el-form-item__label {
|
||||||
@@ -596,3 +636,5 @@ audio::-webkit-media-controls-panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,97 +3,91 @@
|
|||||||
<el-container style="height: 100%;">
|
<el-container style="height: 100%;">
|
||||||
<el-header>
|
<el-header>
|
||||||
<div
|
<div
|
||||||
style="display: flex;align-items: center;margin-top: 15px;margin-left: 10px;gap: 10px;">
|
style="display: flex;align-items: center;margin-top: 15px;margin-left: 10px;gap: 10px;">
|
||||||
<img src="@/assets/xiaozhi-logo.png" alt="" style="width: 45px;height: 45px;" />
|
<img src="@/assets/xiaozhi-logo.png" alt="" style="width: 45px;height: 45px;"/>
|
||||||
<img src="@/assets/xiaozhi-ai.png" alt="" style="width: 70px;height: 13px;" />
|
<img src="@/assets/xiaozhi-ai.png" alt="" style="width: 70px;height: 13px;"/>
|
||||||
</div>
|
</div>
|
||||||
</el-header>
|
</el-header>
|
||||||
<el-main style="position: relative;">
|
<el-main style="position: relative;">
|
||||||
<div class="login-box">
|
<div class="login-box">
|
||||||
<div
|
<div
|
||||||
style="display: flex;align-items: center;gap: 20px;margin-bottom: 39px;padding: 0 30px;">
|
style="display: flex;align-items: center;gap: 20px;margin-bottom: 39px;padding: 0 30px;">
|
||||||
<img src="@/assets/login/hi.png" alt="" style="width: 34px;height: 34px;" />
|
<img src="@/assets/login/hi.png" alt="" style="width: 34px;height: 34px;"/>
|
||||||
<div class="login-text">登录</div>
|
<div class="login-text">登录</div>
|
||||||
<div class="login-welcome">
|
<div class="login-welcome">
|
||||||
WELCOME TO LOG IN</div>
|
WELCOME TO LOGIN
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
<div style="padding: 0 30px;">
|
||||||
<el-tab-pane label="用户名" name="username">
|
<div class="input-box">
|
||||||
<div style="padding: 0 30px;">
|
<img src="@/assets/login/username.png" alt="" class="input-icon"/>
|
||||||
<div class="input-box">
|
<el-input v-model="form.username" placeholder="请输入用户名"/>
|
||||||
<img src="@/assets/login/username.png" alt="" class="input-icon" />
|
</div>
|
||||||
<el-input v-model="form.username" placeholder="请输入用户名" />
|
<div class="input-box">
|
||||||
</div>
|
<img src="@/assets/login/password.png" alt="" class="input-icon"/>
|
||||||
<div class="input-box">
|
<el-input v-model="form.password" placeholder="请输入密码"/>
|
||||||
<img src="@/assets/login/password.png" alt="" class="input-icon" />
|
</div>
|
||||||
<el-input v-model="form.password" placeholder="请输入密码" />
|
<div class="input-box">
|
||||||
</div>
|
<img src="@/assets/login/shield.png" alt="" class="input-icon"/>
|
||||||
<div style="font-weight: 400;font-size: 14px;text-align: left;color: #5778ff;display: flex;justify-content: space-between;margin-top: 20px;">
|
<el-input v-model="form.captcha" placeholder="请输入验证码"/>
|
||||||
<div style="cursor: pointer;">新用户注册</div>
|
</div>
|
||||||
<div style="cursor: pointer;">忘记密码?</div>
|
<div
|
||||||
</div>
|
style="font-weight: 400;font-size: 14px;text-align: left;color: #5778ff;display: flex;justify-content: space-between;margin-top: 20px;">
|
||||||
</div>
|
<div style="cursor: pointer;">新用户注册</div>
|
||||||
</el-tab-pane>
|
</div>
|
||||||
<el-tab-pane label="手机号" name="phone">
|
</div>
|
||||||
<div style="padding: 0 30px;">
|
<div class="login-btn" @click="login">登陆</div>
|
||||||
<div class="input-box">
|
|
||||||
<img src="@/assets/login/phone.png" alt="" class="input-icon" />
|
|
||||||
<el-input v-model="form.phoneNumber" placeholder="请输入手机号" />
|
|
||||||
<div style="width: 120px;flex-shrink: 0;">
|
|
||||||
<el-dropdown>
|
|
||||||
<span class="el-dropdown-link">
|
|
||||||
+86 中国大陆<i class="el-icon-arrow-down el-icon--right"></i>
|
|
||||||
</span>
|
|
||||||
<el-dropdown-menu slot="dropdown">
|
|
||||||
<el-dropdown-item>黄金糕</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</el-dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="padding: 0 12px 0 30px;margin-top: 20px;" class="input-box">
|
|
||||||
<img src="@/assets/login/shield.png" alt="" class="input-icon" />
|
|
||||||
<el-input v-model="form.phoneCode" placeholder="请输入验证码" />
|
|
||||||
<div class="code-send">
|
|
||||||
发送
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
<div class="login-btn">登陆</div>
|
|
||||||
<div style="font-size: 14px;color: #979db1;">
|
<div style="font-size: 14px;color: #979db1;">
|
||||||
登录即同意<div style="display: inline-block;color: #5778FF;cursor: pointer;">《用户协议》</div>和
|
登录即同意
|
||||||
|
<div style="display: inline-block;color: #5778FF;cursor: pointer;">《用户协议》</div>
|
||||||
|
和
|
||||||
<div style="display: inline-block;color: #5778FF;cursor: pointer;">《隐私政策》</div>
|
<div style="display: inline-block;color: #5778FF;cursor: pointer;">《隐私政策》</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-main>
|
</el-main>
|
||||||
<el-footer>
|
<el-footer>
|
||||||
<div style="font-size: 12px;font-weight: 400;color: #979db1;">
|
<div style="font-size: 12px;font-weight: 400;color: #979db1;">
|
||||||
©2024 小智Al控制面板2.0粤ICP备2022121736号-2</div>
|
©2025 xiaozhi-esp32-server
|
||||||
|
</div>
|
||||||
</el-footer>
|
</el-footer>
|
||||||
</el-container>
|
</el-container>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// @ is an alias to /src
|
import Api from '@/apis/api'
|
||||||
|
import {isNull, showDanger, showSuccess, goToPage} from '@/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'login',
|
name: 'login',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeName: "username",
|
activeName: "username",
|
||||||
form:{
|
form: {
|
||||||
phoneNumber:'',
|
username: '',
|
||||||
phoneCode:'',
|
password: '',
|
||||||
username:'',
|
captcha: ''
|
||||||
password:''
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods:{
|
methods: {
|
||||||
handleClick(){
|
login() {
|
||||||
|
if (isNull(this.form.username)) {
|
||||||
|
showDanger('用户名不能为空')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isNull(this.form.password)) {
|
||||||
|
showDanger('密码不能为空')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isNull(this.form.captcha)) {
|
||||||
|
showDanger('验证码不能为空')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Api.user.login(this.form, ({data}) => {
|
||||||
|
showSuccess('登陆成功!')
|
||||||
|
goToPage('/home')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,12 +107,14 @@ export default {
|
|||||||
-o-background-size: cover;
|
-o-background-size: cover;
|
||||||
/* 兼容老版本Opera浏览器 */
|
/* 兼容老版本Opera浏览器 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-text {
|
.login-text {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
color: #3d4566;
|
color: #3d4566;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-welcome {
|
.login-welcome {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
@@ -127,6 +123,7 @@ export default {
|
|||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
margin-bottom: 7px;
|
margin-bottom: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-box {
|
.login-box {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@@ -138,17 +135,20 @@ export default {
|
|||||||
width: 450px;
|
width: 450px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-dropdown-link {
|
.el-dropdown-link {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
color: #979db1;
|
color: #979db1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-icon {
|
.input-icon {
|
||||||
width: 19px;
|
width: 19px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-btn {
|
.login-btn {
|
||||||
height: 35px;
|
height: 35px;
|
||||||
background: #5778ff;
|
background: #5778ff;
|
||||||
@@ -160,6 +160,7 @@ export default {
|
|||||||
line-height: 35px;
|
line-height: 35px;
|
||||||
margin: 35px 15px 15px;
|
margin: 35px 15px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-send {
|
.code-send {
|
||||||
width: 70px;
|
width: 70px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
@@ -172,6 +173,7 @@ export default {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-box {
|
.input-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
@@ -183,10 +185,12 @@ export default {
|
|||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep {
|
::v-deep {
|
||||||
.el-tabs__nav-wrap::after {
|
.el-tabs__nav-wrap::after {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tabs__nav-wrap::before {
|
.el-tabs__nav-wrap::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -197,18 +201,22 @@ export default {
|
|||||||
background-color: #e4e7ed;
|
background-color: #e4e7ed;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tabs__item {
|
.el-tabs__item {
|
||||||
height: 65px;
|
height: 65px;
|
||||||
line-height: 65px;
|
line-height: 65px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #3d4566;
|
color: #3d4566;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tabs__item.is-active {
|
.el-tabs__item.is-active {
|
||||||
color: #5778ff;
|
color: #5778ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tabs__nav-scroll {
|
.el-tabs__nav-scroll {
|
||||||
padding: 0 30px;
|
padding: 0 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-input__inner {
|
.el-input__inner {
|
||||||
border: none;
|
border: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 第一阶段:构建 Python 依赖
|
# 第一阶段:构建 Python 依赖
|
||||||
FROM kalicyh/poetry:v3.10_xiaozhi AS builder
|
FROM kalicyh/poetry:v3.10_xiaozhi AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /opt/xiaozhi-esp32-server
|
||||||
|
|
||||||
# 同时拷贝本地环境.venv
|
# 同时拷贝本地环境.venv
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
# 第一阶段:前端构建
|
|
||||||
FROM node:18 AS frontend-builder
|
|
||||||
|
|
||||||
WORKDIR /app/web
|
|
||||||
|
|
||||||
# 配置npm使用淘宝源
|
|
||||||
RUN npm config set registry https://registry.npmmirror.com
|
|
||||||
|
|
||||||
COPY web/package*.json ./
|
|
||||||
|
|
||||||
# 安装axios依赖
|
|
||||||
RUN npm install axios
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
COPY web .
|
|
||||||
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
# 第二阶段:构建Python依赖
|
|
||||||
FROM python:3.10-slim AS builder
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY requirements.txt .
|
|
||||||
|
|
||||||
# 优化apt安装
|
|
||||||
RUN pip install --no-cache-dir -r requirements.txt \
|
|
||||||
-i https://mirrors.aliyun.com/pypi/simple/
|
|
||||||
|
|
||||||
# 第三阶段:生产镜像
|
|
||||||
FROM python:3.10-slim
|
|
||||||
|
|
||||||
WORKDIR /opt/xiaozhi-esp32-server
|
|
||||||
|
|
||||||
# 优化apt安装
|
|
||||||
RUN echo "deb https://mirrors.aliyun.com/debian/ bookworm main contrib non-free non-free-firmware" > /etc/apt/sources.list && \
|
|
||||||
echo "deb https://mirrors.aliyun.com/debian/ bookworm-updates main contrib non-free non-free-firmware" >> /etc/apt/sources.list && \
|
|
||||||
apt-get update && \
|
|
||||||
apt-get install -y --no-install-recommends libopus0 ffmpeg && \
|
|
||||||
apt-get clean && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# 从构建阶段复制Python包和前端构建产物
|
|
||||||
COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
|
|
||||||
COPY --from=frontend-builder /app/web/dist /opt/xiaozhi-esp32-server/manager/static/webui
|
|
||||||
|
|
||||||
# 复制应用代码
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# 启动应用
|
|
||||||
CMD ["python", "app.py"]
|
|
||||||
@@ -158,6 +158,15 @@ LLM:
|
|||||||
base_url: http://homeassistant.local:8123
|
base_url: http://homeassistant.local:8123
|
||||||
agent_id: conversation.chatgpt
|
agent_id: conversation.chatgpt
|
||||||
api_key: 你的home assistant api访问令牌
|
api_key: 你的home assistant api访问令牌
|
||||||
|
FastgptLLM:
|
||||||
|
# 定义LLM API类型
|
||||||
|
type: fastgpt
|
||||||
|
# 如果使用fastgpt,配置文件里prompt(提示词)是无效的,需要在fastgpt控制台设置提示词
|
||||||
|
base_url: https://host/api/v1
|
||||||
|
api_key: fastgpt-xxx
|
||||||
|
variables:
|
||||||
|
k: "v"
|
||||||
|
k2: "v2"
|
||||||
TTS:
|
TTS:
|
||||||
# 当前支持的type为edge、doubao,可自行适配
|
# 当前支持的type为edge、doubao,可自行适配
|
||||||
EdgeTTS:
|
EdgeTTS:
|
||||||
@@ -296,8 +305,11 @@ TTS:
|
|||||||
type: aliyun
|
type: aliyun
|
||||||
output_file: tmp/
|
output_file: tmp/
|
||||||
appkey: 你的阿里云智能语音交互服务项目Appkey
|
appkey: 你的阿里云智能语音交互服务项目Appkey
|
||||||
token: 你的阿里云智能语音交互服务AccessToken
|
token: 你的阿里云智能语音交互服务AccessToken,临时的24小时,要长期用下方的access_key_id,access_key_secret
|
||||||
voice: xiaoyun
|
voice: xiaoyun
|
||||||
|
access_key_id: 你的阿里云账号access_key_id
|
||||||
|
access_key_secret: 你的阿里云账号access_key_secret
|
||||||
|
|
||||||
# 以下可不用设置,使用默认设置
|
# 以下可不用设置,使用默认设置
|
||||||
# format: wav
|
# format: wav
|
||||||
# sample_rate: 16000
|
# sample_rate: 16000
|
||||||
@@ -316,6 +328,36 @@ TTS:
|
|||||||
voice: "zh_female_wanwanxiaohe_moon_bigtts"
|
voice: "zh_female_wanwanxiaohe_moon_bigtts"
|
||||||
output_file: tmp/
|
output_file: tmp/
|
||||||
access_token: "你的302API密钥"
|
access_token: "你的302API密钥"
|
||||||
|
ACGNTTS:
|
||||||
|
#在线网址:https://acgn.ttson.cn/
|
||||||
|
#token购买:www.ttson.cn
|
||||||
|
#开发相关疑问请提交至3497689533@qq.com
|
||||||
|
#角色id获取地址:ctrl+f快速检索角色——网站管理者不允许发布,可询问网站管理者:1069379506
|
||||||
|
#各参数意义见开发文档:https://www.yuque.com/alexuh/skmti9/wm6taqislegb02gd?singleDoc#
|
||||||
|
type: ttson
|
||||||
|
token: your_token
|
||||||
|
voice_id: 1695
|
||||||
|
speed_factor: 1
|
||||||
|
pitch_factor: 0
|
||||||
|
volume_change_dB: 0
|
||||||
|
to_lang: ZH
|
||||||
|
url: https://u95167-bd74-2aef8085.westx.seetacloud.com:8443/flashsummary/tts?token=
|
||||||
|
format: mp3
|
||||||
|
output_file: tmp/
|
||||||
|
emotion: 1
|
||||||
|
OpenAITTS:
|
||||||
|
# openai官方文本转语音服务,可支持全球大多数语种
|
||||||
|
type: openai
|
||||||
|
api_key: 你的openai api key
|
||||||
|
# 国内需要使用代理
|
||||||
|
api_url: https://api.openai.com/v1/audio/speech
|
||||||
|
# 可选tts-1或tts-1-hd,tts-1速度更快tts-1-hd质量更好
|
||||||
|
model: tts-1
|
||||||
|
# 演讲者,可选alloy, echo, fable, onyx, nova, shimmer
|
||||||
|
voice: onyx
|
||||||
|
# 语速范围0.25-4.0
|
||||||
|
speed: 1
|
||||||
|
output_file: tmp/
|
||||||
# 模块测试配置
|
# 模块测试配置
|
||||||
module_test:
|
module_test:
|
||||||
test_sentences: # 自定义测试语句
|
test_sentences: # 自定义测试语句
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import json
|
||||||
|
from config.logger import setup_logging
|
||||||
|
import requests
|
||||||
|
from core.providers.llm.base import LLMProviderBase
|
||||||
|
|
||||||
|
TAG = __name__
|
||||||
|
logger = setup_logging()
|
||||||
|
|
||||||
|
|
||||||
|
class LLMProvider(LLMProviderBase):
|
||||||
|
def __init__(self, config):
|
||||||
|
self.api_key = config["api_key"]
|
||||||
|
self.base_url = config.get("base_url")
|
||||||
|
self.detail = config.get("detail", False)
|
||||||
|
self.variables = config.get("variables", {})
|
||||||
|
|
||||||
|
def response(self, session_id, dialogue):
|
||||||
|
try:
|
||||||
|
# 取最后一条用户消息
|
||||||
|
last_msg = next(m for m in reversed(dialogue) if m["role"] == "user")
|
||||||
|
|
||||||
|
# 发起流式请求
|
||||||
|
with requests.post(
|
||||||
|
f"{self.base_url}/chat/completions",
|
||||||
|
headers={"Authorization": f"Bearer {self.api_key}"},
|
||||||
|
json={
|
||||||
|
"stream": True,
|
||||||
|
"chatId": session_id,
|
||||||
|
"detail": self.detail,
|
||||||
|
"variables": self.variables,
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": last_msg["content"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
stream=True
|
||||||
|
) as r:
|
||||||
|
for line in r.iter_lines():
|
||||||
|
if line:
|
||||||
|
try:
|
||||||
|
if line.startswith(b'data: '):
|
||||||
|
if line[6:].decode('utf-8') == '[DONE]':
|
||||||
|
break
|
||||||
|
|
||||||
|
data = json.loads(line[6:])
|
||||||
|
if 'choices' in data and len(data['choices']) > 0:
|
||||||
|
delta = data['choices'][0].get('delta', {})
|
||||||
|
if delta and 'content' in delta and delta['content'] is not None:
|
||||||
|
content = delta['content']
|
||||||
|
if '<think>' in content:
|
||||||
|
continue
|
||||||
|
if '</think>' in content:
|
||||||
|
continue
|
||||||
|
yield content
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
continue
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"Error in response generation: {e}")
|
||||||
|
yield "【服务响应异常】"
|
||||||
@@ -1,25 +1,24 @@
|
|||||||
from config.logger import setup_logging
|
|
||||||
import google.generativeai as genai
|
import google.generativeai as genai
|
||||||
|
from core.utils.util import check_model_key
|
||||||
from core.providers.llm.base import LLMProviderBase
|
from core.providers.llm.base import LLMProviderBase
|
||||||
|
|
||||||
TAG = __name__
|
|
||||||
logger = setup_logging()
|
|
||||||
|
|
||||||
class LLMProvider(LLMProviderBase):
|
class LLMProvider(LLMProviderBase):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
"""初始化Gemini LLM Provider"""
|
"""初始化Gemini LLM Provider"""
|
||||||
self.model_name = config.get("model_name", "gemini-1.5-pro")
|
self.model_name = config.get("model_name", "gemini-1.5-pro")
|
||||||
self.api_key = config.get("api_key")
|
self.api_key = config.get("api_key")
|
||||||
|
|
||||||
if not self.api_key or "你" in self.api_key:
|
have_key = check_model_key("LLM", self.api_key)
|
||||||
logger.bind(tag=TAG).error("你还没配置Gemini LLM的密钥,请在配置文件中配置密钥,否则无法正常工作")
|
|
||||||
|
if not have_key:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 初始化Gemini客户端
|
# 初始化Gemini客户端
|
||||||
genai.configure(api_key=self.api_key)
|
genai.configure(api_key=self.api_key)
|
||||||
self.model = genai.GenerativeModel(self.model_name)
|
self.model = genai.GenerativeModel(self.model_name)
|
||||||
|
|
||||||
# 设置生成参数
|
# 设置生成参数
|
||||||
self.generation_config = {
|
self.generation_config = {
|
||||||
"temperature": 0.7,
|
"temperature": 0.7,
|
||||||
@@ -55,7 +54,7 @@ class LLMProvider(LLMProviderBase):
|
|||||||
|
|
||||||
# 创建新的聊天会话
|
# 创建新的聊天会话
|
||||||
chat = self.model.start_chat(history=chat_history)
|
chat = self.model.start_chat(history=chat_history)
|
||||||
|
|
||||||
# 发送消息并获取流式响应
|
# 发送消息并获取流式响应
|
||||||
response = chat.send_message(
|
response = chat.send_message(
|
||||||
current_msg,
|
current_msg,
|
||||||
@@ -71,7 +70,7 @@ class LLMProvider(LLMProviderBase):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = str(e)
|
error_msg = str(e)
|
||||||
logger.bind(tag=TAG).error(f"Gemini响应生成错误: {error_msg}")
|
logger.bind(tag=TAG).error(f"Gemini响应生成错误: {error_msg}")
|
||||||
|
|
||||||
# 针对不同错误返回友好提示
|
# 针对不同错误返回友好提示
|
||||||
if "Rate limit" in error_msg:
|
if "Rate limit" in error_msg:
|
||||||
yield "【Gemini服务请求太频繁,请稍后再试】"
|
yield "【Gemini服务请求太频繁,请稍后再试】"
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
from config.logger import setup_logging
|
|
||||||
import openai
|
import openai
|
||||||
|
from core.utils.util import check_model_key
|
||||||
from core.providers.llm.base import LLMProviderBase
|
from core.providers.llm.base import LLMProviderBase
|
||||||
|
|
||||||
TAG = __name__
|
|
||||||
logger = setup_logging()
|
|
||||||
|
|
||||||
|
|
||||||
class LLMProvider(LLMProviderBase):
|
class LLMProvider(LLMProviderBase):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
@@ -14,8 +11,7 @@ class LLMProvider(LLMProviderBase):
|
|||||||
self.base_url = config.get("base_url")
|
self.base_url = config.get("base_url")
|
||||||
else:
|
else:
|
||||||
self.base_url = config.get("url")
|
self.base_url = config.get("url")
|
||||||
if "你" in self.api_key:
|
check_model_key("LLM", self.api_key)
|
||||||
logger.bind(tag=TAG).error("你还没配置LLM的密钥,请在配置文件中配置密钥,否则无法正常工作")
|
|
||||||
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url)
|
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url)
|
||||||
|
|
||||||
def response(self, session_id, dialogue):
|
def response(self, session_id, dialogue):
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import traceback
|
||||||
|
|
||||||
from ..base import MemoryProviderBase, logger
|
from ..base import MemoryProviderBase, logger
|
||||||
from mem0 import MemoryClient
|
from mem0 import MemoryClient
|
||||||
|
from core.utils.util import check_model_key
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
|
|
||||||
@@ -8,13 +11,19 @@ class MemoryProvider(MemoryProviderBase):
|
|||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
self.api_key = config.get("api_key", "")
|
self.api_key = config.get("api_key", "")
|
||||||
self.api_version = config.get("api_version", "v1.1")
|
self.api_version = config.get("api_version", "v1.1")
|
||||||
if len(self.api_key) == 0 or "你" in self.api_key:
|
have_key = check_model_key("Mem0ai", self.api_key)
|
||||||
logger.bind(tag=TAG).error("你还没配置Mem0ai的密钥,请在配置文件中配置密钥,否则无法提供记忆服务")
|
if not have_key :
|
||||||
self.use_mem0 = False
|
self.use_mem0 = False
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.use_mem0 = True
|
self.use_mem0 = True
|
||||||
self.client = MemoryClient(api_key=self.api_key)
|
try:
|
||||||
|
self.client = MemoryClient(api_key=self.api_key)
|
||||||
|
logger.bind(tag=TAG).info("成功连接到 Mem0ai 服务")
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"连接到 Mem0ai 服务时发生错误: {str(e)}")
|
||||||
|
logger.bind(tag=TAG).error(f"详细错误: {traceback.format_exc()}")
|
||||||
|
self.use_mem0 = False
|
||||||
|
|
||||||
async def save_memory(self, msgs):
|
async def save_memory(self, msgs):
|
||||||
if not self.use_mem0:
|
if not self.use_mem0:
|
||||||
|
|||||||
@@ -1,19 +1,94 @@
|
|||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
import json
|
import json
|
||||||
|
import hmac
|
||||||
|
import hashlib
|
||||||
|
import base64
|
||||||
import requests
|
import requests
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from core.providers.tts.base import TTSProviderBase
|
from core.providers.tts.base import TTSProviderBase
|
||||||
|
|
||||||
import http.client
|
import http.client
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
from urllib import parse
|
||||||
|
class AccessToken:
|
||||||
|
@staticmethod
|
||||||
|
def _encode_text(text):
|
||||||
|
encoded_text = parse.quote_plus(text)
|
||||||
|
return encoded_text.replace('+', '%20').replace('*', '%2A').replace('%7E', '~')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _encode_dict(dic):
|
||||||
|
keys = dic.keys()
|
||||||
|
dic_sorted = [(key, dic[key]) for key in sorted(keys)]
|
||||||
|
encoded_text = parse.urlencode(dic_sorted)
|
||||||
|
return encoded_text.replace('+', '%20').replace('*', '%2A').replace('%7E', '~')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_token(access_key_id, access_key_secret):
|
||||||
|
parameters = {'AccessKeyId': access_key_id,
|
||||||
|
'Action': 'CreateToken',
|
||||||
|
'Format': 'JSON',
|
||||||
|
'RegionId': 'cn-shanghai',
|
||||||
|
'SignatureMethod': 'HMAC-SHA1',
|
||||||
|
'SignatureNonce': str(uuid.uuid1()),
|
||||||
|
'SignatureVersion': '1.0',
|
||||||
|
'Timestamp': time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||||
|
'Version': '2019-02-28'}
|
||||||
|
# 构造规范化的请求字符串
|
||||||
|
query_string = AccessToken._encode_dict(parameters)
|
||||||
|
print('规范化的请求字符串: %s' % query_string)
|
||||||
|
# 构造待签名字符串
|
||||||
|
string_to_sign = 'GET' + '&' + AccessToken._encode_text('/') + '&' + AccessToken._encode_text(query_string)
|
||||||
|
print('待签名的字符串: %s' % string_to_sign)
|
||||||
|
# 计算签名
|
||||||
|
secreted_string = hmac.new(bytes(access_key_secret + '&', encoding='utf-8'),
|
||||||
|
bytes(string_to_sign, encoding='utf-8'),
|
||||||
|
hashlib.sha1).digest()
|
||||||
|
signature = base64.b64encode(secreted_string)
|
||||||
|
print('签名: %s' % signature)
|
||||||
|
# 进行URL编码
|
||||||
|
signature = AccessToken._encode_text(signature)
|
||||||
|
print('URL编码后的签名: %s' % signature)
|
||||||
|
# 调用服务
|
||||||
|
full_url = 'http://nls-meta.cn-shanghai.aliyuncs.com/?Signature=%s&%s' % (signature, query_string)
|
||||||
|
print('url: %s' % full_url)
|
||||||
|
# 提交HTTP GET请求
|
||||||
|
response = requests.get(full_url)
|
||||||
|
if response.ok:
|
||||||
|
root_obj = response.json()
|
||||||
|
key = 'Token'
|
||||||
|
if key in root_obj:
|
||||||
|
token = root_obj[key]['Id']
|
||||||
|
expire_time = root_obj[key]['ExpireTime']
|
||||||
|
return token, expire_time
|
||||||
|
print(response.text)
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
class TTSProvider(TTSProviderBase):
|
class TTSProvider(TTSProviderBase):
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, config, delete_audio_file):
|
def __init__(self, config, delete_audio_file):
|
||||||
super().__init__(config, delete_audio_file)
|
super().__init__(config, delete_audio_file)
|
||||||
|
|
||||||
|
# 新增空值判断逻辑
|
||||||
|
access_key_id = config.get("access_key_id")
|
||||||
|
access_key_secret = config.get("access_key_secret")
|
||||||
|
if access_key_id and access_key_secret:
|
||||||
|
# 使用密钥对生成临时token
|
||||||
|
token, expire_time = AccessToken.create_token(access_key_id, access_key_secret)
|
||||||
|
else:
|
||||||
|
# 直接使用预生成的长期token
|
||||||
|
token = config.get("token")
|
||||||
|
expire_time = None
|
||||||
|
|
||||||
|
print('token: %s, expire time(s): %s' % (token, expire_time))
|
||||||
|
|
||||||
self.appkey = config.get("appkey")
|
self.appkey = config.get("appkey")
|
||||||
self.token = config.get("token")
|
self.token = token
|
||||||
self.format = config.get("format", "wav")
|
self.format = config.get("format", "wav")
|
||||||
self.sample_rate = config.get("sample_rate", 16000)
|
self.sample_rate = config.get("sample_rate", 16000)
|
||||||
self.voice = config.get("voice", "xiaoyun")
|
self.voice = config.get("voice", "xiaoyun")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import json
|
|||||||
import base64
|
import base64
|
||||||
import requests
|
import requests
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from core.utils.util import check_model_key
|
||||||
from core.providers.tts.base import TTSProviderBase
|
from core.providers.tts.base import TTSProviderBase
|
||||||
|
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.api_url = config.get("api_url")
|
self.api_url = config.get("api_url")
|
||||||
self.authorization = config.get("authorization")
|
self.authorization = config.get("authorization")
|
||||||
self.header = {"Authorization": f"{self.authorization}{self.access_token}"}
|
self.header = {"Authorization": f"{self.authorization}{self.access_token}"}
|
||||||
|
check_model_key("TTS", self.access_token)
|
||||||
|
|
||||||
def generate_filename(self, extension=".wav"):
|
def generate_filename(self, extension=".wav"):
|
||||||
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}")
|
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}")
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
@@ -9,6 +8,7 @@ from pydantic import BaseModel, Field, conint, model_validator
|
|||||||
from typing_extensions import Annotated
|
from typing_extensions import Annotated
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
from core.utils.util import check_model_key
|
||||||
from core.providers.tts.base import TTSProviderBase
|
from core.providers.tts.base import TTSProviderBase
|
||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ class ServeReferenceAudio(BaseModel):
|
|||||||
def decode_audio(cls, values):
|
def decode_audio(cls, values):
|
||||||
audio = values.get("audio")
|
audio = values.get("audio")
|
||||||
if (
|
if (
|
||||||
isinstance(audio, str) and len(audio) > 255
|
isinstance(audio, str) and len(audio) > 255
|
||||||
): # Check if audio is a string (Base64)
|
): # Check if audio is a string (Base64)
|
||||||
try:
|
try:
|
||||||
values["audio"] = base64.b64decode(audio)
|
values["audio"] = base64.b64decode(audio)
|
||||||
@@ -36,6 +36,7 @@ class ServeReferenceAudio(BaseModel):
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"ServeReferenceAudio(text={self.text!r}, audio_size={len(self.audio)})"
|
return f"ServeReferenceAudio(text={self.text!r}, audio_size={len(self.audio)})"
|
||||||
|
|
||||||
|
|
||||||
class ServeTTSRequest(BaseModel):
|
class ServeTTSRequest(BaseModel):
|
||||||
text: str
|
text: str
|
||||||
chunk_length: Annotated[int, conint(ge=100, le=300, strict=True)] = 200
|
chunk_length: Annotated[int, conint(ge=100, le=300, strict=True)] = 200
|
||||||
@@ -70,6 +71,7 @@ def audio_to_bytes(file_path):
|
|||||||
wav = wav_file.read()
|
wav = wav_file.read()
|
||||||
return wav
|
return wav
|
||||||
|
|
||||||
|
|
||||||
def read_ref_text(ref_text):
|
def read_ref_text(ref_text):
|
||||||
path = Path(ref_text)
|
path = Path(ref_text)
|
||||||
if path.exists() and path.is_file():
|
if path.exists() and path.is_file():
|
||||||
@@ -77,31 +79,32 @@ def read_ref_text(ref_text):
|
|||||||
return file.read()
|
return file.read()
|
||||||
return ref_text
|
return ref_text
|
||||||
|
|
||||||
|
|
||||||
class TTSProvider(TTSProviderBase):
|
class TTSProvider(TTSProviderBase):
|
||||||
|
|
||||||
def __init__(self, config, delete_audio_file):
|
def __init__(self, config, delete_audio_file):
|
||||||
super().__init__(config, delete_audio_file)
|
super().__init__(config, delete_audio_file)
|
||||||
|
|
||||||
self.reference_id = config.get("reference_id")
|
self.reference_id = config.get("reference_id")
|
||||||
self.reference_audio = config.get("reference_audio",[])
|
self.reference_audio = config.get("reference_audio", [])
|
||||||
self.reference_text = config.get("reference_text",[])
|
self.reference_text = config.get("reference_text", [])
|
||||||
self.format = config.get("format","wav")
|
self.format = config.get("format", "wav")
|
||||||
self.channels = config.get("channels",1)
|
self.channels = config.get("channels", 1)
|
||||||
self.rate = config.get("rate",44100)
|
self.rate = config.get("rate", 44100)
|
||||||
self.api_key = config.get("api_key","YOUR_API_KEY")
|
self.api_key = config.get("api_key", "YOUR_API_KEY")
|
||||||
if "你" in self.api_key:
|
have_key = check_model_key("FishSpeech TTS", self.api_key)
|
||||||
logger.bind(tag=TAG).error("你还没配置FishSpeech TTS的密钥,请在配置文件中配置密钥,否则无法正常工作")
|
if not have_key:
|
||||||
return
|
return
|
||||||
self.normalize = config.get("normalize",True)
|
self.normalize = config.get("normalize", True)
|
||||||
self.max_new_tokens = config.get("max_new_tokens",1024)
|
self.max_new_tokens = config.get("max_new_tokens", 1024)
|
||||||
self.chunk_length = config.get("chunk_length",200)
|
self.chunk_length = config.get("chunk_length", 200)
|
||||||
self.top_p = config.get("top_p",0.7)
|
self.top_p = config.get("top_p", 0.7)
|
||||||
self.repetition_penalty = config.get("repetition_penalty",1.2)
|
self.repetition_penalty = config.get("repetition_penalty", 1.2)
|
||||||
self.temperature = config.get("temperature",0.7)
|
self.temperature = config.get("temperature", 0.7)
|
||||||
self.streaming = config.get("streaming",False)
|
self.streaming = config.get("streaming", False)
|
||||||
self.use_memory_cache = config.get("use_memory_cache","on")
|
self.use_memory_cache = config.get("use_memory_cache", "on")
|
||||||
self.seed = config.get("seed")
|
self.seed = config.get("seed")
|
||||||
self.api_url = config.get("api_url","http://127.0.0.1:8080/v1/tts")
|
self.api_url = config.get("api_url", "http://127.0.0.1:8080/v1/tts")
|
||||||
|
|
||||||
def generate_filename(self, extension=".wav"):
|
def generate_filename(self, extension=".wav"):
|
||||||
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}")
|
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}")
|
||||||
@@ -148,11 +151,9 @@ class TTSProvider(TTSProviderBase):
|
|||||||
|
|
||||||
with open(output_file, "wb") as audio_file:
|
with open(output_file, "wb") as audio_file:
|
||||||
audio_file.write(audio_content)
|
audio_file.write(audio_content)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"Request failed with status code {response.status_code}")
|
print(f"Request failed with status code {response.status_code}")
|
||||||
print(response.json())
|
print(response.json())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import requests
|
||||||
|
from datetime import datetime
|
||||||
|
from core.utils.util import check_model_key
|
||||||
|
from core.providers.tts.base import TTSProviderBase
|
||||||
|
|
||||||
|
class TTSProvider(TTSProviderBase):
|
||||||
|
def __init__(self, config, delete_audio_file):
|
||||||
|
super().__init__(config, delete_audio_file)
|
||||||
|
self.api_key = config.get("api_key")
|
||||||
|
self.api_url = config.get("api_url", "https://api.openai.com/v1/audio/speech")
|
||||||
|
self.model = config.get("model", "tts-1")
|
||||||
|
self.voice = config.get("voice", "alloy")
|
||||||
|
self.response_format = "wav"
|
||||||
|
self.speed = config.get("speed", 1.0)
|
||||||
|
self.output_file = config.get("output_file", "tmp/")
|
||||||
|
check_model_key("TTS", self.api_key)
|
||||||
|
|
||||||
|
def generate_filename(self, extension=".wav"):
|
||||||
|
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}")
|
||||||
|
|
||||||
|
async def text_to_speak(self, text, output_file):
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"model": self.model,
|
||||||
|
"input": text,
|
||||||
|
"voice": self.voice,
|
||||||
|
"response_format": "wav",
|
||||||
|
"speed": self.speed
|
||||||
|
}
|
||||||
|
response = requests.post(self.api_url, json=data, headers=headers)
|
||||||
|
if response.status_code == 200:
|
||||||
|
with open(output_file, "wb") as audio_file:
|
||||||
|
audio_file.write(response.content)
|
||||||
|
else:
|
||||||
|
raise Exception(f"OpenAI TTS请求失败: {response.status_code} - {response.text}")
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import shutil
|
||||||
|
from datetime import datetime
|
||||||
|
from core.providers.tts.base import TTSProviderBase
|
||||||
|
|
||||||
|
|
||||||
|
class TTSProvider(TTSProviderBase):
|
||||||
|
def __init__(self, config, delete_audio_file):
|
||||||
|
super().__init__(config, delete_audio_file)
|
||||||
|
self.url = config.get("url", "https://u95167-bd74-2aef8085.westx.seetacloud.com:8443/flashsummary/tts?token=")
|
||||||
|
self.voice_id = config.get("voice_id", 1695)
|
||||||
|
self.token = config.get("token")
|
||||||
|
self.to_lang = config.get("to_lang")
|
||||||
|
self.volume_change_dB = config.get("volume_change_dB", 0)
|
||||||
|
self.speed_factor = config.get("speed_factor", 1)
|
||||||
|
self.stream = config.get("stream", False)
|
||||||
|
self.output_file = config.get("output_file")
|
||||||
|
self.pitch_factor = config.get("pitch_factor", 0)
|
||||||
|
self.format = config.get("format", "mp3")
|
||||||
|
self.emotion = config.get("emotion", 1)
|
||||||
|
self.header = {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_filename(self, extension=".mp3"):
|
||||||
|
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}")
|
||||||
|
|
||||||
|
async def text_to_speak(self, text, output_file):
|
||||||
|
url = f'{self.url}{self.token}'
|
||||||
|
result = "firefly"
|
||||||
|
payload = json.dumps({
|
||||||
|
"to_lang": self.to_lang,
|
||||||
|
"text": text,
|
||||||
|
"emotion": self.emotion,
|
||||||
|
"format": self.format,
|
||||||
|
"volume_change_dB": self.volume_change_dB,
|
||||||
|
"voice_id": self.voice_id,
|
||||||
|
"pitch_factor": self.pitch_factor,
|
||||||
|
"speed_factor": self.speed_factor,
|
||||||
|
"token": self.token
|
||||||
|
})
|
||||||
|
|
||||||
|
resp = requests.request("POST", url, data=payload)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
return None
|
||||||
|
resp_json = resp.json()
|
||||||
|
try:
|
||||||
|
result = resp_json['url'] + ':' + str(
|
||||||
|
resp_json[
|
||||||
|
'port']) + '/flashsummary/retrieveFileData?stream=True&token=' + self.token + '&voice_audio_path=' + \
|
||||||
|
resp_json['voice_path']
|
||||||
|
except Exception as e:
|
||||||
|
print("error:", e)
|
||||||
|
|
||||||
|
audio_content = requests.get(result)
|
||||||
|
with open(output_file, "wb") as f:
|
||||||
|
f.write(audio_content.content)
|
||||||
|
return True
|
||||||
|
voice_path = resp_json.get("voice_path")
|
||||||
|
des_path = output_file
|
||||||
|
shutil.move(voice_path, des_path)
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import json
|
import json
|
||||||
import yaml
|
import yaml
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
def get_project_dir():
|
def get_project_dir():
|
||||||
@@ -87,35 +87,13 @@ def remove_punctuation_and_length(text):
|
|||||||
return 0, ""
|
return 0, ""
|
||||||
return len(result), result
|
return len(result), result
|
||||||
|
|
||||||
|
def check_model_key(modelType, modelKey):
|
||||||
def check_password(password):
|
if "你" in modelKey:
|
||||||
"""
|
logging.error("你还没配置" + modelType + "的密钥,请在配置文件中配置密钥,否则无法正常工作")
|
||||||
检查密码是否满足以下条件:
|
|
||||||
1. 密码长度大于八位。
|
|
||||||
2. 密码包含英文和数字。
|
|
||||||
3. 密码不能包含“xiaozhi”字符。
|
|
||||||
|
|
||||||
:param password: 要检查的密码
|
|
||||||
:return: 如果密码满足条件,则返回True;否则返回False。
|
|
||||||
"""
|
|
||||||
# 检查密码长度
|
|
||||||
if len(password) < 8:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 检查是否包含英文字符和数字
|
|
||||||
if not re.search(r'[A-Za-z]', password) or not re.search(r'[0-9]', password):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 检查是否包含“xiaozhi”字符
|
|
||||||
if "xiaozhi" in password:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if "1234" in password:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 如果满足所有条件,则返回True
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def check_ffmpeg_installed():
|
def check_ffmpeg_installed():
|
||||||
ffmpeg_installed = False
|
ffmpeg_installed = False
|
||||||
try:
|
try:
|
||||||
@@ -140,4 +118,4 @@ def check_ffmpeg_installed():
|
|||||||
error_msg += "\n建议您:\n"
|
error_msg += "\n建议您:\n"
|
||||||
error_msg += "1、按照项目的安装文档,正确进入conda环境\n"
|
error_msg += "1、按照项目的安装文档,正确进入conda环境\n"
|
||||||
error_msg += "2、查阅安装文档,如何在conda环境中安装ffmpeg\n"
|
error_msg += "2、查阅安装文档,如何在conda环境中安装ffmpeg\n"
|
||||||
raise ValueError(error_msg)
|
raise ValueError(error_msg)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
security_opt:
|
security_opt:
|
||||||
- seccomp:unconfined
|
- seccomp:unconfined
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
ports:
|
ports:
|
||||||
# ws服务端
|
# ws服务端
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
@@ -13,6 +15,6 @@ services:
|
|||||||
- "8002:8002"
|
- "8002:8002"
|
||||||
volumes:
|
volumes:
|
||||||
# 配置文件目录
|
# 配置文件目录
|
||||||
- ./data:/app/data
|
- ./data:/opt/xiaozhi-esp32-server/data
|
||||||
# 模型文件挂接,很重要
|
# 模型文件挂接,很重要
|
||||||
- ./models/SenseVoiceSmall/model.pt:/app/models/SenseVoiceSmall/model.pt
|
- ./models/SenseVoiceSmall/model.pt:/opt/xiaozhi-esp32-server/models/SenseVoiceSmall/model.pt
|
||||||
Reference in New Issue
Block a user