From e4907121f4d8580cc00de76d275b2759d51a0012 Mon Sep 17 00:00:00 2001 From: Tyke Chen <190473011+chentyke@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:41:50 +0800 Subject: [PATCH 1/3] fix(manager-api): eliminate premature bean initialization warnings --- .../modules/security/config/ShiroConfig.java | 16 +++-- .../security/config/ShiroConfigTest.java | 60 +++++++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 main/manager-api/src/test/java/xiaozhi/modules/security/config/ShiroConfigTest.java 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 0d0c42d3..9e7c6387 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 @@ -4,16 +4,19 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.config.ShiroFilterConfiguration; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.apache.shiro.web.mgt.WebSecurityManager; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Role; import jakarta.servlet.Filter; import xiaozhi.modules.security.oauth2.Oauth2Filter; @@ -39,7 +42,7 @@ public class ShiroConfig { } @Bean("securityManager") - public SecurityManager securityManager(Oauth2Realm oAuth2Realm, SessionManager sessionManager) { + public WebSecurityManager securityManager(Oauth2Realm oAuth2Realm, SessionManager sessionManager) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(oAuth2Realm); securityManager.setSessionManager(sessionManager); @@ -48,7 +51,8 @@ public class ShiroConfig { } @Bean("shiroFilter") - public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager, SysParamsService sysParamsService) { + public static ShiroFilterFactoryBean shirFilter(@Lazy WebSecurityManager securityManager, + @Lazy SysParamsService sysParamsService) { ShiroFilterConfiguration config = new ShiroFilterConfiguration(); config.setFilterOncePerRequest(true); @@ -101,12 +105,14 @@ public class ShiroConfig { } @Bean("lifecycleBeanPostProcessor") - public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean - public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public static AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor( + @Lazy WebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; diff --git a/main/manager-api/src/test/java/xiaozhi/modules/security/config/ShiroConfigTest.java b/main/manager-api/src/test/java/xiaozhi/modules/security/config/ShiroConfigTest.java new file mode 100644 index 00000000..9b10be11 --- /dev/null +++ b/main/manager-api/src/test/java/xiaozhi/modules/security/config/ShiroConfigTest.java @@ -0,0 +1,60 @@ +package xiaozhi.modules.security.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.apache.shiro.session.mgt.SessionManager; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.WebSecurityManager; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Role; + +import xiaozhi.modules.security.oauth2.Oauth2Realm; +import xiaozhi.modules.sys.service.SysParamsService; + +class ShiroConfigTest { + + @Test + void beanPostProcessorFactoriesDoNotInstantiateShiroConfigOrBusinessDependenciesEarly() throws Exception { + Method lifecycleFactory = ShiroConfig.class.getDeclaredMethod("lifecycleBeanPostProcessor"); + Method filterFactory = ShiroConfig.class.getDeclaredMethod( + "shirFilter", WebSecurityManager.class, SysParamsService.class); + + assertTrue(Modifier.isStatic(lifecycleFactory.getModifiers())); + assertTrue(BeanPostProcessor.class.isAssignableFrom(ShiroFilterFactoryBean.class)); + assertTrue(Modifier.isStatic(filterFactory.getModifiers())); + Lazy securityManagerLazy = filterFactory.getParameters()[0].getAnnotation(Lazy.class); + Lazy sysParamsServiceLazy = filterFactory.getParameters()[1].getAnnotation(Lazy.class); + assertNotNull(securityManagerLazy); + assertNotNull(sysParamsServiceLazy); + assertTrue(securityManagerLazy.value()); + assertTrue(sysParamsServiceLazy.value()); + } + + @Test + void authorizationAdvisorIsInfrastructureAndDefersItsSecurityManager() throws Exception { + Method advisorFactory = ShiroConfig.class.getDeclaredMethod( + "authorizationAttributeSourceAdvisor", WebSecurityManager.class); + + assertTrue(Modifier.isStatic(advisorFactory.getModifiers())); + Lazy securityManagerLazy = advisorFactory.getParameters()[0].getAnnotation(Lazy.class); + assertNotNull(securityManagerLazy); + assertTrue(securityManagerLazy.value()); + assertEquals(BeanDefinition.ROLE_INFRASTRUCTURE, advisorFactory.getAnnotation(Role.class).value()); + } + + @Test + void securityManagerRetainsTheWebSecurityContractRequiredByShiroFilter() throws Exception { + Method securityManagerFactory = ShiroConfig.class.getDeclaredMethod( + "securityManager", Oauth2Realm.class, SessionManager.class); + + assertEquals(WebSecurityManager.class, securityManagerFactory.getReturnType()); + } +} From 0598b096292b46e5cdb293edc2a8c70146ff2811 Mon Sep 17 00:00:00 2001 From: Tyke Chen <190473011+chentyke@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:56:28 +0800 Subject: [PATCH 2/3] fix(manager-api): remove invalid logback startup directives --- .../src/main/resources/logback-spring.xml | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/main/manager-api/src/main/resources/logback-spring.xml b/main/manager-api/src/main/resources/logback-spring.xml index c69d03c0..42bf076f 100644 --- a/main/manager-api/src/main/resources/logback-spring.xml +++ b/main/manager-api/src/main/resources/logback-spring.xml @@ -1,20 +1,8 @@ - - - - - - - - - - ${LOG_HOME} - true - - + From b500d1c6bdd6e8fa70d34e6964cc8daaa390a4ec Mon Sep 17 00:00:00 2001 From: Tyke Chen <190473011+chentyke@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:59:24 +0800 Subject: [PATCH 3/3] fix(manager-api): avoid duplicate address book insert mapping --- .../device/dao/DeviceAddressBookDao.java | 7 ++- .../impl/DeviceAddressBookServiceImpl.java | 2 +- .../mapper/device/DeviceAddressBookDao.xml | 4 +- .../DeviceAddressBookServiceImplTest.java | 46 +++++++++++++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 main/manager-api/src/test/java/xiaozhi/modules/device/service/impl/DeviceAddressBookServiceImplTest.java diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceAddressBookDao.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceAddressBookDao.java index c6ead166..fc7d6428 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceAddressBookDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceAddressBookDao.java @@ -12,6 +12,11 @@ import xiaozhi.modules.device.entity.DeviceAddressBookEntity; @Mapper public interface DeviceAddressBookDao extends BaseMapper { + /** + * 新增设备通讯录记录 + */ + int insertAddressBook(DeviceAddressBookEntity entity); + /** * 获取设备通讯录列表 */ @@ -31,4 +36,4 @@ public interface DeviceAddressBookDao extends BaseMapper macAddresses); -} \ No newline at end of file +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceAddressBookServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceAddressBookServiceImpl.java index 8b6ce468..ff2e4317 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceAddressBookServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceAddressBookServiceImpl.java @@ -167,7 +167,7 @@ public class DeviceAddressBookServiceImpl implements DeviceAddressBookService { alias = generateUniqueAlias(macAddress, targetMac, alias); entity.setAlias(alias); entity.setHasPermission(hasPermission); - deviceAddressBookDao.insert(entity); + deviceAddressBookDao.insertAddressBook(entity); } else { if (alias != null) { updateAlias(macAddress, targetMac, alias); diff --git a/main/manager-api/src/main/resources/mapper/device/DeviceAddressBookDao.xml b/main/manager-api/src/main/resources/mapper/device/DeviceAddressBookDao.xml index 9a74946f..2601f60e 100644 --- a/main/manager-api/src/main/resources/mapper/device/DeviceAddressBookDao.xml +++ b/main/manager-api/src/main/resources/mapper/device/DeviceAddressBookDao.xml @@ -21,7 +21,7 @@ WHERE mac_address = #{macAddress} AND target_mac = #{targetMac} - + INSERT INTO ai_device_address_book (mac_address, target_mac, alias, has_permission, creator, create_date, updater, update_date) VALUES (#{macAddress}, #{targetMac}, #{alias}, #{hasPermission}, #{creator}, NOW(), #{updater}, NOW()) @@ -36,4 +36,4 @@ #{mac} - \ No newline at end of file + diff --git a/main/manager-api/src/test/java/xiaozhi/modules/device/service/impl/DeviceAddressBookServiceImplTest.java b/main/manager-api/src/test/java/xiaozhi/modules/device/service/impl/DeviceAddressBookServiceImplTest.java new file mode 100644 index 00000000..69ae3b6d --- /dev/null +++ b/main/manager-api/src/test/java/xiaozhi/modules/device/service/impl/DeviceAddressBookServiceImplTest.java @@ -0,0 +1,46 @@ +package xiaozhi.modules.device.service.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import xiaozhi.common.redis.RedisUtils; +import xiaozhi.modules.device.dao.DeviceAddressBookDao; +import xiaozhi.modules.device.entity.DeviceAddressBookEntity; +import xiaozhi.modules.device.service.DeviceService; +import xiaozhi.modules.sys.service.SysParamsService; + +class DeviceAddressBookServiceImplTest { + + @Test + void newEntryUsesTheDedicatedAddressBookInsertMapping() { + DeviceAddressBookDao addressBookDao = mock(DeviceAddressBookDao.class); + RedisUtils redisUtils = mock(RedisUtils.class); + DeviceService deviceService = mock(DeviceService.class); + SysParamsService sysParamsService = mock(SysParamsService.class); + DeviceAddressBookServiceImpl service = new DeviceAddressBookServiceImpl( + addressBookDao, redisUtils, deviceService, sysParamsService); + when(addressBookDao.selectOne(any())).thenReturn(null); + when(addressBookDao.selectList(null)).thenReturn(List.of()); + + service.saveOrUpdate("00:11:22:33:44:55", "00:11:22:33:44:66", "living-room", true); + + ArgumentCaptor entityCaptor = ArgumentCaptor.forClass(DeviceAddressBookEntity.class); + verify(addressBookDao).insertAddressBook(entityCaptor.capture()); + DeviceAddressBookEntity entity = entityCaptor.getValue(); + assertEquals("00:11:22:33:44:55", entity.getMacAddress()); + assertEquals("00:11:22:33:44:66", entity.getTargetMac()); + assertEquals("living-room", entity.getAlias()); + assertTrue(entity.getHasPermission()); + verify(addressBookDao, never()).insert(any(DeviceAddressBookEntity.class)); + } +}