快速开发框架若依的基础使用详解

news2024/7/6 18:10:13

Hi I’m Shendi


快速开发框架若依的基础使用详解



最近在为公司制作新的项目,经过了一段时间的技术沉淀,我开始尝试接触市面上用的比较多的快速开发框架,听的最多的当属若依吧

于是就选用了若依



介绍

为什么选?目的是为了提高开发效率,至于能不能提高,得实践才能知晓。


若依的官网:http://www.ruoyi.vip/

是一个快速开发框架,典型的一个后台管理系统。

RuoYi-Vue 是一个 Java EE 企业级快速开发平台,基于经典技术组合(Spring Boot、Spring Security、MyBatis、Jwt、Vue),内置模块如:部门管理、角色用户、菜单及按钮授权、数据权限、系统参数、日志管理、代码生成等。在线定时任务配置;支持集群,支持多数据源,支持分布式事务。


可以通过官网的在线演示看到效果,当然,没有具体使用并没有这方面经验的话,看这些大概都是云里雾里的

若依分几个版本,但都是基于SpringBoot的,这点没有问题,因为SpringBoot是目前Java用的最多的JavaWeb框架了


我目前挑选的是前后端分离版本,若依除此之外还有

  • 微服务版(公司有一定规模了,有很多台服务器的话可以用)
  • 不分离版(现在基本上都前后端分离了,不分离的基本上没人用了吧)


从侧边栏讲起,先大概有一个了解

在这里插入图片描述


首先若依表面上的有三个大部分

  • 系统管理
  • 系统监控
  • 系统工具

其中系统管理是最核心的部分,包含了用户,角色,菜单,部门…,可以说是一个ERP系统了

而这些都是已经做好了的,我们使用的话就不需要自己写登录,做权限,菜单这些了,需要根据自己的业务去更改


若依是使用了目前市面上流行的一些开源框架。Spring系列,比如做权限验证,前后端分离版是用的Spring Security…

我使用了的这段时间,给我的感觉就是”还行“,对我来说,重要的部分就是将权限都做好了,我只需要专心写接口就可以了。大体我也讲不上来,所以写下这篇文章来总结一下。



架构

使用了 Java,Mysql,Redis

具体参考官方文档



项目的下载与导入

我选的前后端分离版,前端使用的vue系列

首先从git上拉项目。或者下载压缩包也行,在官网上进入对应的仓库地址

在这里插入图片描述


我这里直接使用 Git 下载了,因为这样比较快(至于没有 Git 的先要去下载 Git)

打开 Git bash,进入到自己新建的文件夹中,使用 git clone 命令从网络上将git项目克隆下来

git clone https://gitee.com/y_project/RuoYi-Vue.git

ps: 至于 git 的地址,在官方源码的Gitee中点击克隆就可以获取到了

在这里插入图片描述


下载下来的目录结构和仓库展示的目录结构是差不多的


有的时候我们会给文件夹改名称,但是建议如果对这些不是很熟练的话最好先不要改。不然就会和我一样花一些时间去补错误了



其中,ruoyi-ui 是前端项目,这里需要注意一下,前端项目和后端项目不要放到一起,因为前端项目构建的时候会生成 node_modules 文件夹,这个文件夹内会有很多的文件,然后后端导入就会卡死(我Eclipse导入就卡死了,然后改IDEA,后来才想到是不是这个的原因,然后将前端项目移出来就没问题了…)



后端导入

首先从后端开始,导入是非常简单的,如果是IDEA的话,那么直接打开这个项目就可以了,这是一个Maven多模块项目。

如果是 Eclipse 的话,使用Maven的方式导入,方法如下

File -> Import -> Maven -> Exists Maven Project,然后 Root Directory 选择若依的文件夹就好了。

在这里插入图片描述


然后等待右下角进度条开始编译之类的,完成后是这样的

在这里插入图片描述


我现在这个是Eclipse的Java视图,在Eclipse的JavaEE视图或者使用IDEA的话,那么子maven模块会在父maven模块的里面而不是展示为同级


如果你导入不能成功的话,大概率是网络的问题或者Maven的问题,带着报错去参考网上解决方案进行解决



前端导入

那个 ruoyi-ui 就是前端项目。

如果没有 Node 则需要先安装Node,因为这个是基于 Vue 的。

在前端这个地方环境问题可搞了我好久的时间…

就照着基础的vue项目一样,打开命令行,进入前端项目文件夹,使用 npm install 或者 cnpm install 先安装需要的依赖。

npm配置了镜像的话可以用,不然就照着网上的先安装 cnpm。

网络没问题的话,执行命令完成后,依赖安装完后,一般都会出现一大堆的Depar…什么的红色文字的错误,可以先不用去管

在这里插入图片描述



执行 npm run dev 先看看能不能跑起来,一般情况下,从官网拉下里的是可以直接跑起来的。

在这里插入图片描述

像我这样,就跑起来了,会自动打开浏览器,打开前端地址。

下面的 Proxy error 是我没有打开后端,访问不到后端的问题。


当然,我第一次其实是跑不起来的,问题是没有 vue-cli-service,现在能跑起来是因为全局安装了这么个东西。

如果你没有安装的话,找到前端项目下的 package.json,从中找到devDependencies,在里面找到对应的版本

在这里插入图片描述

然后使用 npm install 或者 cnpm install 安装就可以了

例如

cnpm install @vue/cli-service@4.4.6

版本号也是使用 @分隔

如果缺少的是其他依赖也可以使用这样的方式解决



后端运行与配置

首先可以看一下若依官方文档给的项目结构介绍

在这里插入图片描述


主要模块是 admin,所有的接口都在这个模块,包括后面自己写的业务接口建议都放到这个模块

然后是system与common模块

system模块是将增删改查操作都放到里面了,做了分层,Service(提供给接口的服务层),Mapper(与数据库的操作,带xml),domain(JavaBean)

common模块比较杂,工具类都在这里面,包括:

  • Redis工具(直接与Redis操作)
  • SpringUtils(可以拿到SpringBoot的Context,getBean之类的)
  • ServletUtils(使用的ThreadLocal,可以拿到当前的请求与响应,接口参数之类的)

更多的需要自己发现了



framework模块是若依自己编写的包装(框架),这部分让我印象最深的就是权限认证了#-_-#,需求是做第三方登录,但要自定义登陆的话。卡了我一天多时间,主要原因是因为我没接触过Spring Security,关于有做第三方登陆需求的,这部分我会在这篇文章中将经验写出来。


还有几个模块,目前我的需求基本上不需要动这几部分,ruoyi-generator 这个代码生成,可以根据需要去修改配置(yml)



配置

都是SpringBoot项目,首先进入 admin 模块,找到 application.yml,进入,根据自己的需要进行修改。

比较需要注意的地方是

  • ruoyi:profile(上传文件保存的地址)
  • spring:redis:(Redis的配置)

然后进入 application-druid.yml,这个是数据库的配置,其中要配置数据库的密码,数据库名称等看注释操作就行


在父模块文件夹中(RuoYi-Vue),有个sql文件夹,里面有两个sql,需要新建一个数据库进入,然后执行这两个sql

这样就可以运行后端了

运行后,启动成功的话可以在控制台看到启动成功的字样的

在这里插入图片描述



这样,将前面说前端也运行起来,就可以正常使用了。



还有很多地方可以进行配置,但在这里就不过多进行赘述了,熟悉SpringBoot的使用基本上没有什么太大压力。



前端配置

首先可能需要配置的是后端的地址。在 vue.config.js

在这里插入图片描述


我们可能会发现,前端请求的后端地址路由前缀带上了 /dev-api,这是因为做了多环境,使用了代理,后端并不是这样的,所以可以忽略这个。


其余的就是业务的编写了,组件部分都在 Component 文件夹,而页面布局在 layou 文件夹

我们自己写的页面在放 views 文件夹下。



使用

可以先查阅官方文档,有很多使用上面都有写

代码生成

首先需要建立一个sql表,表名称以在generator模块的yml配置前缀开头,如果没有更改的话就是 sys_,可以有多个,使用逗号分隔

然后在系统工具 - 代码生成界面中导入表(可视化的,不多赘述了),有编辑预览和生成,编辑是设置生成代码的参数之类的,觉得没问题就生成

生成后的压缩包里有很多文件(大概好像是9个),包含了一个sql(菜单的增删改查sql语句以及权限等),后端controller、mapper、xml…前端 api.js、view…照着目录结构放到对应目录结构就可以了。



公开接口

这部分官网文档上有,不过不是很显眼,我找了半天,在这里记录下

直接加上 @Anonymous 注解就可以了(前后端分离版),在类上加,类内的所有接口都可访问,在接口上加那就单独公开这个接口


日志

使用 @Log 注解,可以参考若依里的代码,有很多地方都用到了

但需要注意的是,必须要有登录信息,也就是说公开接口不能使用这个注解。

例如

@Log(title = "字典数据", businessType = BusinessType.EXPORT)

还有一个是登录日志,这个可以在登陆类中找到,直接照葫芦画瓢就行了。


国际化

若依是做了国际化的,但不多。用的 i18n,在 admin 模块的 resources 下可以看到 i18n 文件夹,有个message.properties 文件,其中包含了所有的文字…

使用的话就是

MessageUtils.message("message.properties中的key")


第三方登录注册

这篇文章记下这里就结尾吧,因为若依是使用了很多框架的一个快速开发框架。如果熟悉若依所使用的那些框架,那么上手起来没有什么太大的压力。


对于第三方登录,有一个特点,只会给你一个第三方的唯一用户标识,而在若依中,使用的Spring Security(这个我没学过,所以这里记录的可能并不是一个比较好的方法)。

我的方法如下

第三方登录,用户不存在就注册并登录,存在就直接登录

注册很简单,参考若依的注册接口 System 模块的 web.service.SysRegisterService,但因为是第三方登录,没有账号密码,我的做法是生成一个随机的账号,与默认密码…因为照葫芦画瓢,登录是必须要账号和密码的

下面描述的是整个验证流程部分


首先查阅参考若依的登录接口,在 system 模块中 web.service.SysLoginService

它登陆部分代码是这样的

/**
 * 登录验证
 * 
 * @param username 用户名
 * @param password 密码
 * @param code 验证码
 * @param uuid 唯一标识
 * @return 结果
 */
public String login(String username, String password, String code, String uuid)
{
    // 验证码校验
    validateCaptcha(username, code, uuid);
    // 登录前置校验
    loginPreCheck(username, password);
    // 用户验证
    Authentication authentication = null;
    try
    {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        AuthenticationContextHolder.setContext(authenticationToken);
        // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
        authentication = authenticationManager.authenticate(authenticationToken);
    }
    catch (Exception e)
    {
        if (e instanceof BadCredentialsException)
        {
        	// 记录登录日志
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        else
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
            throw new ServiceException(e.getMessage());
        }
    }
    finally
    {
        AuthenticationContextHolder.clearContext();
    }
    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
    LoginUser loginUser = (LoginUser) authentication.getPrincipal();
    recordLoginInfo(loginUser.getUserId());
    // 生成token
    return tokenService.createToken(loginUser);
}

熟悉 Security 的看起来是非常简单的,核心部分就下面这三行。

UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
AuthenticationContextHolder.setContext(authenticationToken);
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager.authenticate(authenticationToken);

将账号和密码构建一个UsernamePasswordAuthenticationToken,然后加入context,最后去调用验证,这是 Spring Security 的一个基础的做法

通过注释找到 UserDetailsServiceImpl 类,就在同一个包下,核心代码是这样的

(这部分我已经改过了,将这个类当作验证类了。)

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
    SysUser user = userService.selectUserByUserName(username);
    if (StringUtils.isNull(user))
    {
        log.info("登录用户:{} 不存在.", username);
        throw new ServiceException("登录用户:" + username + " 不存在");
    }
    else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
    {
        log.info("登录用户:{} 已被删除.", username);
        throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
    }
    else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
    {
        log.info("登录用户:{} 已被停用.", username);
        throw new ServiceException("对不起,您的账号:" + username + " 已停用");
    }

    Object type = ServletUtils.getRequest().getAttribute("type");
    if (type != null) {
    	switch ((int) type) {
    	case SecurityConfig.TYPE_OTHER:
    		passwordService.validateOther(user);
    		break;
    	}
    } else {
    	passwordService.validate(user);
    }

    return createLoginUser(user);
}

看起来就好像是在这验证账号密码,虽然可以这样,但 Spring Security 定义的并不是这样的…

查阅 SecurityConfig 类,这个是 Spring Security 的配置类,在System模块的config下,我们需要在意的是最后一个函数,

   /**
     * 身份认证接口
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }

在这里如果没学过 Spring Security 就开始懵逼起来了,所以我先去学习了Spring Security,流程还是很简单,反正就是在这里设置的 UserDetailsService

因为是第三方登录,用户不需要输入账号密码,那么我拿到的密码就是加密后的密码,这样就和普通登录不一样了,就会导致认证不通过

经过 debug 后发现,首先走的是 AuthenticationManagerBuilder,这个不用管,只要知道这个里面有 ProviderManager 就行了,ProviderManager 是个管理器,具体的我也不记得了,它里面有AuthenticationProvider列表,而AuthenticationProvider这个东西就是需要解决掉的东西

private List<AuthenticationProvider> providers = Collections.emptyList();

Spring Security 默认使用的是 DaoAuthenticationProvider,跟着debug可以看到

需要注意的就两个函数,一个additionalAuthenticationChecks,一个retrieveUser

	@Override
	@SuppressWarnings("deprecation")
	protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		if (authentication.getCredentials() == null) {
			this.logger.debug("Failed to authenticate since no credentials provided");
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
		String presentedPassword = authentication.getCredentials().toString();
		if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
			this.logger.debug("Failed to authenticate since password does not match stored value");
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
	}

	@Override
	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		}
		catch (UsernameNotFoundException ex) {
			mitigateAgainstTimingAttack(authentication);
			throw ex;
		}
		catch (InternalAuthenticationServiceException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}

首先执行的是 retrieveUser 函数,这个函数会去调用若依的 UserDetailsServiceImpl 的 loadUserByUsername 函数

执行完后,会执行 additionalAuthenticationChecks 函数,这个函数进行账号密码验证,而若依是在 UserDetailsServiceImpl 的 loadUserByUsername 验证的,验证完后还会在验证一遍,因为账号密码的方式相同,所以不会有什么问题,但对我来说,传递的密码是加密的,所以不需要函数内进行加密,于是就要干掉这个类,在网上找了很久都找不到办法,就只能自己慢慢尝试了,这里直接写结果了

首先新建一个类,继承 DaoAuthenticationProvider 类,然后覆盖 additionalAuthenticationChecks 函数,在这个函数内做自己的操作。


然后还要覆盖一个函数 supports,这个是代表支持不支持当前传递的 authentication,例如UsernamePasswordAuthenticationToken

我直接返回 true 就行了,不支持的话那么就不会被覆盖

代码如下

/**
 * 自定义
 * 创建时间 2023年7月29日
 * @author Shendi
 */
public class MyAuthenticationProvider extends DaoAuthenticationProvider {

	@Override
	protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		// 有两种类型
		Object type = ServletUtils.getRequest().getAttribute("type");
        if (type != null) {
        	// 已经在 UserDetailsService 做了判断
        	switch ((int) type) {
        	case SecurityConfig.TYPE_OTHER:
        		break;
        	}
        } else {
        	if (authentication.getCredentials() == null) {
        		this.logger.debug("Failed to authenticate since no credentials provided");
        		throw new BadCredentialsException(this.messages
        				.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        	}
        	String presentedPassword = authentication.getCredentials().toString();
        	if (!this.getPasswordEncoder().matches(presentedPassword, userDetails.getPassword())) {
        		this.logger.debug("Failed to authenticate since password does not match stored value");
        		throw new BadCredentialsException(this.messages
        				.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        	}
        }
	}
	
	/** 是否支持指定类型的 authentication */
	public boolean supports(Class<?> authentication) {
//        return (MyAuthenticationProvider.class.isAssignableFrom(authentication));
		return true;
    }
	
}


然后就是在 SecurityConfig 中配置了,直接上代码

@Bean  
public MyAuthenticationProvider myAuP() {  
	MyAuthenticationProvider myAuP = new MyAuthenticationProvider();  
    myAuP.setUserDetailsService(userDetailsService);  
    myAuP.setPasswordEncoder(bCryptPasswordEncoder());  
    return myAuP;
}

/**
 * 身份认证接口
 */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
	auth.authenticationProvider(myAuP());
//        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}


这样就ok了.




END

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/816369.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【前端设计】尝试一文搞懂verilog parameter的全部细节

这里是尼德兰的喵芯片设计相关文章&#xff0c;欢迎您的访问&#xff01; 如果文章对您有所帮助&#xff0c;期待您的点赞收藏&#xff01; 让我们一起为成为芯片前端全栈工程师而努力&#xff01; 一个参数的标准定义呢应该是这样的&#xff1a; parameter type range name …

SHELL——备份脚本

编写脚本&#xff0c;使用mysqldump实现分库分表备份。 1、获取分库备份的库名列表 [rootweb01 scripts]# mysql -uroot -p123456 -e "show databases;" | egrep -v "Database|information_schema|mysql|performance_schema|sys" mysql: [Warning] Using …

二十三种设计模式第十九篇--命令模式

命令模式是一种行为设计模式&#xff0c;它将请求封装成一个独立的对象&#xff0c;从而允许您以参数化的方式将客户端代码与具体实现解耦。在命令模式中&#xff0c;命令对象充当调用者和接收者之间的中介。这使您能够根据需要将请求排队、记录请求日志、撤销操作等。 命令模…

华为数通HCIP-IP组播基础

ospf、isis、BGP--ping通&#xff0c;单播路由--单播路由表&#xff1b; mpls--单播标签互通 点到多点业务流量下发 1、通过广播 一对所有发送&#xff1b; 缺陷&#xff1a;导致流量的有偿性、安全性得不到保障&#xff1b; 2、通过单播 一对一发送&#xff1b; 缺陷&…

嵌入式软件和硬件首先说一下怎么入门?

1&#xff0c;编程。这个你是否懂C语言编程&#xff0c;能够熟练或者熟悉使用C语言编写一段程序。不懂学习C语言。 2&#xff0c;单片机。你是否了解什么是单片机&#xff0c;单片机是做什么的。不懂的话&#xff0c;最好先了解一下什么是单片机&#xff0c;看下书&#xff0c…

万里腾飞终有路,国产替代正当时

2023年7月27-28日&#xff0c;I-CAR 2023 第六届细胞免疫疗法深度聚焦论坛在上海建工浦江皇冠假日酒店隆重举办&#xff0c;本次大会主要围绕实体瘤、质量控制&产业化、通用CAR-T疗法、免疫治疗新风口四大主题展开&#xff0c;就细胞免疫治疗快速发展崛起的同时&#xff0c…

openGauss学习笔记-26 openGauss 高级数据管理-约束

文章目录 openGauss学习笔记-26 openGauss 高级数据管理-约束26.1 NOT NULL约束26.2 UNIQUE约束26.3 PRIMARY KEY26.4 FOREIGN KEY26.5 CHECK约束 openGauss学习笔记-26 openGauss 高级数据管理-约束 约束子句用于声明约束&#xff0c;新行或者更新的行必须满足这些约束才能成…

ubuntu下,在vscode中使用platformio出现 Can not find working Python 3.6+ Interpreter的问题

有一段时间没有使用platformio了&#xff0c;今天突然使用的时候&#xff0c;发现用不了&#xff0c;报错&#xff1a; Ubuntu PlatformIO: Can not find working Python 3.6 Interpreter. Please install the latest Python 3 and restart VSCode。 上网一查&#xff0c;发现…

最全面的TCP、UDP、Socket、HTTP网络编程面试题

先看一天面试的经验&#xff1a; 第一场&#xff1a; 面试官&#xff1a;你说一下TCP的三次握手 我&#xff1a;第一次Client将SYN置1......、第二次Server收........、 第三次........ 面试官&#xff1a;很难背吧&#xff1f; 我&#xff1a;......是啊&#xff0c;很难&…

【编译】gcc make cmake Makefile CMakeList.txt 区别

文章目录 一 关系二 gcc2.1 编译过程2.2 编译参数2.3 静态库和动态库1 后缀名2 联系与区别 2.4 GDB 调试器1 常用命令 三 make、makefile四 cmake、cmakelist4.1 语法特性4.2 重要命令4.2 重要变量4.3 编译流程4.4 两种构建方式 五 Vscode5.0 常用快捷键5.1 界面5.2 插件5.3 .v…

程序的编译(3/13)

经过预处理后的源文件&#xff0c;褪去一切包装&#xff0c;注释被删除&#xff0c;预处理命令也基本上被处理掉&#xff0c;剩下的就是 C 代码了。接下来的第二步&#xff0c;就进入到编译阶段。编译阶段主要分为两步&#xff1a;第一步&#xff0c;编译器调用一系列解析工具分…

leetcode 面试题 08.05.递归乘法

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;面试题 08.05.递归乘法 思路&#xff1a; A 3 , B 4 &#xff0c;3 * 4 等价于 3 3 3 3。 代码&#xff1a; int multiply(int A, int B){if (!B) {return 0;}return A multiply(A , B - 1); }

23款奔驰AMG GLE53加装原厂HUD抬头显示系统,增加您的行车安全

HUD是平视显示器的简称&#xff0c;它原先是运用在航空器上的飞行辅助仪器。指飞行员不需要低头&#xff0c;就能够看到他需要的重要资讯。由于HUD的方便性以及能够提高飞行安全&#xff0c;这项技术后来也发展到汽车行业。汽车搭载的HUD抬头数字显示功能&#xff0c;是利用光学…

Antd 日期范围选择框选择一个日期后,面板收起

需求 初始状态只有开始时间&#xff0c;如下图⬇️ 产品需要优化体验&#xff1a; 点击结束日期选择日期下拉面板直接收起&#xff0c;而不是需要选择起始时间 思路 需要一段逻辑的open属性控制后续操作还是由组件内部控制 逻辑 第一次打开&#xff0c;open控制选择了&am…

史上最全,商业智能BI干货满满

商业智能BI是什么&#xff1f; 商业智能BI可以实现业务流程和业务数据的规范化、流程化、标准化&#xff0c;打通ERP、OA、CRM等不同业务信息系统&#xff0c;商业智能BI整合归纳企业数据&#xff0c;商业智能BI利用数据可视化满足企业不同人群对数据查询、分析和探索的需求&a…

AOP自定义注解保存到MongoDB数据库

如果你想用mongoDB做全局日志记录&#xff0c;比如记录controller方法执行相关数据&#xff0c;比如像这样。 整个流程&#xff1a;先写自定义注解。定义一个日志对象&#xff0c;字段属性设置好。 写aop 规范自定义注解如何使用&#xff0c;在其中通过切点和反射把相关参数存到…

机器学习笔记之优化算法(五)线搜索方法(步长角度;非精确搜索;Armijo Condition)

机器学习笔记之优化算法——线搜索方法[步长角度&#xff0c;非精确搜索&#xff0c;Armijo Condition] 引言回顾&#xff1a;关于 f ( x k 1 ) ϕ ( α ) f(x_{k1}) \phi(\alpha) f(xk1​)ϕ(α)的一些特性非精确搜索近似求解最优步长的条件 Armijo Condition \text{Armijo…

行业追踪,2023-07-31,板块多数都是指向消费

自动复盘 2023-07-31 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

【数据挖掘竞赛】——科大讯飞:锂离子电池生产参数调控及生产温度预测挑战赛

🤵‍♂️ 个人主页:@Lingxw_w的个人主页 ✍🏻作者简介:计算机科学与技术研究生在读 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ ​ 【科大讯飞】报名链接:https://challenge.xfyun.cn?invitaC…

JAVASE---类和对象

1. 面向对象的初步认知 1.1 什么是面向对象 Java是一门纯面向对象的语言(Object Oriented Program&#xff0c;简称OOP)&#xff0c;在面向对象的世界里&#xff0c;一切皆为对象。面向对象是解决问题的一种思想&#xff0c;主要依靠对象之间的交互完成一件事情。用面向对象的…