mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-26 17:13:54 +08:00
merge main
This commit is contained in:
@@ -232,14 +232,9 @@ server:
|
|||||||
|
|
||||||
本项目支持以下三种部署方式,您可根据实际需求选择。
|
本项目支持以下三种部署方式,您可根据实际需求选择。
|
||||||
|
|
||||||
本项目的文档主要是`文字版本`的教程,如果你想要`视频版本`
|
|
||||||
的教程,您可以学习一下[这个大佬的手把手教程](https://www.bilibili.com/video/BV1gePuejEvT)。
|
|
||||||
|
|
||||||
如果你能把`文字版本的教程`和`视频版本的教程`结合起来一起看,可以让你更快上手。
|
|
||||||
|
|
||||||
1. [Docker 快速部署](./docs/Deployment.md)
|
1. [Docker 快速部署](./docs/Deployment.md)
|
||||||
|
|
||||||
适合快速体验的普通用户,不需过多环境配置。缺点是,拉取镜像有点慢。
|
适合快速体验的普通用户,不需过多环境配置。缺点是,拉取镜像有点慢。视频教程可参考[美女大佬教你Docker部署](https://www.bilibili.com/video/BV1RNQnYDE5t)
|
||||||
|
|
||||||
2. [借助 Docker 环境运行部署](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E5%80%9F%E5%8A%A9docker%E7%8E%AF%E5%A2%83%E8%BF%90%E8%A1%8C%E9%83%A8%E7%BD%B2)
|
2. [借助 Docker 环境运行部署](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E5%80%9F%E5%8A%A9docker%E7%8E%AF%E5%A2%83%E8%BF%90%E8%A1%8C%E9%83%A8%E7%BD%B2)
|
||||||
|
|
||||||
@@ -249,7 +244,7 @@ server:
|
|||||||
|
|
||||||
适合熟悉`Conda` 环境或希望从零搭建运行环境的用户。
|
适合熟悉`Conda` 环境或希望从零搭建运行环境的用户。
|
||||||
|
|
||||||
对于对响应速度要求较高的场景,推荐使用本地源码运行方式以降低额外开销。
|
对于对响应速度要求较高的场景,推荐使用本地源码运行方式以降低额外开销。视频教程可参考[帅哥大佬教你源码部署](https://www.bilibili.com/video/BV1GvQWYZEd2)
|
||||||
|
|
||||||
### 二、[固件编译](./docs/firmware-build.md)
|
### 二、[固件编译](./docs/firmware-build.md)
|
||||||
|
|
||||||
@@ -378,4 +373,4 @@ VAD:
|
|||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
|
||||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
|
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -14,11 +14,11 @@
|
|||||||
|
|
||||||
## 加入我们
|
## 加入我们
|
||||||
|
|
||||||
我们热忱欢迎志同道合的朋友加入,共同为项目贡献力量。参与方式如下:
|
我们热忱欢迎志同道合的朋友加入,共同为项目贡献力量。您可在[这个链接](https://github.com/users/xinnan-tech/projects/3)查看我们近期要实现的功能,功能列表中还没指派相关人员处理的,正是急需您的参与。参与方式如下:
|
||||||
|
|
||||||
### 1、成为普通贡献者
|
### 1、成为普通贡献者
|
||||||
|
|
||||||
Fork 项目,提交 PR,由开发者审核后合入主分支。
|
Fork 项目,,提交 PR,由开发者审核后合入主分支。
|
||||||
|
|
||||||
### 2、成为开发者
|
### 2、成为开发者
|
||||||
|
|
||||||
@@ -32,20 +32,9 @@ Fork 项目,提交 PR,由开发者审核后合入主分支。
|
|||||||
2. **提交 PR 审核**
|
2. **提交 PR 审核**
|
||||||
功能开发完成后,请在 GitHub 上提交 PR,由其他开发者审核,审核通过后合并入主分支。
|
功能开发完成后,请在 GitHub 上提交 PR,由其他开发者审核,审核通过后合并入主分支。
|
||||||
|
|
||||||
## 功能点的来源
|
|
||||||
|
|
||||||
- **创新的点子与功能**
|
|
||||||
你的每一个灵感都可能成为项目的突破点,欢迎大胆提出前沿的创意与功能建议。
|
|
||||||
|
|
||||||
- **代码优化**
|
|
||||||
如果你发现代码中存在改进的空间,欢迎提交优化方案,让我们的代码更加高效、易读。
|
|
||||||
|
|
||||||
- **解决 Issues 问题**
|
|
||||||
Issues 中有很多问题需要解决,我们一同完善、打造产品。
|
|
||||||
|
|
||||||
亲爱的开发者们,
|
亲爱的开发者们,
|
||||||
每一次提交、每一份建议,都是对我们共同梦想的加持。每一代人有每一代人的使命,让我们共同书写智能生活的新篇章!
|
每一次提交、每一份建议,都是对我们共同梦想的加持。每一代人有每一代人的使命,让我们共同书写智能生活的新篇章!
|
||||||
|
|
||||||
期待与你携手前行,共创未来。
|
期待与你携手前行,共创未来。
|
||||||
|
|
||||||
John,2025.3.11,广州
|
John,2025.3.11,广州
|
||||||
|
|||||||
@@ -42,4 +42,5 @@ public interface ErrorCode {
|
|||||||
int PASSWORD_LENGTH_ERROR = 10030;
|
int PASSWORD_LENGTH_ERROR = 10030;
|
||||||
int PASSWORD_WEAK_ERROR = 10031;
|
int PASSWORD_WEAK_ERROR = 10031;
|
||||||
int DEL_MYSELF_ERROR = 10032;
|
int DEL_MYSELF_ERROR = 10032;
|
||||||
|
int DEVICE_CAPTCHA_ERROR = 10033;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,60 +21,9 @@ public class RedisKeys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录用户Key
|
* 未注册设备验证码Key
|
||||||
*/
|
*/
|
||||||
public static String getSecurityUserKey(Long id) {
|
public static String getDeviceCaptchaKey(String captcha) {
|
||||||
return "sys:security:user:" + id;
|
return "sys:device:captcha:" + captcha;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统日志Key
|
|
||||||
*/
|
|
||||||
public static String getSysLogKey() {
|
|
||||||
return "sys:log";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统资源Key
|
|
||||||
*/
|
|
||||||
public static String getSysResourceKey() {
|
|
||||||
return "sys:resource";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户菜单导航Key
|
|
||||||
*/
|
|
||||||
public static String getUserMenuNavKey(Long userId) {
|
|
||||||
return "sys:user:nav:" + userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户权限标识Key
|
|
||||||
*/
|
|
||||||
public static String getUserPermissionsKey(Long userId) {
|
|
||||||
return "sys:user:permissions:" + userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户登陆错误次数
|
|
||||||
*
|
|
||||||
* @param username
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String getUserLoginErrorCountKey(String username) {
|
|
||||||
return "sys:user:login:error:" + username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getUserInfoKey(Long userId) {
|
|
||||||
return "sys:user:" + userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getDataScopeListKey(Long userId) {
|
|
||||||
return "sys:user:data:scope:" + userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getSysUserName(Long id) {
|
|
||||||
return "sys:user:name" + id;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
package xiaozhi.modules.device.controller;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameters;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
|
import xiaozhi.common.page.PageData;
|
||||||
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
|
import xiaozhi.common.user.UserDetail;
|
||||||
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
|
import xiaozhi.common.utils.Result;
|
||||||
|
import xiaozhi.modules.device.dto.DeviceHeaderDTO;
|
||||||
|
import xiaozhi.modules.device.dto.DeviceUnBindDTO;
|
||||||
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/device")
|
||||||
|
@Tag(name = "设备管理")
|
||||||
|
|
||||||
|
public class DeviceController {
|
||||||
|
private final DeviceService deviceService;
|
||||||
|
private final RedisUtils redisUtils;
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("/bind/{deviceCode}")
|
||||||
|
@Operation(summary = "绑定设备")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<DeviceEntity> bindDevice(@PathVariable String deviceCode) {
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
|
||||||
|
String deviceHeaders = (String) redisUtils.get(RedisKeys.getDeviceCaptchaKey(deviceCode));
|
||||||
|
if (StringUtils.isBlank(deviceHeaders)) {
|
||||||
|
return new Result<DeviceEntity>().error(ErrorCode.DEVICE_CAPTCHA_ERROR);
|
||||||
|
}
|
||||||
|
DeviceHeaderDTO deviceHeader = JsonUtils.parseObject(deviceHeaders.getBytes(), DeviceHeaderDTO.class);
|
||||||
|
DeviceEntity device = deviceService.bindDevice(user.getId(), deviceHeader);
|
||||||
|
return new Result<DeviceEntity>().ok(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/bind")
|
||||||
|
@Operation(summary = "获取已绑定设备")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<List<DeviceEntity>> getUserDevices() {
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
List<DeviceEntity> devices = deviceService.getUserDevices(user.getId());
|
||||||
|
return new Result<List<DeviceEntity>>().ok(devices);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/unbind")
|
||||||
|
@Operation(summary = "解绑设备")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result unbindDevice(@RequestBody DeviceUnBindDTO unDeviveBind) {
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
deviceService.unbindDevice(user.getId(), unDeviveBind.getDeviceId());
|
||||||
|
return new Result();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/all")
|
||||||
|
@Operation(summary = "设备列表(管理员)")
|
||||||
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
|
||||||
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
|
||||||
|
})
|
||||||
|
public Result<PageData<DeviceEntity>> adminDeviceList(
|
||||||
|
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
PageData<DeviceEntity> page = deviceService.adminDeviceList(params);
|
||||||
|
return new Result<PageData<DeviceEntity>>().ok(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package xiaozhi.modules.device.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface DeviceDao extends BaseMapper<DeviceEntity> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package xiaozhi.modules.device.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "设备连接头信息")
|
||||||
|
public class DeviceHeaderDTO {
|
||||||
|
|
||||||
|
@Schema(description = "设备ID")
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
@Schema(description = "协议版本号")
|
||||||
|
private Long protocolVersion;
|
||||||
|
|
||||||
|
@Schema(description = "认证信息")
|
||||||
|
private String authorization;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package xiaozhi.modules.device.dto;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备解绑表单
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "设备解绑表单")
|
||||||
|
public class DeviceUnBindDTO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "设备ID")
|
||||||
|
@NotBlank(message = "设备ID不能为空")
|
||||||
|
private Long deviceId;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package xiaozhi.modules.device.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("ai_device")
|
||||||
|
@Schema(description = "设备信息")
|
||||||
|
public class DeviceEntity {
|
||||||
|
@Schema(description = "设备ID")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "关联用户ID")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "MAC地址")
|
||||||
|
private String macAddress;
|
||||||
|
|
||||||
|
@Schema(description = "最后连接时间")
|
||||||
|
private Date lastConnectedAt;
|
||||||
|
|
||||||
|
@Schema(description = "自动更新开关(0关闭/1开启)")
|
||||||
|
private Integer autoUpdate;
|
||||||
|
|
||||||
|
@Schema(description = "设备硬件型号")
|
||||||
|
private String board;
|
||||||
|
|
||||||
|
@Schema(description = "设备别名")
|
||||||
|
private String alias;
|
||||||
|
|
||||||
|
@Schema(description = "智能体编码")
|
||||||
|
private String agentCode;
|
||||||
|
|
||||||
|
@Schema(description = "智能体ID")
|
||||||
|
private Long agentId;
|
||||||
|
|
||||||
|
@Schema(description = "固件版本号")
|
||||||
|
private String appVersion;
|
||||||
|
|
||||||
|
@Schema(description = "排序")
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
@Schema(description = "创建者")
|
||||||
|
private Long creator;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private Date createDate;
|
||||||
|
|
||||||
|
@Schema(description = "更新者")
|
||||||
|
private Long updater;
|
||||||
|
|
||||||
|
@Schema(description = "更新时间")
|
||||||
|
private Date updateDate;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package xiaozhi.modules.device.service;
|
||||||
|
|
||||||
|
import xiaozhi.common.page.PageData;
|
||||||
|
import xiaozhi.modules.device.dto.DeviceHeaderDTO;
|
||||||
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface DeviceService {
|
||||||
|
DeviceEntity bindDevice(Long userId, DeviceHeaderDTO deviceHeader);
|
||||||
|
|
||||||
|
List<DeviceEntity> getUserDevices(Long userId);
|
||||||
|
|
||||||
|
void unbindDevice(Long userId, Long deviceId);
|
||||||
|
|
||||||
|
PageData<DeviceEntity> adminDeviceList(Map<String, Object> params);
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
package xiaozhi.modules.device.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import xiaozhi.common.page.PageData;
|
||||||
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
|
import xiaozhi.modules.device.dao.DeviceDao;
|
||||||
|
import xiaozhi.modules.device.dto.DeviceHeaderDTO;
|
||||||
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity> implements DeviceService {
|
||||||
|
private final DeviceDao deviceDao;
|
||||||
|
|
||||||
|
// 添加构造函数来初始化 deviceMapper
|
||||||
|
public DeviceServiceImpl(DeviceDao deviceDao) {
|
||||||
|
this.deviceDao = deviceDao;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceEntity bindDevice(Long userId, DeviceHeaderDTO deviceHeader) {
|
||||||
|
DeviceEntity device = new DeviceEntity();
|
||||||
|
device.setUserId(userId);
|
||||||
|
device.setMacAddress(deviceHeader.getDeviceId());
|
||||||
|
device.setCreateDate(new Date());
|
||||||
|
deviceDao.insert(device);
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DeviceEntity> getUserDevices(Long userId) {
|
||||||
|
QueryWrapper<DeviceEntity> wrapper = new QueryWrapper<>();
|
||||||
|
wrapper.eq("user_id", userId);
|
||||||
|
return deviceDao.selectList(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unbindDevice(Long userId, Long deviceId) {
|
||||||
|
deviceDao.deleteById(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageData<DeviceEntity> adminDeviceList(Map<String, Object> params) {
|
||||||
|
IPage<DeviceEntity> page = deviceDao.selectPage(
|
||||||
|
getPage(params, "sort", true),
|
||||||
|
new QueryWrapper<>()
|
||||||
|
);
|
||||||
|
return new PageData<>(page.getRecords(), page.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -61,14 +61,9 @@ public class ShiroConfig {
|
|||||||
Map<String, String> filterMap = new LinkedHashMap<>();
|
Map<String, String> filterMap = new LinkedHashMap<>();
|
||||||
filterMap.put("/webjars/**", "anon");
|
filterMap.put("/webjars/**", "anon");
|
||||||
filterMap.put("/druid/**", "anon");
|
filterMap.put("/druid/**", "anon");
|
||||||
filterMap.put("/login", "anon");
|
|
||||||
filterMap.put("/publicKey", "anon");
|
|
||||||
filterMap.put("/v3/api-docs/**", "anon");
|
filterMap.put("/v3/api-docs/**", "anon");
|
||||||
filterMap.put("/doc.html", "anon");
|
filterMap.put("/doc.html", "anon");
|
||||||
filterMap.put("/sys/oss/download/**", "anon");
|
|
||||||
filterMap.put("/captcha", "anon");
|
|
||||||
filterMap.put("/favicon.ico", "anon");
|
filterMap.put("/favicon.ico", "anon");
|
||||||
filterMap.put("/mobile/**", "anon");
|
|
||||||
filterMap.put("/user/**", "anon");
|
filterMap.put("/user/**", "anon");
|
||||||
filterMap.put("/**", "oauth2");
|
filterMap.put("/**", "oauth2");
|
||||||
shiroFilter.setFilterChainDefinitionMap(filterMap);
|
shiroFilter.setFilterChainDefinitionMap(filterMap);
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
package xiaozhi.modules.security.oauth2;
|
package xiaozhi.modules.security.oauth2;
|
||||||
|
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
|
||||||
import xiaozhi.common.user.UserDetail;
|
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
|
||||||
import xiaozhi.common.utils.MessageUtils;
|
|
||||||
import xiaozhi.modules.security.entity.SysUserTokenEntity;
|
|
||||||
import xiaozhi.modules.security.service.ShiroService;
|
|
||||||
import xiaozhi.modules.sys.entity.SysUserEntity;
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.apache.shiro.authc.*;
|
import org.apache.shiro.authc.*;
|
||||||
import org.apache.shiro.authz.AuthorizationInfo;
|
import org.apache.shiro.authz.AuthorizationInfo;
|
||||||
@@ -15,8 +8,15 @@ import org.apache.shiro.realm.AuthorizingRealm;
|
|||||||
import org.apache.shiro.subject.PrincipalCollection;
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
|
import xiaozhi.common.user.UserDetail;
|
||||||
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
|
import xiaozhi.common.utils.MessageUtils;
|
||||||
|
import xiaozhi.modules.security.entity.SysUserTokenEntity;
|
||||||
|
import xiaozhi.modules.security.service.ShiroService;
|
||||||
|
import xiaozhi.modules.sys.entity.SysUserEntity;
|
||||||
|
import xiaozhi.modules.sys.enums.SuperAdminEnum;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,6 +45,13 @@ public class Oauth2Realm extends AuthorizingRealm {
|
|||||||
//用户权限列表
|
//用户权限列表
|
||||||
Set<String> permsSet = shiroService.getUserPermissions(user);
|
Set<String> permsSet = shiroService.getUserPermissions(user);
|
||||||
|
|
||||||
|
if (user.getSuperAdmin() == SuperAdminEnum.YES.value()) {
|
||||||
|
permsSet.add("sys:role:superAdmin");
|
||||||
|
permsSet.add("sys:role:normal");
|
||||||
|
} else {
|
||||||
|
permsSet.add("sys:role:normal");
|
||||||
|
}
|
||||||
|
|
||||||
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
||||||
info.setStringPermissions(permsSet);
|
info.setStringPermissions(permsSet);
|
||||||
return info;
|
return info;
|
||||||
|
|||||||
+2
-141
@@ -1,34 +1,9 @@
|
|||||||
package xiaozhi.modules.sys.controller;
|
package xiaozhi.modules.sys.controller;
|
||||||
|
|
||||||
import xiaozhi.common.annotation.LogOperation;
|
|
||||||
import xiaozhi.common.constant.Constant;
|
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
|
||||||
import xiaozhi.common.exception.RenException;
|
|
||||||
import xiaozhi.common.page.PageData;
|
|
||||||
import xiaozhi.common.user.UserDetail;
|
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
|
||||||
import xiaozhi.common.utils.Result;
|
|
||||||
import xiaozhi.common.validator.AssertUtils;
|
|
||||||
import xiaozhi.common.validator.ValidatorUtils;
|
|
||||||
import xiaozhi.common.validator.group.AddGroup;
|
|
||||||
import xiaozhi.common.validator.group.DefaultGroup;
|
|
||||||
import xiaozhi.common.validator.group.UpdateGroup;
|
|
||||||
import xiaozhi.modules.security.password.PasswordUtils;
|
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
|
||||||
import xiaozhi.modules.sys.dto.PasswordDTO;
|
|
||||||
import xiaozhi.modules.sys.dto.SysUserDTO;
|
|
||||||
import xiaozhi.modules.sys.service.SysUserService;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.Parameters;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户管理
|
* 用户管理
|
||||||
@@ -38,119 +13,5 @@ import java.util.Map;
|
|||||||
@RequestMapping("/sys/user")
|
@RequestMapping("/sys/user")
|
||||||
@Tag(name = "用户管理")
|
@Tag(name = "用户管理")
|
||||||
public class SysUserController {
|
public class SysUserController {
|
||||||
private final SysUserService sysUserService;
|
|
||||||
|
|
||||||
@GetMapping("page")
|
|
||||||
@Operation(summary = "分页")
|
|
||||||
@Parameters({
|
|
||||||
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
|
|
||||||
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
|
|
||||||
@Parameter(name = Constant.ORDER_FIELD, description = "排序字段"),
|
|
||||||
@Parameter(name = Constant.ORDER, description = "排序方式,可选值(asc、desc)"),
|
|
||||||
@Parameter(name = "username", description = "用户名"),
|
|
||||||
@Parameter(name = "gender", description = "性别"),
|
|
||||||
@Parameter(name = "deptId", description = "部门ID")
|
|
||||||
})
|
|
||||||
@RequiresPermissions("sys:user:page")
|
|
||||||
public Result<PageData<SysUserDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
|
||||||
PageData<SysUserDTO> page = sysUserService.page(params);
|
|
||||||
|
|
||||||
return new Result<PageData<SysUserDTO>>().ok(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("{id}")
|
|
||||||
@Operation(summary = "信息")
|
|
||||||
@RequiresPermissions("sys:user:info")
|
|
||||||
public Result<SysUserDTO> get(@PathVariable("id") Long id) {
|
|
||||||
SysUserDTO data = sysUserService.get(id);
|
|
||||||
return new Result<SysUserDTO>().ok(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("info")
|
|
||||||
@Operation(summary = "登录用户信息")
|
|
||||||
public Result<SysUserDTO> info() {
|
|
||||||
SysUserDTO data = ConvertUtils.sourceToTarget(SecurityUser.getUser(), SysUserDTO.class);
|
|
||||||
return new Result<SysUserDTO>().ok(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PutMapping("password")
|
|
||||||
@Operation(summary = "修改密码")
|
|
||||||
@LogOperation("修改密码")
|
|
||||||
public Result password(@RequestBody PasswordDTO dto) {
|
|
||||||
//效验数据
|
|
||||||
ValidatorUtils.validateEntity(dto);
|
|
||||||
String newPassword = dto.getNewPassword();
|
|
||||||
String password = dto.getPassword();
|
|
||||||
|
|
||||||
//密码的强度
|
|
||||||
if (newPassword == null || newPassword.length() < 8) {
|
|
||||||
return new Result().error(ErrorCode.PASSWORD_LENGTH_ERROR);
|
|
||||||
}
|
|
||||||
if (!sysUserService.isStrongPassword(newPassword)) {
|
|
||||||
return new Result().error(ErrorCode.PASSWORD_WEAK_ERROR);
|
|
||||||
}
|
|
||||||
UserDetail user = SecurityUser.getUser();
|
|
||||||
//原密码不正确
|
|
||||||
if (!PasswordUtils.matches(dto.getPassword(), user.getPassword())) {
|
|
||||||
return new Result().error(ErrorCode.PASSWORD_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
sysUserService.updatePassword(user.getId(), dto.getNewPassword());
|
|
||||||
|
|
||||||
return new Result();
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping
|
|
||||||
@Operation(summary = "保存")
|
|
||||||
@LogOperation("保存")
|
|
||||||
@RequiresPermissions("sys:user:save")
|
|
||||||
public Result save(@RequestBody SysUserDTO dto) {
|
|
||||||
//效验数据
|
|
||||||
ValidatorUtils.validateEntity(dto, AddGroup.class, DefaultGroup.class);
|
|
||||||
|
|
||||||
sysUserService.save(dto);
|
|
||||||
|
|
||||||
return new Result();
|
|
||||||
}
|
|
||||||
|
|
||||||
@PutMapping
|
|
||||||
@Operation(summary = "修改")
|
|
||||||
@LogOperation("修改")
|
|
||||||
@RequiresPermissions("sys:user:update")
|
|
||||||
public Result update(@RequestBody SysUserDTO dto) {
|
|
||||||
//效验数据
|
|
||||||
ValidatorUtils.validateEntity(dto, UpdateGroup.class, DefaultGroup.class);
|
|
||||||
|
|
||||||
sysUserService.update(dto);
|
|
||||||
|
|
||||||
return new Result();
|
|
||||||
}
|
|
||||||
|
|
||||||
@PutMapping("app")
|
|
||||||
@Operation(summary = "修改")
|
|
||||||
@LogOperation("修改")
|
|
||||||
@RequiresPermissions("sys:user:update")
|
|
||||||
public Result updateUserInfo(@RequestBody SysUserDTO dto) {
|
|
||||||
sysUserService.updateUserInfo(dto);
|
|
||||||
|
|
||||||
return new Result();
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping
|
|
||||||
@Operation(summary = "删除")
|
|
||||||
@LogOperation("删除")
|
|
||||||
@RequiresPermissions("sys:user:delete")
|
|
||||||
public Result delete(@RequestBody Long[] ids) {
|
|
||||||
//效验数据
|
|
||||||
AssertUtils.isArrayEmpty(ids, "id");
|
|
||||||
|
|
||||||
List<Long> idList = Arrays.asList(ids);
|
|
||||||
if (idList.contains(SecurityUser.getUserId())) {
|
|
||||||
throw new RenException(ErrorCode.DEL_MYSELF_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
sysUserService.deleteBatchIds(idList);
|
|
||||||
|
|
||||||
return new Result();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,8 @@
|
|||||||
package xiaozhi.modules.sys.dao;
|
package xiaozhi.modules.sys.dao;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import xiaozhi.common.dao.BaseDao;
|
import xiaozhi.common.dao.BaseDao;
|
||||||
import xiaozhi.modules.sys.entity.SysUserEntity;
|
import xiaozhi.modules.sys.entity.SysUserEntity;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
|
||||||
import org.apache.ibatis.annotations.Param;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统用户
|
* 系统用户
|
||||||
@@ -14,21 +10,4 @@ import java.util.Map;
|
|||||||
@Mapper
|
@Mapper
|
||||||
public interface SysUserDao extends BaseDao<SysUserEntity> {
|
public interface SysUserDao extends BaseDao<SysUserEntity> {
|
||||||
|
|
||||||
List<SysUserEntity> getList(Map<String, Object> params);
|
|
||||||
|
|
||||||
SysUserEntity getById(Long id);
|
|
||||||
|
|
||||||
SysUserEntity getByUsername(String username);
|
|
||||||
|
|
||||||
int updatePassword(@Param("id") Long id, @Param("newPassword") String newPassword);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据部门ID,查询用户数
|
|
||||||
*/
|
|
||||||
int getCountByDeptId(Long deptId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据部门ID,查询用户ID列表
|
|
||||||
*/
|
|
||||||
List<Long> getUserIdListByDeptId(List<Long> deptIdList);
|
|
||||||
}
|
}
|
||||||
@@ -24,30 +24,6 @@ public class SysUserEntity extends BaseEntity {
|
|||||||
* 密码
|
* 密码
|
||||||
*/
|
*/
|
||||||
private String password;
|
private String password;
|
||||||
/**
|
|
||||||
* 姓名
|
|
||||||
*/
|
|
||||||
private String realName;
|
|
||||||
/**
|
|
||||||
* 头像
|
|
||||||
*/
|
|
||||||
private String headUrl;
|
|
||||||
/**
|
|
||||||
* 性别 0:男 1:女 2:保密
|
|
||||||
*/
|
|
||||||
private Integer gender;
|
|
||||||
/**
|
|
||||||
* 邮箱
|
|
||||||
*/
|
|
||||||
private String email;
|
|
||||||
/**
|
|
||||||
* 手机号
|
|
||||||
*/
|
|
||||||
private String mobile;
|
|
||||||
/**
|
|
||||||
* 部门ID
|
|
||||||
*/
|
|
||||||
private Long deptId;
|
|
||||||
/**
|
/**
|
||||||
* 超级管理员 0:否 1:是
|
* 超级管理员 0:否 1:是
|
||||||
*/
|
*/
|
||||||
@@ -56,6 +32,11 @@ public class SysUserEntity extends BaseEntity {
|
|||||||
* 状态 0:停用 1:正常
|
* 状态 0:停用 1:正常
|
||||||
*/
|
*/
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
/**
|
||||||
|
* 更新者
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private Long create_date;
|
||||||
/**
|
/**
|
||||||
* 更新者
|
* 更新者
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,67 +1,19 @@
|
|||||||
package xiaozhi.modules.sys.service;
|
package xiaozhi.modules.sys.service;
|
||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
|
||||||
import xiaozhi.common.service.BaseService;
|
import xiaozhi.common.service.BaseService;
|
||||||
import xiaozhi.modules.sys.dto.SysUserDTO;
|
import xiaozhi.modules.sys.dto.SysUserDTO;
|
||||||
import xiaozhi.modules.sys.entity.SysUserEntity;
|
import xiaozhi.modules.sys.entity.SysUserEntity;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统用户
|
* 系统用户
|
||||||
*/
|
*/
|
||||||
public interface SysUserService extends BaseService<SysUserEntity> {
|
public interface SysUserService extends BaseService<SysUserEntity> {
|
||||||
|
|
||||||
PageData<SysUserDTO> page(Map<String, Object> params);
|
|
||||||
|
|
||||||
List<SysUserDTO> list(Map<String, Object> params);
|
|
||||||
|
|
||||||
SysUserDTO get(Long id);
|
|
||||||
|
|
||||||
SysUserDTO getByUsername(String username);
|
SysUserDTO getByUsername(String username);
|
||||||
|
|
||||||
void save(SysUserDTO dto);
|
void save(SysUserDTO dto);
|
||||||
|
|
||||||
void update(SysUserDTO dto);
|
|
||||||
|
|
||||||
void updateUserInfo(SysUserDTO dto);
|
|
||||||
|
|
||||||
void delete(Long[] ids);
|
void delete(Long[] ids);
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改密码
|
|
||||||
*
|
|
||||||
* @param id 用户ID
|
|
||||||
* @param newPassword 新密码
|
|
||||||
*/
|
|
||||||
void updatePassword(Long id, String newPassword);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据部门ID,查询用户数
|
|
||||||
*/
|
|
||||||
int getCountByDeptId(Long deptId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据部门ID,查询用户Id列表
|
|
||||||
*/
|
|
||||||
List<Long> getUserIdListByDeptId(List<Long> deptIdList);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除用户缓存
|
|
||||||
*
|
|
||||||
* @param userId
|
|
||||||
*/
|
|
||||||
void deleteUserCache(Long userId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证密码强度
|
|
||||||
*
|
|
||||||
* @param newPassword
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
boolean isStrongPassword(String newPassword);
|
|
||||||
|
|
||||||
String getName(Long creator);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-113
@@ -1,29 +1,22 @@
|
|||||||
package xiaozhi.modules.sys.service.impl;
|
package xiaozhi.modules.sys.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.page.PageData;
|
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
import xiaozhi.common.user.UserDetail;
|
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
import xiaozhi.modules.security.password.PasswordUtils;
|
import xiaozhi.modules.security.password.PasswordUtils;
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
|
||||||
import xiaozhi.modules.sys.dao.SysUserDao;
|
import xiaozhi.modules.sys.dao.SysUserDao;
|
||||||
import xiaozhi.modules.sys.dto.SysUserDTO;
|
import xiaozhi.modules.sys.dto.SysUserDTO;
|
||||||
import xiaozhi.modules.sys.entity.SysUserEntity;
|
import xiaozhi.modules.sys.entity.SysUserEntity;
|
||||||
import xiaozhi.modules.sys.enums.SuperAdminEnum;
|
import xiaozhi.modules.sys.enums.SuperAdminEnum;
|
||||||
import xiaozhi.modules.sys.service.SysUserService;
|
import xiaozhi.modules.sys.service.SysUserService;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -34,40 +27,17 @@ import java.util.regex.Pattern;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Service
|
@Service
|
||||||
public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntity> implements SysUserService {
|
public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntity> implements SysUserService {
|
||||||
private final RedisUtils redisUtils;
|
private final SysUserDao sysUserDao;
|
||||||
|
|
||||||
@Override
|
|
||||||
public PageData<SysUserDTO> page(Map<String, Object> params) {
|
|
||||||
//转换成like
|
|
||||||
paramsToLike(params, "username");
|
|
||||||
|
|
||||||
//分页
|
|
||||||
IPage<SysUserEntity> page = getPage(params, "t1.create_date", false);
|
|
||||||
|
|
||||||
//查询
|
|
||||||
List<SysUserEntity> list = baseDao.getList(params);
|
|
||||||
|
|
||||||
return getPageData(list, page.getTotal(), SysUserDTO.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<SysUserDTO> list(Map<String, Object> params) {
|
|
||||||
|
|
||||||
List<SysUserEntity> entityList = baseDao.getList(params);
|
|
||||||
|
|
||||||
return ConvertUtils.sourceToTarget(entityList, SysUserDTO.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SysUserDTO get(Long id) {
|
|
||||||
SysUserEntity entity = baseDao.getById(id);
|
|
||||||
|
|
||||||
return ConvertUtils.sourceToTarget(entity, SysUserDTO.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SysUserDTO getByUsername(String username) {
|
public SysUserDTO getByUsername(String username) {
|
||||||
SysUserEntity entity = baseDao.getByUsername(username);
|
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("username", username);
|
||||||
|
List<SysUserEntity> users = sysUserDao.selectList(queryWrapper);
|
||||||
|
if (users == null || users.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
SysUserEntity entity = users.getFirst();
|
||||||
return ConvertUtils.sourceToTarget(entity, SysUserDTO.class);
|
return ConvertUtils.sourceToTarget(entity, SysUserDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,42 +56,15 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
|
|||||||
entity.setPassword(password);
|
entity.setPassword(password);
|
||||||
|
|
||||||
//保存用户
|
//保存用户
|
||||||
entity.setSuperAdmin(SuperAdminEnum.NO.value());
|
Long userCount = getUserCount();
|
||||||
insert(entity);
|
if (userCount == 0) {
|
||||||
}
|
entity.setSuperAdmin(SuperAdminEnum.YES.value());
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public void update(SysUserDTO dto) {
|
|
||||||
SysUserEntity entity = ConvertUtils.sourceToTarget(dto, SysUserEntity.class);
|
|
||||||
|
|
||||||
//密码加密
|
|
||||||
if (StringUtils.isBlank(dto.getPassword())) {
|
|
||||||
entity.setPassword(null);
|
|
||||||
} else {
|
} else {
|
||||||
//密码强度
|
entity.setSuperAdmin(SuperAdminEnum.NO.value());
|
||||||
if (!isStrongPassword(entity.getPassword())) {
|
|
||||||
throw new RenException(ErrorCode.PASSWORD_WEAK_ERROR);
|
|
||||||
}
|
|
||||||
String password = PasswordUtils.encode(entity.getPassword());
|
|
||||||
entity.setPassword(password);
|
|
||||||
}
|
}
|
||||||
|
entity.setStatus(1);
|
||||||
|
|
||||||
//更新用户
|
insert(entity);
|
||||||
updateById(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public void updateUserInfo(SysUserDTO dto) {
|
|
||||||
SysUserEntity entity = selectById(dto.getId());
|
|
||||||
entity.setHeadUrl(dto.getHeadUrl());
|
|
||||||
entity.setRealName(dto.getRealName());
|
|
||||||
entity.setGender(dto.getGender());
|
|
||||||
entity.setMobile(dto.getMobile());
|
|
||||||
entity.setEmail(dto.getEmail());
|
|
||||||
|
|
||||||
updateById(entity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -131,51 +74,17 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
|
|||||||
baseDao.deleteBatchIds(Arrays.asList(ids));
|
baseDao.deleteBatchIds(Arrays.asList(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private Long getUserCount() {
|
||||||
@Transactional(rollbackFor = Exception.class)
|
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
|
||||||
public void updatePassword(Long id, String newPassword) {
|
return baseDao.selectCount(queryWrapper);
|
||||||
newPassword = PasswordUtils.encode(newPassword);
|
|
||||||
|
|
||||||
baseDao.updatePassword(id, newPassword);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCountByDeptId(Long deptId) {
|
|
||||||
return baseDao.getCountByDeptId(deptId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private boolean isStrongPassword(String password) {
|
||||||
public List<Long> getUserIdListByDeptId(List<Long> deptIdList) {
|
|
||||||
return baseDao.getUserIdListByDeptId(deptIdList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deleteUserCache(Long userId) {
|
|
||||||
// 删除缓存
|
|
||||||
redisUtils.delete(RedisKeys.getUserInfoKey(userId));
|
|
||||||
redisUtils.delete(RedisKeys.getDataScopeListKey(userId));
|
|
||||||
redisUtils.delete(RedisKeys.getSysUserName(userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isStrongPassword(String password) {
|
|
||||||
// 弱密码的正则表达式
|
// 弱密码的正则表达式
|
||||||
String weakPasswordRegex = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).+$";
|
String weakPasswordRegex = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).+$";
|
||||||
Pattern pattern = Pattern.compile(weakPasswordRegex);
|
Pattern pattern = Pattern.compile(weakPasswordRegex);
|
||||||
Matcher matcher = pattern.matcher(password);
|
Matcher matcher = pattern.matcher(password);
|
||||||
return matcher.matches();
|
return matcher.matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName(Long id) {
|
|
||||||
String name = (String) redisUtils.get(RedisKeys.getSysUserName(id));
|
|
||||||
if (StringUtils.isBlank(name)) {
|
|
||||||
SysUserEntity sysUserEntity = selectById(id);
|
|
||||||
if (sysUserEntity != null) {
|
|
||||||
redisUtils.set(RedisKeys.getSysUserName(id), sysUserEntity.getUsername());
|
|
||||||
return sysUserEntity.getUsername();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
-- 模型配置表
|
||||||
|
DROP TABLE IF EXISTS `ai_model_config`;
|
||||||
|
CREATE TABLE `ai_model_config` (
|
||||||
|
`id` BIGINT NOT NULL COMMENT '主键',
|
||||||
|
`model_type` VARCHAR(20) COMMENT '模型类型(Memory/ASR/VAD/LLM/TTS)',
|
||||||
|
`model_code` VARCHAR(50) COMMENT '模型编码(如AliLLM、DoubaoTTS)',
|
||||||
|
`model_name` VARCHAR(50) COMMENT '模型名称',
|
||||||
|
`is_default` TINYINT(1) DEFAULT 0 COMMENT '是否默认配置(0否 1是)',
|
||||||
|
`is_enabled` TINYINT(1) DEFAULT 0 COMMENT '是否启用(原注释有误,应为是否启用而非是否默认配置)',
|
||||||
|
`config_json` JSON COMMENT '模型配置(JSON格式)',
|
||||||
|
`doc_link` VARCHAR(200) COMMENT '官方文档链接',
|
||||||
|
`remark` VARCHAR(255) COMMENT '备注',
|
||||||
|
`sort` INT UNSIGNED DEFAULT 0 COMMENT '排序',
|
||||||
|
`creator` BIGINT COMMENT '创建者',
|
||||||
|
`create_date` DATETIME COMMENT '创建时间',
|
||||||
|
`updater` BIGINT COMMENT '更新者',
|
||||||
|
`update_date` DATETIME COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='模型配置表';
|
||||||
|
|
||||||
|
-- TTS 音色表
|
||||||
|
DROP TABLE IF EXISTS `ai_tts_voice`;
|
||||||
|
CREATE TABLE `ai_tts_voice` (
|
||||||
|
`id` BIGINT NOT NULL COMMENT '主键',
|
||||||
|
`tts_model_id` BIGINT COMMENT '对应 TTS 模型主键',
|
||||||
|
`name` VARCHAR(20) COMMENT '音色名称',
|
||||||
|
`tts_voice` VARCHAR(50) COMMENT '音色编码',
|
||||||
|
`languages` VARCHAR(50) COMMENT '语言',
|
||||||
|
`voice_demo` VARCHAR(500) DEFAULT NULL COMMENT '音色 Demo',
|
||||||
|
`remark` VARCHAR(255) COMMENT '备注',
|
||||||
|
`sort` INT UNSIGNED DEFAULT 0 COMMENT '排序',
|
||||||
|
`creator` BIGINT COMMENT '创建者',
|
||||||
|
`create_date` DATETIME COMMENT '创建时间',
|
||||||
|
`updater` BIGINT COMMENT '更新者',
|
||||||
|
`update_date` DATETIME COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='TTS 音色表';
|
||||||
|
|
||||||
|
-- 对话历史表
|
||||||
|
DROP TABLE IF EXISTS `ai_chat_history`;
|
||||||
|
CREATE TABLE `ai_chat_history` (
|
||||||
|
`id` BIGINT NOT NULL COMMENT '对话编号',
|
||||||
|
`user_id` BIGINT COMMENT '用户编号',
|
||||||
|
`agent_id` BIGINT DEFAULT NULL COMMENT '聊天角色',
|
||||||
|
`device_id` BIGINT DEFAULT NULL COMMENT '设备编号(原注释有误,应为设备编号)',
|
||||||
|
`message_count` INT COMMENT '信息汇总',
|
||||||
|
`sort` INT UNSIGNED DEFAULT 0 COMMENT '排序',
|
||||||
|
`creator` BIGINT COMMENT '创建者',
|
||||||
|
`create_date` DATETIME COMMENT '创建时间',
|
||||||
|
`updater` BIGINT COMMENT '更新者',
|
||||||
|
`update_date` DATETIME COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='对话历史表';
|
||||||
|
|
||||||
|
-- 对话信息表
|
||||||
|
DROP TABLE IF EXISTS `ai_chat_message`;
|
||||||
|
CREATE TABLE `ai_chat_message` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '对话记录唯一标识',
|
||||||
|
`user_id` BIGINT COMMENT '用户唯一标识',
|
||||||
|
`chat_id` VARCHAR(64) COMMENT '对话历史 ID',
|
||||||
|
`agent_name` VARCHAR(64) COMMENT '智能体名称',
|
||||||
|
`role` ENUM('user', 'assistant') COMMENT '角色(用户或助理)',
|
||||||
|
`content` TEXT COMMENT '对话内容',
|
||||||
|
`embedding` TEXT COMMENT '对话内容的嵌入向量(可选)',
|
||||||
|
`url` VARCHAR(255) COMMENT '相关音频文件的 URL(可选)',
|
||||||
|
`prompt_tokens` INT UNSIGNED DEFAULT 0 COMMENT '提示令牌数',
|
||||||
|
`total_tokens` INT UNSIGNED DEFAULT 0 COMMENT '总令牌数',
|
||||||
|
`completion_tokens` INT UNSIGNED DEFAULT 0 COMMENT '完成令牌数',
|
||||||
|
`prompt_ms` INT UNSIGNED DEFAULT 0 COMMENT '提示耗时(毫秒)',
|
||||||
|
`total_ms` INT UNSIGNED DEFAULT 0 COMMENT '总耗时(毫秒)',
|
||||||
|
`completion_ms` INT UNSIGNED DEFAULT 0 COMMENT '完成耗时(毫秒)',
|
||||||
|
`sort` INT UNSIGNED DEFAULT 0 COMMENT '排序',
|
||||||
|
`creator` BIGINT COMMENT '创建者',
|
||||||
|
`create_date` DATETIME COMMENT '创建时间',
|
||||||
|
`updater` BIGINT COMMENT '更新者',
|
||||||
|
`update_date` DATETIME COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
INDEX `idx_user_id_chat_id_role` (`user_id`, `chat_id`) COMMENT '用户 ID、聊天会话 ID 和角色的联合索引,用于快速检索对话记录',
|
||||||
|
INDEX `idx_created_at` (`create_date`) COMMENT '创建时间的索引,用于按时间排序或检索对话记录'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='对话信息表';
|
||||||
|
|
||||||
|
-- 设备信息表
|
||||||
|
DROP TABLE IF EXISTS `ai_device`;
|
||||||
|
CREATE TABLE `ai_device` (
|
||||||
|
`id` BIGINT NOT NULL COMMENT '设备唯一标识',
|
||||||
|
`user_id` BIGINT COMMENT '关联用户 ID',
|
||||||
|
`mac_address` VARCHAR(50) COMMENT 'MAC 地址',
|
||||||
|
`last_connected_at` DATETIME COMMENT '最后连接时间',
|
||||||
|
`auto_update` TINYINT UNSIGNED DEFAULT 0 COMMENT '自动更新开关(0 关闭/1 开启)',
|
||||||
|
`board` VARCHAR(50) COMMENT '设备硬件型号',
|
||||||
|
`alias` VARCHAR(64) DEFAULT NULL COMMENT '设备别名',
|
||||||
|
`agent_code` VARCHAR(36) COMMENT '智能体编码',
|
||||||
|
`agent_id` BIGINT COMMENT '智能体 ID',
|
||||||
|
`app_version` VARCHAR(20) COMMENT '固件版本号',
|
||||||
|
`sort` INT UNSIGNED DEFAULT 0 COMMENT '排序',
|
||||||
|
`creator` BIGINT COMMENT '创建者',
|
||||||
|
`create_date` DATETIME COMMENT '创建时间',
|
||||||
|
`updater` BIGINT COMMENT '更新者',
|
||||||
|
`update_date` DATETIME COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备信息表';
|
||||||
|
|
||||||
|
-- 智能体配置表
|
||||||
|
DROP TABLE IF EXISTS `ai_agent`;
|
||||||
|
CREATE TABLE `ai_agent` (
|
||||||
|
`id` BIGINT NOT NULL COMMENT '智能体唯一标识',
|
||||||
|
`user_id` BIGINT COMMENT '所属用户 ID',
|
||||||
|
`agent_code` VARCHAR(36) COMMENT '智能体唯一凭证',
|
||||||
|
`agent_name` VARCHAR(64) COMMENT '智能体名称',
|
||||||
|
`tts_voice` VARCHAR(64) COMMENT '语音合成标识',
|
||||||
|
`llm_model` VARCHAR(32) COMMENT '大语言模型标识',
|
||||||
|
`memory` TEXT COMMENT '历史记忆数据',
|
||||||
|
`character` TEXT COMMENT '角色设定参数',
|
||||||
|
`long_memory_switch` TINYINT UNSIGNED DEFAULT 0 COMMENT '长期记忆开关',
|
||||||
|
`lang_code` VARCHAR(10) COMMENT '语言编码',
|
||||||
|
`language` VARCHAR(10) COMMENT '交互语种',
|
||||||
|
`sort` INT UNSIGNED DEFAULT 0 COMMENT '排序权重',
|
||||||
|
`creator` BIGINT COMMENT '创建者 ID',
|
||||||
|
`created_at` DATETIME COMMENT '创建时间',
|
||||||
|
`updater` BIGINT COMMENT '更新者 ID',
|
||||||
|
`updated_at` DATETIME COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='智能体配置表';
|
||||||
|
|
||||||
|
-- 智能体配置模板表
|
||||||
|
DROP TABLE IF EXISTS `ai_agent_template`;
|
||||||
|
CREATE TABLE `ai_agent_template` (
|
||||||
|
`id` BIGINT NOT NULL COMMENT '智能体唯一标识',
|
||||||
|
`agent_code` VARCHAR(36) COMMENT '智能体编码',
|
||||||
|
`agent_name` VARCHAR(64) COMMENT '智能体名称',
|
||||||
|
`asr_model_id` VARCHAR(32) COMMENT '语音识别模型标识',
|
||||||
|
`vad_model_id` VARCHAR(64) COMMENT '语音活动检测标识',
|
||||||
|
`llm_model_id` VARCHAR(32) COMMENT '大语言模型标识',
|
||||||
|
`tts_model_id` VARCHAR(32) COMMENT '语音合成模型标识',
|
||||||
|
`tts_voice_id` VARCHAR(32) COMMENT '音色标识',
|
||||||
|
`memory` TEXT COMMENT '历史记忆数据',
|
||||||
|
`character` TEXT COMMENT '角色设定参数',
|
||||||
|
`long_memory_switch` TINYINT UNSIGNED DEFAULT 0 COMMENT '长期记忆开关',
|
||||||
|
`lang_code` VARCHAR(10) COMMENT '语言编码',
|
||||||
|
`language` VARCHAR(10) COMMENT '交互语种',
|
||||||
|
`sort` INT UNSIGNED DEFAULT 0 COMMENT '排序权重',
|
||||||
|
`creator` BIGINT COMMENT '创建者 ID',
|
||||||
|
`created_at` DATETIME COMMENT '创建时间',
|
||||||
|
`updater` BIGINT COMMENT '更新者 ID',
|
||||||
|
`updated_at` DATETIME COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='智能体配置模板表';
|
||||||
|
|
||||||
|
-- 声纹识别表
|
||||||
|
DROP TABLE IF EXISTS `ai_voiceprint`;
|
||||||
|
CREATE TABLE `ai_voiceprint` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '声纹唯一标识',
|
||||||
|
`name` VARCHAR(64) COMMENT '声纹名称',
|
||||||
|
`user_id` BIGINT COMMENT '用户 ID(关联用户表)',
|
||||||
|
`agent_id` BIGINT COMMENT '关联智能体 ID',
|
||||||
|
`agent_code` VARCHAR(36) COMMENT '关联智能体编码',
|
||||||
|
`agent_name` VARCHAR(36) COMMENT '关联智能体名称',
|
||||||
|
`description` VARCHAR(255) COMMENT '声纹描述',
|
||||||
|
`embedding` LONGTEXT COMMENT '声纹特征向量(JSON 数组格式)',
|
||||||
|
`memory` TEXT COMMENT '关联记忆数据',
|
||||||
|
`sort` INT UNSIGNED DEFAULT 0 COMMENT '排序权重',
|
||||||
|
`creator` BIGINT COMMENT '创建者 ID',
|
||||||
|
`created_at` DATETIME COMMENT '创建时间',
|
||||||
|
`updater` BIGINT COMMENT '更新者 ID',
|
||||||
|
`updated_at` DATETIME COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='声纹识别表';
|
||||||
@@ -16,3 +16,10 @@ databaseChangeLog:
|
|||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202503101631.sql
|
path: classpath:db/changelog/202503101631.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202503131429
|
||||||
|
author: czc
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202503131429.sql
|
||||||
@@ -33,3 +33,4 @@
|
|||||||
10030=\u5BC6\u7801\u957F\u5EA6\u4E0D\u8DB3{0}\u4F4D
|
10030=\u5BC6\u7801\u957F\u5EA6\u4E0D\u8DB3{0}\u4F4D
|
||||||
10031=\u5F31\u5BC6\u7801
|
10031=\u5F31\u5BC6\u7801
|
||||||
10032=\u5220\u9664\u672C\u6570\u636E\u5F02\u5E38
|
10032=\u5220\u9664\u672C\u6570\u636E\u5F02\u5E38
|
||||||
|
10033=\u8BBE\u5907\u9A8C\u8BC1\u7801\u9519\u8BEF
|
||||||
|
|||||||
@@ -30,5 +30,6 @@
|
|||||||
10029=Cannot contain illegal characters
|
10029=Cannot contain illegal characters
|
||||||
|
|
||||||
10030=The password is less than {0} digits.
|
10030=The password is less than {0} digits.
|
||||||
10031=Weak password. The password must contain {0} of uppercase letters, lowercase letters, symbols and numbers.
|
10031=The password must consist of numbers, uppercase and lowercase letters, and special characters at the same time
|
||||||
10032=Exception in deleting this data
|
10032=Exception in deleting this data
|
||||||
|
10033=Device verification code error
|
||||||
@@ -30,5 +30,6 @@
|
|||||||
10028=\u5B9A\u65F6\u4EFB\u52A1\u5931\u8D25
|
10028=\u5B9A\u65F6\u4EFB\u52A1\u5931\u8D25
|
||||||
10029=\u4E0D\u80FD\u5305\u542B\u975E\u6CD5\u5B57\u7B26
|
10029=\u4E0D\u80FD\u5305\u542B\u975E\u6CD5\u5B57\u7B26
|
||||||
10030=\u5BC6\u7801\u957F\u5EA6\u4E0D\u8DB3{0}\u4F4D
|
10030=\u5BC6\u7801\u957F\u5EA6\u4E0D\u8DB3{0}\u4F4D
|
||||||
10031=\u5F31\u5BC6\u7801
|
10031=\u5BC6\u7801\u5FC5\u987B\u540C\u65F6\u5305\u542B\u6570\u5B57\u3001\u5927\u5C0F\u5B57\u6BCD\u548C\u7279\u6B8A\u5B57\u7B26\u7EC4\u6210
|
||||||
10032=\u5220\u9664\u672C\u6570\u636E\u5F02\u5E38\u5EA6\u4E0D\u591F\uFF0C\u9700\u8981\u540C\u65F6\u5305\u542B\u6570\u5B57,\u5C0F\u5199\u82F1\u6587,\u5927\u5199\u82F1\u6587
|
10032=\u5220\u9664\u672C\u6570\u636E\u5F02\u5E38
|
||||||
|
10033=\u8BBE\u5907\u9A8C\u8BC1\u7801\u9519\u8BEF
|
||||||
@@ -30,5 +30,6 @@
|
|||||||
10028=\u5B9A\u6642\u4EFB\u52D9\u5931\u6557
|
10028=\u5B9A\u6642\u4EFB\u52D9\u5931\u6557
|
||||||
10029=\u4E0D\u80FD\u5305\u542B\u975E\u6CD5\u5B57\u7B26
|
10029=\u4E0D\u80FD\u5305\u542B\u975E\u6CD5\u5B57\u7B26
|
||||||
10030=\u5BC6\u78BC\u9577\u5EA6\u4E0D\u8DB3{0}\u4F4D
|
10030=\u5BC6\u78BC\u9577\u5EA6\u4E0D\u8DB3{0}\u4F4D
|
||||||
10031=\u5F31\u5BC6\u78BC\uFF0C\u5BC6\u78BC\u9700\u8981\u5305\u542B\u5927\u5BEB\u5B57\u6BCD\uFF0C\u5C0F\u5BEB\u5B57\u6BCD\uFF0C\u7B26\u865F\uFF0C\u6578\u5B57\u5176\u4E2D{0}\u7A2E
|
10031=\u5BC6\u78BC\u5FC5\u9808\u540C\u6642\u5305\u542B\u6578\u5B57\u3001\u5927\u5C0F\u5B57\u6BCD\u548C\u7279\u6B8A\u5B57\u7B26\u7D44\u6210
|
||||||
10032=\u522A\u9664\u6B64\u6578\u64DA\u7570\u5E38
|
10032=\u522A\u9664\u6B64\u6578\u64DA\u7570\u5E38
|
||||||
|
10033=\u8A2D\u5099\u9A57\u8B49\u78BC\u932F\u8AA4
|
||||||
@@ -3,48 +3,4 @@
|
|||||||
|
|
||||||
<mapper namespace="xiaozhi.modules.sys.dao.SysUserDao">
|
<mapper namespace="xiaozhi.modules.sys.dao.SysUserDao">
|
||||||
|
|
||||||
<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
|
|
||||||
from sys_user t1 where t1.super_admin = 0
|
|
||||||
<if test="username != null and username.trim() != ''">
|
|
||||||
and t1.username like #{username}
|
|
||||||
</if>
|
|
||||||
<if test="deptId != null and deptId.trim() != ''">
|
|
||||||
and t1.dept_id = #{deptId}
|
|
||||||
</if>
|
|
||||||
<if test="gender != null and gender.trim() != ''">
|
|
||||||
and t1.gender = #{gender}
|
|
||||||
</if>
|
|
||||||
<if test="deptIdList != null">
|
|
||||||
and t1.dept_id in
|
|
||||||
<foreach item="id" collection="deptIdList" open="(" separator="," close=")">
|
|
||||||
#{id}
|
|
||||||
</foreach>
|
|
||||||
</if>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<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
|
|
||||||
where t1.id = #{value}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="getByUsername" resultType="xiaozhi.modules.sys.entity.SysUserEntity">
|
|
||||||
select * from sys_user where username = #{value}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<update id="updatePassword">
|
|
||||||
update sys_user set password = #{newPassword} where id = #{id}
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<select id="getCountByDeptId" resultType="int">
|
|
||||||
select count(*) from sys_user where dept_id = #{value}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="getUserIdListByDeptId" resultType="Long">
|
|
||||||
select id from sys_user where dept_id in
|
|
||||||
<foreach item="deptId" collection="list" open="(" separator="," close=")">
|
|
||||||
#{deptId}
|
|
||||||
</foreach>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="input-box">
|
<div class="input-box">
|
||||||
<img src="@/assets/login/password.png" alt="" class="input-icon"/>
|
<img src="@/assets/login/password.png" alt="" class="input-icon"/>
|
||||||
<el-input v-model="form.password" placeholder="请输入密码"/>
|
<el-input v-model="form.password" type="password" placeholder="请输入密码"/>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; align-items: center; margin-top: 20px; width: 100%; gap: 10px;">
|
<div style="display: flex; align-items: center; margin-top: 20px; width: 100%; gap: 10px;">
|
||||||
<div class="input-box" style="width: calc(100% - 130px); margin-top: 0;">
|
<div class="input-box" style="width: calc(100% - 130px); margin-top: 0;">
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {goToPage, showDanger, showSuccess} from '@/utils'
|
import {getUUID, goToPage, showDanger, showSuccess} from '@/utils'
|
||||||
import Api from '@/apis/api';
|
import Api from '@/apis/api';
|
||||||
|
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchCaptcha() {
|
fetchCaptcha() {
|
||||||
this.captchaUuid = Date.now().toString()
|
this.captchaUuid = getUUID();
|
||||||
|
|
||||||
Api.user.getCaptcha(this.captchaUuid, (res) => {
|
Api.user.getCaptcha(this.captchaUuid, (res) => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
@@ -96,7 +96,7 @@ export default {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.error('验证码加载异常:', error);
|
console.error('验证码加载异常:', error);
|
||||||
showDanger('验证码加载失败,点击刷新');
|
showDanger('验证码加载失败,点击刷新')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -117,9 +117,13 @@ export default {
|
|||||||
|
|
||||||
this.form.captchaId = this.captchaUuid
|
this.form.captchaId = this.captchaUuid
|
||||||
Api.user.login(this.form, ({data}) => {
|
Api.user.login(this.form, ({data}) => {
|
||||||
|
console.log(data)
|
||||||
showSuccess('登陆成功!')
|
showSuccess('登陆成功!')
|
||||||
goToPage('/home')
|
goToPage('/home')
|
||||||
})
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
this.fetchCaptcha()
|
||||||
|
}, 1000)
|
||||||
},
|
},
|
||||||
|
|
||||||
goToRegister() {
|
goToRegister() {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {goToPage, showDanger, showSuccess} from '@/utils'
|
import {getUUID, goToPage, showDanger, showSuccess} from '@/utils'
|
||||||
import Api from '@/apis/api';
|
import Api from '@/apis/api';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -106,7 +106,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
// 复用验证码获取方法
|
// 复用验证码获取方法
|
||||||
fetchCaptcha() {
|
fetchCaptcha() {
|
||||||
this.form.captchaId = Date.now().toString()
|
this.form.captchaId = getUUID();
|
||||||
Api.user.getCaptcha(this.form.captchaId, (res) => {
|
Api.user.getCaptcha(this.form.captchaId, (res) => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
const blob = new Blob([res.data], {type: res.data.type});
|
const blob = new Blob([res.data], {type: res.data.type});
|
||||||
@@ -147,7 +147,9 @@ export default {
|
|||||||
this.fetchCaptcha()
|
this.fetchCaptcha()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
this.fetchCaptcha()
|
||||||
|
}, 1000)
|
||||||
},
|
},
|
||||||
|
|
||||||
goToLogin() {
|
goToLogin() {
|
||||||
|
|||||||
@@ -181,8 +181,9 @@ LLM:
|
|||||||
CozeLLM:
|
CozeLLM:
|
||||||
# 定义LLM API类型
|
# 定义LLM API类型
|
||||||
type: coze
|
type: coze
|
||||||
bot_id: 你的bot_id
|
# bot_id和user_id的内容写在引号之内
|
||||||
user_id: 你的user_id
|
bot_id: "你的bot_id"
|
||||||
|
user_id: "你的user_id"
|
||||||
personal_access_token: 你的coze个人令牌
|
personal_access_token: 你的coze个人令牌
|
||||||
LMStudioLLM:
|
LMStudioLLM:
|
||||||
# 定义LLM API类型
|
# 定义LLM API类型
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from config.logger import setup_logging
|
|||||||
import json
|
import json
|
||||||
from core.handle.sendAudioHandle import send_stt_message
|
from core.handle.sendAudioHandle import send_stt_message
|
||||||
from core.utils.dialogue import Message
|
from core.utils.dialogue import Message
|
||||||
|
from core.utils.util import remove_punctuation_and_length
|
||||||
from config.functionCallConfig import FunctionCallConfig
|
from config.functionCallConfig import FunctionCallConfig
|
||||||
import asyncio
|
import asyncio
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
@@ -105,6 +106,7 @@ async def handle_user_intent(conn, text):
|
|||||||
|
|
||||||
async def check_direct_exit(conn, text):
|
async def check_direct_exit(conn, text):
|
||||||
"""检查是否有明确的退出命令"""
|
"""检查是否有明确的退出命令"""
|
||||||
|
_, text = remove_punctuation_and_length(text)
|
||||||
cmd_exit = conn.cmd_exit
|
cmd_exit = conn.cmd_exit
|
||||||
for cmd in cmd_exit:
|
for cmd in cmd_exit:
|
||||||
if text == cmd:
|
if text == cmd:
|
||||||
@@ -123,7 +125,7 @@ async def analyze_intent_with_llm(conn, text):
|
|||||||
# 对话历史记录
|
# 对话历史记录
|
||||||
dialogue = conn.dialogue
|
dialogue = conn.dialogue
|
||||||
try:
|
try:
|
||||||
intent_result = await conn.intent.detect_intent(dialogue.dialogue, text)
|
intent_result = await conn.intent.detect_intent(conn, dialogue.dialogue, text)
|
||||||
|
|
||||||
# 尝试解析JSON结果
|
# 尝试解析JSON结果
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class IntentProviderBase(ABC):
|
|||||||
logger.bind(tag=TAG).debug("Set LLM for intent provider")
|
logger.bind(tag=TAG).debug("Set LLM for intent provider")
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def detect_intent(self, dialogue_history: List[Dict], text: str) -> str:
|
async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str:
|
||||||
"""
|
"""
|
||||||
检测用户最后一句话的意图
|
检测用户最后一句话的意图
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
from ..base import IntentProviderBase
|
from ..base import IntentProviderBase
|
||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
|
import re
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
|
|
||||||
@@ -20,42 +20,90 @@ class IntentProvider(IntentProviderBase):
|
|||||||
格式化后的系统提示词
|
格式化后的系统提示词
|
||||||
"""
|
"""
|
||||||
intent_list = []
|
intent_list = []
|
||||||
|
|
||||||
|
"""
|
||||||
|
"continue_chat": "1.继续聊天, 除了播放音乐和结束聊天的时候的选项, 比如日常的聊天和问候, 对话等",
|
||||||
|
"end_chat": "2.结束聊天, 用户发来如再见之类的表示结束的话, 不想再进行对话的时候",
|
||||||
|
"play_music": "3.播放音乐, 用户希望你可以播放音乐, 只用于播放音乐的意图"
|
||||||
|
"""
|
||||||
for key, value in self.intent_options.items():
|
for key, value in self.intent_options.items():
|
||||||
if key == "play_music":
|
if key == "play_music":
|
||||||
intent_list.append(f"{value} [歌名]")
|
intent_list.append("3.播放音乐, 用户希望你可以播放音乐, 只用于播放音乐的意图")
|
||||||
|
elif key == "end_chat":
|
||||||
|
intent_list.append("2.结束聊天, 用户发来如再见之类的表示结束的话, 不想再进行对话的时候")
|
||||||
|
elif key == "continue_chat":
|
||||||
|
intent_list.append("1.继续聊天, 除了播放音乐和结束聊天的时候的选项, 比如日常的聊天和问候, 对话等")
|
||||||
else:
|
else:
|
||||||
intent_list.append(value)
|
intent_list.append(value)
|
||||||
|
|
||||||
|
# "如果是唱歌、听歌、播放音乐,请指定歌名,格式为'播放音乐 [识别出的歌名]'。\n"
|
||||||
|
# "如果听不出具体歌名,可以返回'随机播放音乐'。\n"
|
||||||
|
# "只需要返回意图结果的json,不要解释。"
|
||||||
|
# "返回格式如下:\n"
|
||||||
prompt = (
|
prompt = (
|
||||||
"你是一个意图识别助手。你需要根据和用户的对话记录,重点分析用户的最后一句话,判断用户意图属于以下哪一类:\n"
|
"你是一个意图识别助手。你需要根据和用户的对话记录,重点分析用户的最后一句话,判断用户意图属于以下哪一类(使用<start>和<end>标志):\n"
|
||||||
f"{', '.join(intent_list)}\n"
|
"<start>"
|
||||||
"如果是唱歌、听歌、播放音乐,请指定歌名,格式为'播放音乐 [识别出的歌名]'。\n"
|
f"{', '.join(intent_list)}"
|
||||||
"如果听不出具体歌名,可以返回'随机播放音乐'。\n"
|
"<end>\n"
|
||||||
"只需要返回意图结果的json,不要解释。"
|
"你需要按照以下的步骤处理用户的对话"
|
||||||
"返回格式如下:\n"
|
"1. 思考出对话的意图是哪一类的"
|
||||||
"{intent: '用户意图'}"
|
"2. 属于1和2的意图, 直接返回,返回格式如下:\n"
|
||||||
|
"{intent: '用户意图'}\n"
|
||||||
|
"3. 属于3的意图,则继续分析用户希望播放的音乐\n"
|
||||||
|
"4. 如果无法识别出具体歌名,可以返回'随机播放音乐'\n"
|
||||||
|
"{intent: '播放音乐 [获取的音乐名字]'}\n"
|
||||||
|
"下面是几个处理的示例(思考的内容不返回, 只返回json部分, 无额外的内容)\n"
|
||||||
|
"```"
|
||||||
|
"用户: 你今天怎么样?\n"
|
||||||
|
"思考(不返回): 用户发来的数据是一个问候语,属于继续聊天的意图, 是种类1, 种类1的需求是直接返回\n"
|
||||||
|
"返回结果: {intent: '继续聊天'}\n"
|
||||||
|
"```"
|
||||||
|
"用户: 我今天有点累了, 我们明天再聊吧\n"
|
||||||
|
"思考(不返回): 用户表达了今天不想继续对话,属于结束聊天的意图, 是种类2, 种类2的需求是直接返回\n"
|
||||||
|
"返回结果: {intent: '结束聊天'}\n"
|
||||||
|
"```"
|
||||||
|
"用户: 我今天有点累了, 我们明天再聊吧\n"
|
||||||
|
"思考(不返回): 用户表达了今天不想继续对话,属于结束聊天的意图, 是种类2, 种类2的需求是直接返回\n"
|
||||||
|
"返回结果: {intent: '结束聊天'}\n"
|
||||||
|
"```"
|
||||||
|
"用户: 你可以播放一首中秋月给我听吗\n"
|
||||||
|
"思考(不返回): 用户表达了想听音乐的续签,属于播放音乐的意图, 是种类3, 种类3的需求需要继续判断播放的音乐, 这里用户希望的歌曲名明确给出是中秋月\n"
|
||||||
|
"返回结果: {intent: '播放音乐 [中秋月]'}\n"
|
||||||
|
"```"
|
||||||
|
"你现在可以使用的音乐的名称如下(使用<start>和<end>标志):\n"
|
||||||
)
|
)
|
||||||
return prompt
|
return prompt
|
||||||
|
|
||||||
async def detect_intent(self, dialogue_history: List[Dict], text:str) -> str:
|
async def detect_intent(self, conn, dialogue_history: List[Dict], text:str) -> str:
|
||||||
logger.bind(tag=TAG).info(f"分析用户意图: {text}")
|
|
||||||
if not self.llm:
|
if not self.llm:
|
||||||
raise ValueError("LLM provider not set")
|
raise ValueError("LLM provider not set")
|
||||||
|
|
||||||
# 构建用户最后一句话的提示
|
# 构建用户最后一句话的提示
|
||||||
msgStr = ""
|
msgStr = ""
|
||||||
for msg in dialogue_history:
|
|
||||||
if msg.role == "user":
|
# 只使用最后两句即可
|
||||||
msgStr += f"User: {msg.content}\n"
|
if len(dialogue_history) >= 2:
|
||||||
elif msg.role== "assistant":
|
# 保证最少有两句话的时候处理
|
||||||
msgStr += f"Assistant: {msg.content}\n"
|
msgStr += f"{dialogue_history[-2].role}: {dialogue_history[-2].content}\n"
|
||||||
|
msgStr += f"{dialogue_history[-1].role}: {dialogue_history[-1].content}\n"
|
||||||
|
|
||||||
msgStr += f"User: {text}\n"
|
msgStr += f"User: {text}\n"
|
||||||
user_prompt = f"请分析用户的意图:\n{msgStr}"
|
user_prompt = f"当前的对话如下:\n{msgStr}"
|
||||||
|
prompt_music = f"{self.promot}\n<start>{conn.music_handler.music_files}\n<end>"
|
||||||
|
logger.bind(tag=TAG).debug(f"User prompt: {prompt_music}")
|
||||||
# 使用LLM进行意图识别
|
# 使用LLM进行意图识别
|
||||||
intent = self.llm.response_no_stream(
|
intent = self.llm.response_no_stream(
|
||||||
system_prompt=self.promot,
|
system_prompt=prompt_music,
|
||||||
user_prompt=user_prompt
|
user_prompt=user_prompt
|
||||||
)
|
)
|
||||||
intent_result = intent.strip()
|
# 使用正则表达式提取大括号中的内容
|
||||||
logger.bind(tag=TAG).info(f"意图识别结果: {intent_result}")
|
# 使用正则表达式提取 {} 中的内容
|
||||||
return intent_result
|
match = re.search(r'\{.*?\}', intent)
|
||||||
|
if match:
|
||||||
|
result = match.group(0) # 获取匹配到的内容(包含 {})
|
||||||
|
print(result) # 输出:{intent: '播放音乐 [中秋月]'}
|
||||||
|
intent = result
|
||||||
|
else:
|
||||||
|
intent = "{intent: '继续聊天'}"
|
||||||
|
logger.bind(tag=TAG).info(f"Detected intent: {intent}")
|
||||||
|
return intent.strip()
|
||||||
@@ -7,7 +7,7 @@ logger = setup_logging()
|
|||||||
|
|
||||||
|
|
||||||
class IntentProvider(IntentProviderBase):
|
class IntentProvider(IntentProviderBase):
|
||||||
async def detect_intent(self, dialogue_history: List[Dict], text: str) -> str:
|
async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str:
|
||||||
"""
|
"""
|
||||||
默认的意图识别实现,始终返回继续聊天
|
默认的意图识别实现,始终返回继续聊天
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -11,19 +11,31 @@ from cozepy import Coze, TokenAuth, Message, ChatStatus, MessageContentType, Cha
|
|||||||
TAG = __name__
|
TAG = __name__
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
|
|
||||||
|
|
||||||
class LLMProvider(LLMProviderBase):
|
class LLMProvider(LLMProviderBase):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.personal_access_token = config.get("personal_access_token")
|
self.personal_access_token = config.get("personal_access_token")
|
||||||
self.bot_id = config.get("bot_id")
|
self.bot_id = config.get("bot_id")
|
||||||
self.user_id = config.get("user_id")
|
self.user_id = config.get("user_id")
|
||||||
|
self.session_conversation_map = {} # 存储session_id和conversation_id的映射
|
||||||
|
|
||||||
def response(self, session_id, dialogue):
|
def response(self, session_id, dialogue):
|
||||||
coze_api_token = self.personal_access_token
|
coze_api_token = self.personal_access_token
|
||||||
coze_api_base = COZE_CN_BASE_URL
|
coze_api_base = COZE_CN_BASE_URL
|
||||||
|
|
||||||
last_msg = next(m for m in reversed(dialogue) if m["role"] == "user")
|
last_msg = next(m for m in reversed(dialogue) if m["role"] == "user")
|
||||||
|
|
||||||
coze = Coze(auth=TokenAuth(token=coze_api_token), base_url=coze_api_base)
|
coze = Coze(auth=TokenAuth(token=coze_api_token), base_url=coze_api_base)
|
||||||
|
conversation_id = self.session_conversation_map.get(session_id)
|
||||||
|
|
||||||
|
# 如果没有找到conversation_id,则创建新的对话
|
||||||
|
if not conversation_id:
|
||||||
|
conversation = coze.conversations.create(
|
||||||
|
messages=[
|
||||||
|
]
|
||||||
|
)
|
||||||
|
conversation_id = conversation.id
|
||||||
|
self.session_conversation_map[session_id] = conversation_id # 更新映射
|
||||||
|
|
||||||
for event in coze.chat.stream(
|
for event in coze.chat.stream(
|
||||||
bot_id=self.bot_id,
|
bot_id=self.bot_id,
|
||||||
@@ -31,6 +43,7 @@ class LLMProvider(LLMProviderBase):
|
|||||||
additional_messages=[
|
additional_messages=[
|
||||||
Message.build_user_question_text(last_msg["content"]),
|
Message.build_user_question_text(last_msg["content"]),
|
||||||
],
|
],
|
||||||
|
conversation_id=conversation_id,
|
||||||
):
|
):
|
||||||
if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA:
|
if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA:
|
||||||
print(event.message.content, end="", flush=True)
|
print(event.message.content, end="", flush=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user