diff --git a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java index ffe7ff3c..10135cef 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java +++ b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java @@ -78,6 +78,8 @@ public interface Constant { */ String TOKEN_HEADER = "token"; + String AUTHORIZATION = "Authorization"; + /** * 路径分割符 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java index f770c79f..35cde8aa 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java @@ -58,6 +58,12 @@ public class ShiroConfig { filters.put("oauth2", new Oauth2Filter()); shiroFilter.setFilters(filters); + //添加Shiro的内置过滤器 + /*anon:无需认证就可以访问 + authc:必须认证了才能让问 + user:必须拥有,记住我功能,才能访问 + perms:拥有对某个资源的权限才能访问 + role:拥有某个角色权限才能访问*/ Map filterMap = new LinkedHashMap<>(); filterMap.put("/webjars/**", "anon"); filterMap.put("/druid/**", "anon"); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java b/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java index 85c5952b..af3f451c 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java @@ -4,11 +4,16 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import lombok.AllArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.*; import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.RenException; +import xiaozhi.common.page.TokenDTO; import xiaozhi.common.utils.Result; import xiaozhi.common.validator.AssertUtils; +import xiaozhi.modules.security.dao.SysUserTokenDao; import xiaozhi.modules.security.dto.LoginDTO; import xiaozhi.modules.security.password.PasswordUtils; import xiaozhi.modules.security.service.CaptchaService; @@ -31,6 +36,7 @@ public class LoginController { private final SysUserTokenService sysUserTokenService; private final CaptchaService captchaService; + private static final Logger logger = LoggerFactory.getLogger(LoginController.class); @GetMapping("/captcha") @Operation(summary = "验证码") @@ -44,7 +50,7 @@ public class LoginController { @PostMapping("/login") @Operation(summary = "登录") - public Result login( @RequestBody LoginDTO login) { + public Result login(@RequestBody LoginDTO login) { // 验证是否正确输入验证码 boolean validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha()); if (!validate) { @@ -84,4 +90,21 @@ public class LoginController { } + @GetMapping("/info") + @Operation(summary = "用户信息获取") + public Result info(@RequestHeader("Authorization")String authorization) { + logger.info("the authorization:{}", authorization); + + String token; + if (StringUtils.isBlank(authorization) && authorization.contains("Bearer ")) { + throw new RenException(ErrorCode.UNAUTHORIZED); + } + token = authorization.replace("Bearer ", ""); + if (StringUtils.isBlank(token)) { + throw new RenException(ErrorCode.UNAUTHORIZED); + } + SysUserDTO sysUserDTO = sysUserTokenService.getUserByToken(token); + Result result = new Result(); + return result.ok(sysUserDTO); + } } \ No newline at end of file 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 3d8cf0cb..26486b7b 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 @@ -11,6 +11,7 @@ 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.exception.RenException; import xiaozhi.common.utils.HttpContextUtils; import xiaozhi.common.utils.JsonUtils; import xiaozhi.common.utils.Result; @@ -89,12 +90,22 @@ public class Oauth2Filter extends AuthenticatingFilter { * 获取请求的token */ private String getRequestToken(HttpServletRequest httpRequest) { + String token; //从header中获取token - String token = httpRequest.getHeader(Constant.TOKEN_HEADER); + String authorization = httpRequest.getHeader(Constant.AUTHORIZATION); + if (StringUtils.isBlank(authorization) && authorization.contains("Bearer ")) { + throw new RenException(ErrorCode.UNAUTHORIZED); + } + token = authorization.replace("Bearer ", ""); //如果header中不存在token,则从参数中获取token if (StringUtils.isBlank(token)) { - token = httpRequest.getParameter(Constant.TOKEN_HEADER); + authorization = httpRequest.getParameter(Constant.AUTHORIZATION); + + if (StringUtils.isBlank(authorization) && authorization.contains("Bearer ")) { + throw new RenException(ErrorCode.UNAUTHORIZED); + } + token = authorization.replace("Bearer ", ""); } return token; } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Realm.java b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Realm.java index 3f8a3eeb..8c297e17 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Realm.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/oauth2/Oauth2Realm.java @@ -6,12 +6,15 @@ import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; 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.controller.LoginController; import xiaozhi.modules.security.entity.SysUserTokenEntity; import xiaozhi.modules.security.service.ShiroService; import xiaozhi.modules.sys.entity.SysUserEntity; @@ -30,6 +33,8 @@ public class Oauth2Realm extends AuthorizingRealm { @Resource private ShiroService shiroService; + private static final Logger logger = LoggerFactory.getLogger(Oauth2Realm.class); + @Override public boolean supports(AuthenticationToken token) { return token instanceof Oauth2Token; @@ -82,6 +87,11 @@ public class Oauth2Realm extends AuthorizingRealm { userDetail.setToken(accessToken); //账号锁定 + if (userDetail.getStatus() == null) { + logger.error("账号状态异常,status 不能为空"); + throw new DisabledAccountException(MessageUtils.getMessage(ErrorCode.ACCOUNT_DISABLE)); + } + if (userDetail.getStatus() == 0) { throw new LockedAccountException(MessageUtils.getMessage(ErrorCode.ACCOUNT_LOCK)); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/SysUserTokenService.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/SysUserTokenService.java index 220b18b5..74882dea 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/SysUserTokenService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/SysUserTokenService.java @@ -1,9 +1,11 @@ package xiaozhi.modules.security.service; import xiaozhi.common.page.PageData; +import xiaozhi.common.page.TokenDTO; import xiaozhi.common.service.BaseService; import xiaozhi.common.utils.Result; import xiaozhi.modules.security.entity.SysUserTokenEntity; +import xiaozhi.modules.sys.dto.SysUserDTO; import java.util.Map; @@ -19,7 +21,9 @@ public interface SysUserTokenService extends BaseService { * * @param userId 用户ID */ - Result createToken(Long userId); + Result createToken(Long userId); + + SysUserDTO getUserByToken(String token); /** * 退出 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/SysUserTokenServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/SysUserTokenServiceImpl.java index 903ca242..caa24801 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/SysUserTokenServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/SysUserTokenServiceImpl.java @@ -1,6 +1,9 @@ package xiaozhi.modules.security.service.impl; import cn.hutool.core.date.DateUtil; +import lombok.AllArgsConstructor; +import xiaozhi.common.exception.ErrorCode; +import xiaozhi.common.exception.RenException; import xiaozhi.common.page.TokenDTO; import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.utils.HttpContextUtils; @@ -10,18 +13,23 @@ import xiaozhi.modules.security.entity.SysUserTokenEntity; import xiaozhi.modules.security.oauth2.TokenGenerator; import xiaozhi.modules.security.service.SysUserTokenService; import org.springframework.stereotype.Service; +import xiaozhi.modules.sys.dto.SysUserDTO; +import xiaozhi.modules.sys.service.SysUserService; import java.util.Date; +@AllArgsConstructor @Service public class SysUserTokenServiceImpl extends BaseServiceImpl implements SysUserTokenService { + + private final SysUserService sysUserService; /** * 12小时后过期 */ private final static int EXPIRE = 3600 * 12; @Override - public Result createToken(Long userId) { + public Result createToken(Long userId) { //用户token String token; @@ -70,6 +78,20 @@ public class SysUserTokenServiceImpl extends BaseServiceImpl { SysUserDTO getByUsername(String username); + SysUserDTO getByUserId(Long userId); + void save(SysUserDTO dto); void delete(Long[] ids); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java index 5c3a10c5..8f576137 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java @@ -41,6 +41,13 @@ public class SysUserServiceImpl extends BaseServiceImpl + \ No newline at end of file diff --git a/main/manager-web/src/apis/httpRequest.js b/main/manager-web/src/apis/httpRequest.js index 76acd776..98f47bf7 100755 --- a/main/manager-web/src/apis/httpRequest.js +++ b/main/manager-web/src/apis/httpRequest.js @@ -1,6 +1,7 @@ -import {goToPage, showDanger, showWarning} from '../utils/index' +import {goToPage, showDanger, showWarning, isNotNull} from '../utils/index' import Constant from '../utils/constant' import Fly from 'flyio/dist/npm/fly'; +import store from '../store/index' const fly = new Fly() // 设置超时 @@ -25,7 +26,9 @@ function sendRequest() { _url: '', _responseType: undefined, // 新增响应类型字段 'send'() { - this._header.token = localStorage.getItem(Constant.STORAGE_KEY.TOKEN) + if(isNotNull(store.getters.getToken)){ + this._header.Authorization = 'Bearer ' + (JSON.parse(store.getters.getToken)).token + } // 打印请求信息 fly.request(this._url, this._data, { @@ -43,7 +46,7 @@ function sendRequest() { } }).catch((res) => { // 打印失败响应 - console.log(res) + console.log('catch', res) httpHandlerError(res, this._failCallback) }) return this @@ -97,6 +100,7 @@ function sendRequest() { */ // 在错误处理函数中添加日志 function httpHandlerError(info, callBack) { + console.log('httpHandlerError', info) /** 请求成功,退出该函数 可以根据项目需求来判断是否请求成功。这里判断的是status为200的时候是成功 */ let networkError = false diff --git a/main/manager-web/src/apis/module/user.js b/main/manager-web/src/apis/module/user.js index 94de7b3f..444d5d76 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`).method('POST') + RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/login`) + .method('POST') .data(loginForm) .success((res) => { RequestService.clearRequestTime() @@ -19,7 +20,8 @@ export default { }, // 获取用户信息 getUserInfo(callback) { - RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/info`).method('GET') + RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/info`) + .method('GET') .success((res) => { RequestService.clearRequestTime() callback(res) @@ -32,7 +34,8 @@ export default { }, // 获取设备信息 getHomeList(callback) { - RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/device/bind`).method('GET') + RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/device/bind`) + .method('GET') .success((res) => { RequestService.clearRequestTime() callback(res) @@ -76,14 +79,14 @@ export default { }, // 获取验证码 getCaptcha(uuid, callback) { - + RequestService.sendRequest() .url(`${getServiceUrl()}/api/v1/user/captcha?uuid=${uuid}`) .method('GET') .type('blob') .header({ 'Content-Type': 'image/gif', - 'Pragma': 'No-cache', + 'Pragma': 'No-cache', 'Cache-Control': 'no-cache' }) .success((res) => { @@ -91,7 +94,7 @@ export default { callback(res); }) .fail((err) => { // 添加错误参数 - + }).send() }, // 注册账号 @@ -105,4 +108,70 @@ export default { .fail(() => { }).send() }, + + // 保存设备配置 + saveDeviceConfig(device_id, configData, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/user/configDevice/${device_id}`) + .method('PUT') + .data(configData) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('保存配置失败:', err); + RequestService.reAjaxFun(() => { + this.saveDeviceConfig(device_id, configData, callback); + }); + }).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(); + }, + } diff --git a/main/manager-web/src/components/AddDeviceDialog.vue b/main/manager-web/src/components/AddDeviceDialog.vue new file mode 100644 index 00000000..a9eec57c --- /dev/null +++ b/main/manager-web/src/components/AddDeviceDialog.vue @@ -0,0 +1,75 @@ + + + + + \ No newline at end of file diff --git a/main/manager-web/src/components/DeviceItem.vue b/main/manager-web/src/components/DeviceItem.vue new file mode 100644 index 00000000..0ac1f294 --- /dev/null +++ b/main/manager-web/src/components/DeviceItem.vue @@ -0,0 +1,83 @@ + + + + \ No newline at end of file diff --git a/main/manager-web/src/components/HeaderBar.vue b/main/manager-web/src/components/HeaderBar.vue new file mode 100644 index 00000000..ac87a339 --- /dev/null +++ b/main/manager-web/src/components/HeaderBar.vue @@ -0,0 +1,122 @@ + + + + + \ No newline at end of file diff --git a/main/manager-web/src/components/HelloWorld.vue b/main/manager-web/src/components/HelloWorld.vue deleted file mode 100644 index 2589ba46..00000000 --- a/main/manager-web/src/components/HelloWorld.vue +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - diff --git a/main/manager-web/src/router/index.js b/main/manager-web/src/router/index.js index 41f69a21..e4b7f700 100644 --- a/main/manager-web/src/router/index.js +++ b/main/manager-web/src/router/index.js @@ -1,8 +1,5 @@ import Vue from 'vue' import VueRouter from 'vue-router' -import Welcome from '../views/welcome.vue' -import Login from '../views/login.vue' -import Register from '@/views/register.vue' Vue.use(VueRouter) @@ -10,33 +7,36 @@ const routes = [ { path: '/', name: 'welcome', - component: Login + component: function () { + return import('../views/login.vue') + } + }, + { + path: '/role-config', + name: 'RoleConfig', + component: function () { + return import('../views/roleConfig.vue') + } }, { path: '/login', name: 'login', - // route level code-splitting - // this generates a separate chunk (about.[hash].js) for this route - // which is lazy-loaded when the route is visited. component: function () { - return import(/* webpackChunkName: "about" */ '../views/login.vue') + return import('../views/login.vue') } }, { path: '/home', name: 'home', component: function () { - return import(/* webpackChunkName: "about" */ '../views/home.vue') + return import('../views/home.vue') } }, { path: '/register', name: 'Register', - // route level code-splitting - // this generates a separate chunk (about.[hash].js) for this route - // which is lazy-loaded when the route is visited. component: function () { - return import(/* webpackChunkName: "about" */ '../views/register.vue') + return import('../views/register.vue') } }, ] diff --git a/main/manager-web/src/store/index.js b/main/manager-web/src/store/index.js index ceffa8e3..0f810894 100644 --- a/main/manager-web/src/store/index.js +++ b/main/manager-web/src/store/index.js @@ -1,14 +1,26 @@ import Vue from 'vue' import Vuex from 'vuex' +import Constant from '../utils/constant' Vue.use(Vuex) export default new Vuex.Store({ state: { + token: '' }, getters: { + getToken(state) { + if (!state.token) { + state.token = localStorage.getItem('token') + } + return state.token + } }, mutations: { + setToken(state, token) { + state.token = token + localStorage.token = token + } }, actions: { }, diff --git a/main/manager-web/src/views/home.vue b/main/manager-web/src/views/home.vue index 4d0181f5..173bf8f1 100644 --- a/main/manager-web/src/views/home.vue +++ b/main/manager-web/src/views/home.vue @@ -1,346 +1,88 @@ - - - + \ No newline at end of file diff --git a/main/manager-web/src/views/login.vue b/main/manager-web/src/views/login.vue index d52d2c9b..d6083635 100644 --- a/main/manager-web/src/views/login.vue +++ b/main/manager-web/src/views/login.vue @@ -87,18 +87,22 @@ export default { }, methods: { fetchCaptcha() { - this.captchaUuid = getUUID(); + if (this.$store.getters.getToken) { + goToPage('/home') + } else { + this.captchaUuid = getUUID(); - Api.user.getCaptcha(this.captchaUuid, (res) => { - if (res.status === 200) { - const blob = new Blob([res.data], {type: res.data.type}); - this.captchaUrl = URL.createObjectURL(blob); + Api.user.getCaptcha(this.captchaUuid, (res) => { + if (res.status === 200) { + const blob = new Blob([res.data], {type: res.data.type}); + this.captchaUrl = URL.createObjectURL(blob); - } else { - console.error('验证码加载异常:', error); - showDanger('验证码加载失败,点击刷新') - } - }); + } else { + console.error('验证码加载异常:', error); + showDanger('验证码加载失败,点击刷新') + } + }); + } }, async login() { @@ -119,8 +123,12 @@ export default { Api.user.login(this.form, ({data}) => { console.log(data) showSuccess('登陆成功!') + + this.$store.commit('setToken', JSON.stringify(data.data)) + goToPage('/home') }) + setTimeout(() => { this.fetchCaptcha() }, 1000) diff --git a/main/manager-web/src/views/roleConfig.vue b/main/manager-web/src/views/roleConfig.vue new file mode 100644 index 00000000..8c8dba25 --- /dev/null +++ b/main/manager-web/src/views/roleConfig.vue @@ -0,0 +1,264 @@ + + + + + \ No newline at end of file