diff --git a/.gitignore b/.gitignore index ec89322a..4d33c12e 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,7 @@ docs/_build/ # PyBuilder .pybuilder/ target/ +*.pid # Jupyter Notebook .ipynb_checkpoints diff --git a/main/manager-api/README.md b/main/manager-api/README.md index 0435956e..27e4ab10 100644 --- a/main/manager-api/README.md +++ b/main/manager-api/README.md @@ -58,15 +58,16 @@ src/main/java/xiaozhi/AdminApplication.java 执行以下命令生产jar包 ``` -mvn install +mvn clean install ``` 把jar包放在服务器上,执行 ``` -nohup java -jar xiaozhi-esp32-api.jar --spring.profiles.active=dev >/dev/null & +nohup java -jar xiaozhi-esp32-api.jar --spring.profiles.activate=dev ``` + # 接口文档 启动后打开:http://localhost:8002/xiaozhi-esp32-api/doc.html diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Filter.java b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Filter.java index 1520a850..aad888df 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Filter.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Filter.java @@ -1,22 +1,25 @@ package xiaozhi.modules.security.oauth2; +import java.io.IOException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.web.filter.authc.AuthenticatingFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.RequestMethod; + import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.commons.lang3.StringUtils; -import org.apache.shiro.authc.AuthenticationException; -import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.web.filter.authc.AuthenticatingFilter; -import org.springframework.web.bind.annotation.RequestMethod; import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.utils.HttpContextUtils; import xiaozhi.common.utils.JsonUtils; import xiaozhi.common.utils.Result; -import java.io.IOException; - /** * oauth2过滤器 * Copyright (c) 人人开源 All rights reserved. @@ -24,12 +27,15 @@ import java.io.IOException; */ public class Oauth2Filter extends AuthenticatingFilter { + private static final Logger logger = LoggerFactory.getLogger(Oauth2Filter.class); + @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception { - //获取请求token + // 获取请求token String token = getRequestToken((HttpServletRequest) request); if (StringUtils.isBlank(token)) { + logger.warn("createToken:token is empty"); return null; } @@ -47,15 +53,18 @@ public class Oauth2Filter extends AuthenticatingFilter { @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { - //获取请求token,如果token不存在,直接返回401 + // 获取请求token,如果token不存在,直接返回401 String token = getRequestToken((HttpServletRequest) request); + if (StringUtils.isBlank(token)) { + logger.warn("onAccessDenied:token is empty"); + HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setContentType("application/json;charset=utf-8"); httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin()); - String json = JsonUtils.toJsonString(new Result().error(ErrorCode.UNAUTHORIZED)); + String json = JsonUtils.toJsonString(new Result().error(ErrorCode.UNAUTHORIZED)); httpResponse.getWriter().print(json); @@ -66,20 +75,19 @@ public class Oauth2Filter extends AuthenticatingFilter { } @Override - protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { + protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, + ServletResponse response) { HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setContentType("application/json;charset=utf-8"); httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin()); try { - //处理登录失败的异常 Throwable throwable = e.getCause() == null ? e : e.getCause(); - Result r = new Result().error(ErrorCode.UNAUTHORIZED, throwable.getMessage()); + Result r = new Result().error(ErrorCode.UNAUTHORIZED, throwable.getMessage()); String json = JsonUtils.toJsonString(r); httpResponse.getWriter().print(json); } catch (IOException e1) { - } return false; @@ -90,7 +98,7 @@ public class Oauth2Filter extends AuthenticatingFilter { */ private String getRequestToken(HttpServletRequest httpRequest) { String token = null; - //从header中获取token + // 从header中获取token String authorization = httpRequest.getHeader(Constant.AUTHORIZATION); if (StringUtils.isNotBlank(authorization) && authorization.startsWith("Bearer ")) { token = authorization.replace("Bearer ", ""); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/AdminController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/AdminController.java new file mode 100644 index 00000000..535fb548 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/AdminController.java @@ -0,0 +1,90 @@ +package xiaozhi.modules.sys.controller; + +import java.util.Map; + +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +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 xiaozhi.common.constant.Constant; +import xiaozhi.common.page.PageData; +import xiaozhi.common.utils.Result; +import xiaozhi.common.validator.ValidatorUtils; +import xiaozhi.modules.sys.dto.AdminPageUserDTO; +import xiaozhi.modules.sys.service.SysUserService; +import xiaozhi.modules.sys.vo.AdminPageUserVO; + +/** + * 管理员控制层 + * + * @author zjy + * @since 2025-3-25 + */ +@AllArgsConstructor +@RestController +@RequestMapping("/admin") +@Tag(name = "管理员管理") +public class AdminController { + private final SysUserService sysUserService; + + @GetMapping("/users") + @Operation(summary = "分页查找用户") + @RequiresPermissions("sys:role:superAdmin") + @Parameters({ + @Parameter(name = "mobile", description = "用户手机号码", required = false), + @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true), + @Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true), + }) + public Result> pageUser( + @Parameter(hidden = true) @RequestParam Map params) { + AdminPageUserDTO dto = new AdminPageUserDTO(); + dto.setMobile((String) params.get("mobile")); + dto.setLimit((String) params.get(Constant.LIMIT)); + dto.setPage((String) params.get(Constant.PAGE)); + + ValidatorUtils.validateEntity(dto); + PageData page = sysUserService.page(dto); + return new Result>().ok(page); + } + + @PutMapping("/users/{id}") + @Operation(summary = "重置密码") + @RequiresPermissions("sys:role:superAdmin") + public Result update( + @PathVariable Long id) { + String password = sysUserService.resetPassword(id); + return new Result().ok(password); + } + + @DeleteMapping("/users/{id}") + @Operation(summary = "用户删除") + @RequiresPermissions("sys:role:superAdmin") + public Result delete(@PathVariable Long id) { + sysUserService.delete(new Long[] { id }); + return new Result<>(); + } + + @GetMapping("/device/all") + @Operation(summary = "分页查找设备") + @RequiresPermissions("sys:role:superAdmin") + @Parameters({ + @Parameter(name = "keywords", description = "设备关键词", required = false), + @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true), + @Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true), + }) + public Result pageDevice( + @Parameter(hidden = true) @RequestParam Map params) { + // TODO 等设备功能模块写好 + return new Result().error(600, "等设备功能模块写好"); + } +} diff --git a/main/manager-api/src/main/resources/logback-spring.xml b/main/manager-api/src/main/resources/logback-spring.xml index 68ea4cf6..514086d2 100644 --- a/main/manager-api/src/main/resources/logback-spring.xml +++ b/main/manager-api/src/main/resources/logback-spring.xml @@ -1,35 +1,139 @@ - - - + + - + + + + + + + + + ${LOG_HOME} + true + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}) - %msg%n + UTF-8 + + + + + + ${LOG_HOME}/xiaozhi-esp32-api.log + + ${LOG_HOME}/xiaozhi-esp32-api.%d{yyyy-MM-dd}.%i.log + 10MB + 30 + 2GB + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + + + ${LOG_HOME}/error.log + + ERROR + + + ${LOG_HOME}/error.%d{yyyy-MM-dd}.%i.log + 10MB + 30 + 1GB + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + - - + + + + + + + + - + + + + + + + + + + + + + - + + + ${LOG_HOME}/xiaozhi-esp32-api.log - - /system/logs/admin.%d{yyyy-MM-dd}.%i.log - - 1MB - - 7 + ${LOG_HOME}/xiaozhi-esp32-api.%d{yyyy-MM-dd}.%i.log + 10MB + 30 + 2GB - - + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 - - - + + + + ${LOG_HOME}/error.log + + ERROR + + + ${LOG_HOME}/error.%d{yyyy-MM-dd}.%i.log + 10MB + 30 + 1GB + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + + + + + + + + + + + - \ No newline at end of file + + + true + + diff --git a/main/manager-web/README.md b/main/manager-web/README.md index a81c8ff6..945ea29d 100644 --- a/main/manager-web/README.md +++ b/main/manager-web/README.md @@ -6,8 +6,6 @@ 开发使用代码编辑器,导入项目时,选择`manager-web`文件夹作为项目目录 -参照[manager前后端接口协议](https://app.apifox.com/invite/project?token=H_8qhgfjUeaAL0wybghgU)开发 - ``` npm install ``` diff --git a/main/manager-web/src/apis/module/agent.js b/main/manager-web/src/apis/module/agent.js new file mode 100644 index 00000000..034cbf8a --- /dev/null +++ b/main/manager-web/src/apis/module/agent.js @@ -0,0 +1,100 @@ +import RequestService from '../httpRequest' +import {getServiceUrl} from '../api' + + +export default { + // 获取智能体列表 + getAgentList(callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/agent/list`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail(() => { + RequestService.reAjaxFun(() => { + this.getAgentList(callback); + }); + }).send(); + }, + // 添加智能体 + addAgent(agentName, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/agent`) + .method('POST') + .data({agentName: agentName}) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail(() => { + RequestService.reAjaxFun(() => { + this.addAgent(agentName, callback); + }); + }).send(); + }, + // 删除智能体 + deleteAgent(agentId, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/agent/${agentId}`) + .method('DELETE') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail(() => { + RequestService.reAjaxFun(() => { + this.deleteAgent(agentId, callback); + }); + }).send(); + }, + // 获取智能体配置 + getDeviceConfig(deviceId, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/agent/${deviceId}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('获取配置失败:', err); + RequestService.reAjaxFun(() => { + this.getDeviceConfig(deviceId, callback); + }); + }).send(); + }, + // 配置智能体 + updateAgentConfig(agentId, configData, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/agent/${agentId}`) + .method('PUT') + .data(configData) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail(() => { + RequestService.reAjaxFun(() => { + this.updateAgentConfig(agentId, configData, callback); + }); + }).send(); + }, + // 新增方法:获取智能体模板 + getAgentTemplate(callback) { // 移除templateName参数 + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/agent/template`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('获取模板失败:', err); + RequestService.reAjaxFun(() => { + this.getAgentTemplate(callback); + }); + }).send(); + }, +} diff --git a/main/manager-web/src/apis/module/user.js b/main/manager-web/src/apis/module/user.js index bf3eea34..5f5eb931 100755 --- a/main/manager-web/src/apis/module/user.js +++ b/main/manager-web/src/apis/module/user.js @@ -5,7 +5,8 @@ import {getServiceUrl} from '../api' export default { // 登录 login(loginForm, callback) { - RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/login`) + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/user/login`) .method('POST') .data(loginForm) .success((res) => { @@ -18,51 +19,6 @@ export default { }) }).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() - }, - // 解绑设备 - unbindDevice(device_id, callback) { - RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/device/unbind/${device_id}`) - .method('PUT') - .success((res) => { - RequestService.clearRequestTime(); - callback(res); - }) - .fail(() => { - RequestService.reAjaxFun(() => { - this.unbindDevice(device_id, callback); - }); - }).send() - }, - // 绑定设备 - bindDevice(deviceCode, callback) { - RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/device/bind/${deviceCode}`) - .method('POST') - .success((res) => { - RequestService.clearRequestTime(); - callback(res); - }) - .fail((err) => { - console.error('绑定设备失败:', err); - RequestService.reAjaxFun(() => { - this.bindDevice(deviceCode, callback); - }); - }).send(); - }, // 获取验证码 getCaptcha(uuid, callback) { @@ -71,9 +27,9 @@ export default { .method('GET') .type('blob') .header({ - 'Content-Type': 'image/gif', - 'Pragma': 'No-cache', - 'Cache-Control': 'no-cache' + 'Content-Type': 'image/gif', + 'Pragma': 'No-cache', + 'Cache-Control': 'no-cache' }) .success((res) => { RequestService.clearRequestTime(); @@ -85,7 +41,9 @@ export default { }, // 注册账号 register(registerForm, callback) { - RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/register`).method('POST') + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/user/register`) + .method('POST') .data(registerForm) .success((res) => { RequestService.clearRequestTime() @@ -112,69 +70,7 @@ export default { }); }).send(); }, - // 获取设备配置 - getDeviceConfig(device_id, callback) { - RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/configDevice/${device_id}`) - .method('GET') - .success((res) => { - RequestService.clearRequestTime(); - callback(res); - }) - .fail((err) => { - console.error('获取配置失败:', err); - RequestService.reAjaxFun(() => { - this.getDeviceConfig(device_id, callback); - }); - }).send(); - }, - // 获取所有模型名称 - getModelNames(callback) { - RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/models/names`) - .method('GET') - .success((res) => { - RequestService.clearRequestTime(); - callback(res); - }) - .fail(() => { - RequestService.reAjaxFun(() => { - this.getModelNames(callback); - }); - }).send(); - }, - - // 获取模型音色 - getModelVoices(modelName, callback) { - RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/models/${modelName}/voices`) - .method('GET') - .success((res) => { - RequestService.clearRequestTime(); - callback(res); - }) - .fail(() => { - RequestService.reAjaxFun(() => { - this.getModelVoices(modelName, callback); - }); - }).send(); - }, - // 获取智能体列表 - getAgentList(callback) { - RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/agent`) - .method('GET') - .success((res) => { - RequestService.clearRequestTime(); - callback(res); - }) - .fail(() => { - RequestService.reAjaxFun(() => { - this.getAgentList(callback); - }); - }).send(); - }, - + // 用户信息获取 getUserInfo(callback) { RequestService.sendRequest() .url(`${getServiceUrl()}/api/v1/user/info`) @@ -190,21 +86,74 @@ export default { }) }).send() }, - // 添加智能体 - addAgent(agentName, callback) { + // 修改用户密码 + changePassword(oldPassword, newPassword, successCallback, errorCallback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/agent`) + .url(`${getServiceUrl()}/api/v1/user/change-password`) + .method('PUT') + .data({ + password: oldPassword, + newPassword: newPassword, + }) + .success((res) => { + RequestService.clearRequestTime(); + successCallback(res); + }) + .fail((error) => { + RequestService.reAjaxFun(() => { + this.changePassword(oldPassword, newPassword, successCallback, errorCallback); + }); + }) + .send(); + }, + + // 已绑设备 + getAgentBindDevices(agentId, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agentId}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('获取设备列表失败:', err); + RequestService.reAjaxFun(() => { + this.getAgentBindDevices(agentId, callback); + }); + }).send(); + }, + // 解绑设备 + unbindDevice(device_id, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/user/device/unbind/${device_id}`) + .method('PUT') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('解绑设备失败:', err); + RequestService.reAjaxFun(() => { + this.unbindDevice(device_id, callback); + }); + }).send(); + }, + // 绑定设备 + bindDevice(agentId, code, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agentId}`) .method('POST') - .data({ name: agentName }) + .data({ code }) .success((res) => { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .fail((err) => { + console.error('绑定设备失败:', err); RequestService.reAjaxFun(() => { - this.addAgent(agentName, callback); + this.bindDevice(agentId, code, callback); }); }).send(); }, - } diff --git a/main/manager-web/src/components/AddWisdomBodyDialog.vue b/main/manager-web/src/components/AddWisdomBodyDialog.vue index cca026ef..5925470c 100644 --- a/main/manager-web/src/components/AddWisdomBodyDialog.vue +++ b/main/manager-web/src/components/AddWisdomBodyDialog.vue @@ -4,7 +4,7 @@
- 添加智慧体 + 添加智能体
@@ -12,7 +12,7 @@
*
智慧体名称:
- +
@@ -29,7 +29,7 @@ diff --git a/main/manager-web/src/views/roleConfig.vue b/main/manager-web/src/views/roleConfig.vue index 77ecc85f..cc01e046 100644 --- a/main/manager-web/src/views/roleConfig.vue +++ b/main/manager-web/src/views/roleConfig.vue @@ -1,6 +1,5 @@