IntelliJ+SpringBoot项目实战(十七)--在SpringBoot中整合SpringSecurity和JWT(下B)

news/2025/3/22 2:17:08

八、SpringSecurity实现权限控制        

        在上节中介绍了SpringSecurity登录时从数据库中验证用户以及获取用户的权限集合。本文介绍如何进行权限控制。

        在上节中,虽然实现了从数据库中获取用户并验证密码,但是还没有实现权限的控制,只是将用户的权限从v_user_auth或demo_user_auth中获取了而已。现在介绍如何实现权限的控制。

        在进行权限开发之前,我们需要设计跟权限相关的数据库表,在项目开发中,权限的控制都需要设计用户表、资源表(权限ID+资源URL)、角色表、用户角色关系表、角色权限关系表,除此之外,还需要单位表、单位部门表、部门用户表,不过后面这3个表是跟业务有关。在OpenJweb低代码平台中,用户表为comm_user,角色表为comm_roles,资源表comm_auth,角色权限(资源)关系表comm_role_auth、用户角色关系表 comm_user_role。然后基于这些表再建一个v_user_auth方便用户权限的查询。

        现在我们需要开发2个类,一个是MyFilterInvocationSecurityMetadataSource类,实现SpringSecurity的FilterInvocationSecurityMetadataSource接口,一个是MyAccessDecisionManager,实现AccessDecisionManager接口。下面先贴出实现的代码,然后再简单讲解一下,代码可能有点难度,大家掌握主要功能即可。在openjweb-sys工程实现下面2个类:

package org.openjweb.sys.auth.security;import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;@Component
@Slf4j
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {//private static JdbcTemplate service = JdbcTemplateConfig.getDefaultJdbcTemplate();@Resource(name="jdbcTemplateOne")private JdbcTemplate service;AntPathMatcher antPathMatcher = new AntPathMatcher();/*** 从URL解析获取是否有对应的AUTH,并找出AUTH编码加到数组* @param object* @return* @throws IllegalArgumentException*/@Overridepublic Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {String requestUrl = ((FilterInvocation) object).getRequestUrl();// 首先获取菜单列表List<Map<String, Object>> menuList = null;try {menuList = service.queryForList("select distinct auth_resource ,auth_id from comm_auth where "+ "auth_resource is not null and auth_resource<>'#' and auth_resource like '/api%'  ");// 权限列表} catch (Exception ex) {ex.printStackTrace();}//System.out.println("检查权限:"+requestUrl);//if(requestUrl.equals("/")) {//对/控制权限吗????//	System.out.println("/权限返回......");//	return   SecurityConfig.createList("ROLE_USER");//没有权限清单的,登录后可访问//}if(menuList==null||menuList.size()==0) {log.info("权限清单为空......................");}for (Map<String, Object> map : menuList) {log.info("遍历匹配权限.........");String authResource = map.get("auth_resource").toString();System.out.println(authResource+"比较"+requestUrl);Long authId = new Long(map.get("auth_id").toString());List<Map<String, Object>> roleList = null;log.info("检查匹配:"+authResource+"----"+ requestUrl);if (antPathMatcher.match(authResource, requestUrl)) {try {log.info("正在检查角色权限:,先查权限清单::::::::");log.info(authResource);roleList = service.queryForList("select  a.auth_resource,a.comm_code  from comm_auth a , comm_roles b,"+ "comm_role_auth c where a.auth_id=? and a.auth_id=c.auth_id and c.role_id=b.role_id and a.auth_resource<>'#' and a.auth_resource is not null",new Object[] { authId });} catch (Exception ex) {ex.printStackTrace();}String[] strArray = null;if (roleList != null && roleList.size() > 0) {strArray = new String[roleList.size()];for (int i = 0; i < strArray.length; i++) {strArray[i] = roleList.get(i).get("comm_code").toString();}log.info("返回权限数组。。。。。。。。。。。。。。。。。。。。");return SecurityConfig.createList(strArray);}}}log.info("未在权限清单,返回。。。。。。");return  null;// SecurityConfig.createList("ROLE_LOGIN");//没有权限清单的,都可以访问	,如登录访问用ROLE_USER}@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}@Overridepublic boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);}
}

       在上面的代码中,为了简单起见,没使用mybatis-plus查询数据库,直接使用JdbcTemplate进行查询。另外menuList=service.queryForList这句代码中的sql,是从openjweb项目的comm_auth权限表中获取的,下文会贴出此表的DDL。这个类的主要处理逻辑是:

        (1)首先从数据库中获取到权限清单,或者叫资源清单(这里权限等同于资源,内容包括权限ID(auth_id),URL地址(auth_resource))。这一步的实现就是获取menuList。

        (2)然后循环获得的menuList,从menuList逐条检查auth_resource中的URL是否匹配当前的请求URL地址,就是我们要访问的接口地址。如果检查到auth_resource是请求接口URL的一部分,那么说明这个请求接口受权限管控了,然后就根据角色表、权限表、角色权限关系表查询此权限对应有哪些角色,具体实现看roleList = service.queryForList这句的SQL。查到此权限对应的角色之后,然后将拥有此权限的所有角色,通过strArray[i] = roleList.get(i).get("comm_code").toString();这行代码加到权限数组里,然后通过SecurityConfig.createList(strArray)返回。

         再做下总结,就是此类的作用是,根据请求的URL接口,查询数据库中哪些权限匹配这个接口,找到对应的权限并将权限编码加到数组,然后返回权限数组,这个权限数组返回是通过SecurityConfig.createList(strArray)返回,类型是SpringSecurity框架的ConfigAttribute。

      接下来我们再开发MyAccessDecisionManager,见下面的代码:

package com.openjweb.sys.auth.security;import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;import java.util.Collection;@Component
@Slf4j
public class MyAccessDecisionManager implements AccessDecisionManager {@Overridepublic void decide(Authentication auth, Object object, Collection<ConfigAttribute> ca) {Collection<? extends GrantedAuthority> auths = auth.getAuthorities();for (ConfigAttribute configAttribute : ca) {if ("ROLE_LOGIN".equals(configAttribute.getAttribute())&& auth instanceof UsernamePasswordAuthenticationToken) {return;}if ("ROLE_USER".equals(configAttribute.getAttribute())&& auth instanceof UsernamePasswordAuthenticationToken) {return;}for (GrantedAuthority authority : auths) {log.info("得到的权限-当前权限:" + authority.getAuthority() + "-----" + configAttribute.getAttribute());if (configAttribute.getAttribute().equals(authority.getAuthority())) {log.info("匹配:" + authority.getAuthority() + ",返回");return;}}}log.info("权限无匹配,抛异常..........");throw new AccessDeniedException("权限不足");}@Overridepublic boolean supports(ConfigAttribute attribute) {return true;}@Overridepublic boolean supports(Class<?> clazz) {return true;}}

       在上面的代码中,decide方法的ca参数就是上一个类SecurityConfig.createList(strArray)方法返回的结果,至于是怎么传递到这里,这个是SpringSecurity框架内部的处理流程,我们不需要去深究。这个类的decide方法的作用就是决策,就是将用户拥有的权限和访问这个资源所需要的权限进行循环比对,如果发现了有匹配的权限,就返回,允许访问资源,否则抛出权限不足的异常。如果我们在未登录的时候访问一个需要授权访问的URL,控制台会出现下面的日志:

        得到的权限-当前权限:ROLE_ANONYMOUS-----userRole,这个就是上面代码中我们加的日志语句,可以看出,未登录时,SpringSecurity的 authority.getAuthority()返回的是匿名角色ROLE_ANONYMOUS,后面的configAttribute.getAttribute()返回的是userRole。userRole是第一个类运行后,查询到的访问请求链接要求的权限。

        这2个类开发完成之后,需要在WebSecurityConfig.java中配置上,在此类中增加2个Bean:

        

   @BeanMyAccessDecisionManager cadm() {//System.out.println("加载角色权限设置。。。。。。。。。。。。");return new MyAccessDecisionManager();}@BeanMyFilterInvocationSecurityMetadataSource cfisms() {//System.out.println("加载权限设置。。。。。。。。。。。。");return new MyFilterInvocationSecurityMetadataSource();}

       此类中,原来的http.cors()这段也换掉,替换为:

        

/*  http.cors().and().csrf().disable()//登录表单.formLogin().and().authorizeRequests().antMatchers(ALLOW_URL_LIST).permitAll().anyRequest().authenticated();*///下面是第二阶段整合了数据库权限控制的示例http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O object) {object.setSecurityMetadataSource(cfisms());object.setAccessDecisionManager(cadm());return object;}}).and().formLogin().loginProcessingUrl("/login").permitAll().and().logout().permitAll().and().csrf().disable();

        在上面的代码中,加载了我们上面讲的2个类。

        接下来我们开发一个测试类,在openjweb-core工程创建一个测试接口类AuthDemoApi:

package org.openjweb.core.api;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RequestMapping("/api/auth")
@RestController
public class AuthDemoApi {/*** 此接口的URL加到demo_user_auth中测试权限控制* //测试地址:localhost:8001/api/auth/test* @return*/@RequestMapping("test")public String testauth(){return "hello";//不做权限配置的话,返回hello}@RequestMapping("test2")public String testauth2(){return "hello2";//不做权限配置的话,返回hello}}

       我们可以利用/api/auth/test和/api/auth/test2做测试。注意如果URL没收录到权限定义表里,则此权限只受白名单控制,不受权限控制。如果资源收录到权限表,但是没有分配给任何角色,则也是不受权限控制。当然也可以通过修改代码来改变这个规则。

        然后我们还是创建下数据库表,包括角色表、权限表、角色权限关系表、用户角色关系表、以及针对这几个表,创建一个视图v_user_auth_demo:


CREATE TABLE `comm_roles` (`role_id` bigint(20) NOT NULL,`role_name` varchar(40) NOT NULL,`cls_code` varchar(16) DEFAULT NULL,`tree_code` varchar(100) DEFAULT NULL,`p_tree_code` varchar(100) DEFAULT NULL,`lvl_num` bigint(20) DEFAULT NULL,`is_leaf` char(1) DEFAULT NULL,`comm_code` varchar(16) DEFAULT NULL,`node_name` varchar(80) DEFAULT NULL,`node_desc` varchar(255) DEFAULT NULL,`row_id` varchar(40) NOT NULL,`create_dt` varchar(23) DEFAULT NULL,`update_dt` varchar(23) DEFAULT NULL,`create_uid` varchar(40) DEFAULT NULL,`update_uid` varchar(40) DEFAULT NULL,`sort_no` bigint(20) DEFAULT NULL,`data_flg` varchar(5) DEFAULT NULL,`sys_code` varchar(40) DEFAULT NULL,PRIMARY KEY (`role_id`),UNIQUE KEY `comm_code` (`comm_code`),KEY `idx_1730778572184000913` (`comm_code`),KEY `idx_1730778572184000914` (`role_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色表';INSERT INTO `comm_roles` VALUES ('1', '超级用户', 'COMM', null, null, null, null, 'adminRole', null, null, '1', '2009-05-06 10:57:04', '2013-10-16 20:49:48', 'admin', 'admin', null, 'Y', 'COMM');
INSERT INTO `comm_roles` VALUES ('2', '一般用户', 'COMM', null, null, null, null, 'userRole', null, null, '2', '2009-05-06 10:57:04', '2013-06-18 14:56:16', 'admin', 'admin', null, '1', null);
INSERT INTO `comm_roles` VALUES ('3', 'CRM管理员', 'CRM', null, null, null, null, 'crmAdminRole', null, null, '3', '2009-09-16 22:04:23', '2013-10-16 20:49:48', 'admin', 'admin', null, '1', null);CREATE TABLE `comm_auth` (`auth_id` bigint(20) NOT NULL,`auth_name` varchar(60) NOT NULL,`auth_type` varchar(16) DEFAULT NULL,`auth_resource` varchar(255) NOT NULL,`comm_code` varchar(30) NOT NULL,`cls_code` varchar(10) NOT NULL,`tree_code` varchar(100) DEFAULT NULL,`p_tree_code` varchar(100) DEFAULT NULL,`lvl_num` bigint(20) DEFAULT NULL,`is_leaf` char(1) DEFAULT NULL,`node_name` varchar(80) DEFAULT NULL,`node_desc` varchar(255) DEFAULT NULL,`row_id` varchar(40) NOT NULL,`create_dt` varchar(23) DEFAULT NULL,`update_dt` varchar(23) DEFAULT NULL,`create_uid` varchar(40) DEFAULT NULL,`update_uid` varchar(40) DEFAULT NULL,`sort_no` bigint(20) NOT NULL,`data_flg` varchar(10) NOT NULL,`pic_file` varchar(120) DEFAULT NULL,`sys_role` varchar(40) DEFAULT NULL,`is_assign_to_com` varchar(10) DEFAULT NULL,`sys_code` varchar(40) DEFAULT NULL,`menu_url` varchar(200) NOT NULL,`menu_sort_no` bigint(20) NOT NULL,`new_url` varchar(255) DEFAULT NULL,`is_layui` varchar(10) DEFAULT NULL,`layui_name` varchar(80) DEFAULT NULL,`layui_jump` varchar(255) DEFAULT NULL,`is_vue` varchar(10) DEFAULT NULL,`no_keep_alive` varchar(10) DEFAULT NULL,`always_show` varchar(10) DEFAULT NULL,`vue_path` varchar(400) DEFAULT NULL,`vue_component` varchar(400) DEFAULT NULL,`vue_redirect` varchar(60) DEFAULT NULL,`vue_hidden` varchar(10) DEFAULT NULL,`vue_icon` varchar(60) DEFAULT NULL,PRIMARY KEY (`auth_id`),UNIQUE KEY `comm_code` (`comm_code`),UNIQUE KEY `tree_code` (`tree_code`),KEY `idx_1730778570733000801` (`comm_code`),KEY `idx_1730778570734000802` (`tree_code`),KEY `idx_1730778570734000803` (`auth_name`),KEY `idx_1730778570735000804` (`auth_id`,`data_flg`),KEY `idx_1730778570735000805` (`is_vue`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='权限表';INSERT INTO `comm_auth` VALUES ('2', '基本权限', null, '/api/auth/test', 'AUTH_BASIC', 'COMM', '002', null, null, null, '基本权限', null, 'fsdfddfagfdb', null, '2015-09-20 19:26:00', null, 'admin', '100', 'Y', null, null, null, null, '/login1.jsp', '100', null, null, null, null, null, null, null, null, null, null, null, null);
INSERT INTO `comm_auth` VALUES ('11', '测试权限', null, '/**/*.action,/**/*.do', 'AUTH_TEST', 'COMM', '001', null, null, null, '测试权限', null, '1', null, '2015-09-20 19:26:00', null, 'admin', '100', 'Y', null, null, null, null, '/**/*.action,/**/*.do', '100', null, null, null, null, null, null, null, null, null, null, null, null);
INSERT INTO `comm_auth` VALUES ('1084', '系统管理', null, '#', 'CRM_MENU01', 'CRM', '003', null, null, null, '系统管理', '系统管理/apps/images/ico_xtgl.gif   fa-gears', '5df80291f8c44e48ab32342d904d5cd8', '2019-12-19 21:46:56', '2023-03-17 12:28:53', 'admin', 'system', '20', 'Y', 'layui-icon-set', null, 'Y', null, '#', '20', null, 'Y', 'SystemManagement', '3', 'Y', 'Y', 'Y', '/systemManagement', 'Layout', 'noRedirect', null, 'users-cog');
INSERT INTO `comm_auth` VALUES ('1181', '数据字典权限', null, '/**/test.jsp', 'AUTH_1179', 'COMM', '004', null, null, null, '数据字典权限', 'fdsfsd', '85991d1bac864ca6b7e88f73177bc8bc', null, '2015-09-20 19:26:00', null, 'admin', '100', 'Y', null, null, null, null, '/**/test.jsp', '100', null, null, null, null, null, null, null, null, null, null, null, null);
INSERT INTO `comm_auth` VALUES ('1607993', '信息图片数统计', null, '/comm/listCommReport!viewReport.action?repId=03', 'CRM_MENU0906', 'CRM', '021018', null, null, null, '信息图片数统计', null, '89a97e03998d4b00a296065d16100f76', null, '2015-09-20 19:26:00', null, 'admin', '100', 'Y', null, null, null, null, '/comm/listCommReport!viewReport.action?repId=03', '100', null, null, null, null, null, null, null, null, null, null, null, null);
INSERT INTO `comm_auth` VALUES ('20241128', 'SPRINGBOOT', null, '/api/auth/test2', 'CRM_MENU9911', 'CRM', '999999', null, null, null, '权限检测', '#', 'qwertyuiop1234567890olpokilopo11', '2024-11-28 09:01:00', '2024-11-28 09:01:00', 'system', 'system', '9999', 'Y', null, '1', 'N', 'OA', '/api/111111', '1111', null, 'N', null, null, 'N', 'N', 'N', null, null, null, null, null);CREATE TABLE `comm_user_role` (`serial_no` bigint(20) NOT NULL,`user_id` bigint(20) DEFAULT NULL,`role_id` bigint(20) DEFAULT NULL,`create_dt` varchar(23) DEFAULT NULL,`create_uid` varchar(40) DEFAULT NULL,PRIMARY KEY (`serial_no`),KEY `idx_1730778572600000953` (`user_id`),KEY `idx_1730778572601000954` (`role_id`),CONSTRAINT `FK_COMM_USER_ROLE1` FOREIGN KEY (`user_id`) REFERENCES `comm_user` (`user_id`) ON DELETE CASCADE,CONSTRAINT `FK_COMM_USER_ROLE2` FOREIGN KEY (`role_id`) REFERENCES `comm_roles` (`role_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户角色关系表';INSERT INTO `comm_user_role` VALUES ('1667627586734003778', '1', '1', null, null); 
INSERT INTO `comm_user_role` VALUES ('1667627586736003781', '1', '2', null, null);
INSERT INTO `comm_user_role` VALUES ('1667627586737003782', '1', '3', null, null); CREATE TABLE `comm_role_auth` (`serial_no` bigint(20) NOT NULL,`auth_id` bigint(20) DEFAULT NULL,`role_id` bigint(20) DEFAULT NULL,`create_dt` varchar(23) DEFAULT NULL,`create_uid` varchar(40) DEFAULT NULL,PRIMARY KEY (`serial_no`),UNIQUE KEY `role_id` (`role_id`,`auth_id`),KEY `idx_1730778572192000915` (`role_id`,`auth_id`),KEY `idx_1730778572193000916` (`auth_id`),KEY `idx_1730778572193000917` (`role_id`),CONSTRAINT `PK_COMM_ROLE_AUTH` FOREIGN KEY (`role_id`) REFERENCES `comm_roles` (`role_id`) ON DELETE CASCADE,CONSTRAINT `PK_COMM_ROLE_AUTH2` FOREIGN KEY (`auth_id`) REFERENCES `comm_auth` (`auth_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色权限关系表';INSERT INTO `comm_role_auth` VALUES ('1833', '11', '2', null, null);
INSERT INTO `comm_role_auth` VALUES ('1834', '2', '2', null, null);    
INSERT INTO `comm_role_auth` VALUES ('20241128', '2', '2', null, null);  create  view v_user_auth_demo  as
select a.user_id,a.login_id,b.auth_id,b.comm_code,b.auth_name,b.auth_resource,b.pic_file,b.sort_no,b.menu_url,b.menu_sort_no,b.layui_name,b.layui_jump,b.is_layui
from comm_user a,comm_auth b , comm_user_role d,comm_role_auth e
where  a.user_id = d.user_id
and a.is_in_use='Y'
and d.role_id = e.role_id
and e.auth_id = b.auth_id
and b.data_flg ='Y' ;

        然后我们把CommUserMapp.java中selectAuthorities方法注解中的v_user_auth表改为v_user_auth_demo表。在comm_user表中,amdin用户的user_id是1,在面的SQL中,comm_roles表中,超级管理员的role_id是1,一般用户角色的ID是2,用户角色关系表comm_user_role中,admin用户有超管角色和一般用户2个角色。在权限表comm_auth中,auth_id为2映射的/api/auth/test,auth_id为20241128映射的URL是/api/auth/test2。在角色权限关系表comm_role_auth中,这2个权限都分配给了一般用户的角色。所以admin用户同时拥有这2个角色。

        现在我们启动SpringBoot,分别访问:http://localhost:8001/api/auth/test和http://localhost:8001/api/auth/test2, 第一个访问的时候提示需要登录,输入用户名admin,密码Hello0214@,显示页面:

        调用成功,控制台显示:

        可看到找到一匹配项。http://localhost:8001/api/auth/test2 无论是否登录都可以访问,因为没有授权给任何角色,不过也可以修改代码,这个以后再说。 另外上节的白名单机制没生效是因为我们代码中http.authorizeRequests()的配置和前面不太一样了。以后在项目实际开发中,还需要完善白名单逻辑,比如允许访问静态资源。

        接下来我们还需要演示下权限不足的情况,我们可以把用户admin拥有的访问/api/auth/test的权限删掉,因为此权限分配了了一般用户,所以把admin用户的一般用户角色删掉就可以了,执行下面的SQL:

        delete  from comm_user_role where user_id=1 and role_id=2 ;

        权限删除后,可能因为缓存的原因,需要重启下SpringBoot才能使新权限生效,这个以后需要改造下。现在重启后再访问http://localhost:8001/api/auth/test,登录后会显示权限不足,说明权限控制生效:

        如果我们再把删除的权限再加进来,就又能访问了。再执行

INSERT INTO `comm_user_role` VALUES ('1667627586736003781', '1', '2', null, null);

就可以了。

        在这里顺便说一下,权限控制有的模式是在方法前面加权限注解,这个方式后面会介绍,本文介绍的是只要在数据库中把权限配置好就可以,不需要在程序代码中设置权限注解。在前后端分离项目中,前端也不需要关注权限编码,只需要调用接口就可以了。

        到现在为止,我们已经实现了基于数据库用户、角色、权限等表实现了针对不同URL的权限控制。由于篇幅较长,下个帖子还要继续介绍JWT登录,所以本文就介绍到这里。

 本文完整代码见Github: GitHub - openjweb/cloud at masterOpenJWeb is a java bases low code platform. Contribute to openjweb/cloud development by creating an account on GitHub.icon-default.png?t=O83Ahttps://github.com/openjweb/cloud/tree/master

        


https://dhexx.cn/news/show-5466329.html

相关文章

龙迅#LT6912适用于HDMI2.0转HDMI+LVDS/MIPI,分辨率高达4K60HZ,支持音频和HDCP2.2

1. 描述 LT6912是一款高性能的HDMI2.0转HDMI和LVDS和MIPI转换器。 HDMI2.0 输入和输出均支持高达 6Gbps 的数据速率&#xff0c;为4k60Hz视频提供足够的带宽。此外&#xff0c;还支持 HDCP2.2 进行数据解密&#xff08;无数据 加密&#xff09;。 对于 LVDS 输出&#xff0c…

计算属性和监听属性

Vue.js 中的计算属性与监听属性 Vue.js 是一个流行的前端框架&#xff0c;它提供了许多强大的特性来简化 Web 应用的开发。其中&#xff0c;计算属性&#xff08;Computed Properties&#xff09;和监听属性&#xff08;Watchers&#xff09;是两个非常重要的概念&#xff0c;…

恒创科技:服务器操作系统和客户端操作系统之间的区别

客户端操作系统和服务器操作系统是两种不同的操作系统&#xff0c;旨在满足计算机网络环境中的特定目的。虽然每种类型的操作系统在基本功能方面都有一些相似之处&#xff0c;但它们针对不同的用例进行了优化&#xff0c;并具有针对其特定角色量身定制的特定功能。 什么是服务器…

【排版教程】Word、WPS 分节符(奇数页等) 自动变成 分节符(下一页) 解决办法

毕业设计排版时&#xff0c;一般要求每章节的起始页为奇数页&#xff0c;空白页不显示页眉和页脚。具体做法如下&#xff1a; 1 Word 在一个章节的内容完成后&#xff0c;在【布局】中&#xff0c;点击【分隔符】&#xff0c;然后选择【奇数页】 这样在下一章节开始的时&…

物联网智能项目:智能家居系统的设计与实现

物联网(Internet of Things,IoT)技术正在迅速改变我们的生活方式,特别是在智能家居、工业自动化、环境监控等领域。物联网智能项目通过将设备、传感器、控制器等通过互联网连接,实现设备间的智能交互,带来高效、便捷和智能的体验。本文将介绍一个典型的物联网智能家居项目…

Xilinx PCIe高速接口入门实战(一)

引言&#xff1a;本文对Xilinx 7 Series Intergrated Block for PCI Express PCIe硬核IP进行简要介绍&#xff0c;主要包括7系列FPGA PCIe硬核资源支持、三IP硬核差异、PCIe硬核资源利用等相关内容。 1. 概述 1.1 7系列FPGA PCIe硬件资源支持 7系列FPGA对PCIe接口最大支持如…

一文了解 Hugging Face 平台

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ Hugging Face 是一家专注于人工智能与自然语言处理&#xff08;NLP&#xff09;的科技公司&#xff0c;致力于推动先进的机器学习技术变得更加开放、易用并实现协作。对于普通公众而言&#xff0c;它提…

lambda strem流表达式处理工具

一个通用的lambda stream流处理工具, 包含了工作中绝大部分场景常用的使用方式 import java.math.BigDecimal; import java.util.*; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.funct…

Springboot 修改post请求接口入参或重新赋值

前言 很久之前写过一篇就是自动填充接口参数的&#xff0c;利用的 HandlerMethodArgumentResolver 自定义注解 Springboot Controller接口默认自动填充 业务实体参数值_springboot设置入参默认值-CSDN博客 现在这一篇也差不多&#xff0c;达到的目的就是重新去给post请求的参数…

鸿蒙进阶篇-Stage模型、UIAbility

“在科技的浪潮中&#xff0c;鸿蒙操作系统宛如一颗璀璨的新星&#xff0c;引领着创新的方向。作为鸿蒙开天组&#xff0c;今天我们将一同踏上鸿蒙基础的探索之旅&#xff0c;为您揭开这一神奇系统的神秘面纱。” 各位小伙伴们我们又见面了,我就是鸿蒙开天组,下面让我们进入今…