-
提高单元测试覆盖率的策略 单元测试是软件开发过程中的一个重要环节,它帮助我们确保代码的正确性、稳定性和可维护性。然而,在实际开发过程中,由于时间、人力和技术限制,单元测试的覆盖率可能并不高。那么,如何提高单元测试的覆盖率呢?本文将介绍一些有效的策略。 1. 理解代码结构 首先,要提高单元测试覆盖率,需要对代码的结构有深入的理解。这包括了解代码的功能、模块划分、类之间的关系等。只有充分理解了代码结构,才能编写出有针对性的单元测试用例。 2. 使用单元测试框架 使用单元测试框架可以大大提高测试的效率和覆盖率。一个好的单元测试框架应该具备以下特点: - 支持自动生成测试用例:框架应该能够根据代码的结构和功能,自动生成合适的测试用例。 - 支持参数化测试:通过参数化测试,可以方便地为不同的输入数据编写测试用例,从而提高测试覆盖率。 - 支持并发测试:并发测试可以帮助我们发现潜在的竞态条件问题。 - 提供详细的测试报告:测试框架应该能够清晰地展示测试结果,包括通过率、失败原因等信息。 3. 设计测试用例 设计测试用例时,应遵循以下原则: - 尽可能覆盖所有代码路径:确保每个分支、每个条件分支都有对应的测试用例。 - 测试正常情况和异常情况:对于函数和方法,要分别测试其正常输入和异常输入的情况。同时,也要考虑到边界条件和特殊值的测试。 - 使用mock对象:对于依赖于外部资源的对象(如数据库、网络连接等),可以使用mock对象进行隔离,从而简化测试用例的设计和维护。 4. 遵循TDD(测试驱动开发)原则 TDD是一种先编写测试用例,然后编写实现代码的开发模式。这种模式可以帮助我们从一开始就关注代码的质量,确保每个功能都经过充分的测试。在TDD过程中,我们需要: - 为每个函数或方法编写一个或多个独立的测试用例。 - 从这些测试用例出发,逐步实现代码的功能。 - 在实现代码后,运行这些测试用例,确保功能正确无误。 - 如果测试失败,分析原因并修复代码;如果测试通过,则继续添加更多的测试用例。 5. 定期评估和调整测试策略 随着项目的发展,代码结构和需求可能会发生变化。因此,我们需要定期评估现有的测试覆盖率,并根据需要调整测试策略。这可能包括添加新的测试用例、优化现有测试用例、重构代码以便于测试等。 总之,提高单元测试覆盖率是一个持续的过程,需要我们不断地学习、实践和改进。通过遵循上述策略,我们可以有效地提高单元测试的覆盖率,从而提高软件的质量和可维护性。
-
本篇内容主要介绍使用CodeArts IDE for Java创建工程、代码补全、运行调试代码、Build构建和测试相关的主要功能。一、下载安装CodeArts IDE for JavaCodeArts IDE for Java安装要求 至少需要 2 GB RAM ,但是推荐8 GB RAM; 至少需要 2.5 GB 硬盘空间,推荐SSD; 64位Microsoft Windows 10 下载并安装CodeArts IDE for Java>>>前往CodeArts IDE for Java官方下载页面下载完成后,运行codearts-java-*.exe文件。按照安装导航的步骤,选择个人安装配置进行安装。二、登录CodeArts IDE for Java要求用户登录后才能激活并使用Java语言服务和运行调试的相关功能。打开CodeArts IDE后,可以通过点击右上角的登录入口,使用华为账号进行登录:也可以在新建Java项目时弹出的需要登录弹窗中,点击登录按钮进行登录:三、创建Java工程CodeArts IDE for Java可以通过新建工程向导界面创建Java工程,支持选择四种Java模板创建工程(构建系统:Maven/Gradle,框架:None/SpringBoot),选择创建SpringBoot工程时,可根据需求选择(可多选)相应的第三方依赖,创建成功后第三方依赖被成功写入pom.xml或build.gradle文件。新建工程界面效果如下:CodeArts IDE for Java有三种方式打开新建Java工程向导界面,方式如下:1. 点击“文件->新建->工程”菜单:2. 在欢迎界面点击“新建项目”或者“创建Java工程”:3. 使用快捷键“Alt+P”快速唤出新建工程界面。如果在新窗口创建工程(未打开任何工程),工程创建成功后会直接在当前窗口打开;如果已经打开了一个工程,并在当前窗口通过新建工程向导创建新工程,创建成功的提示弹窗中,点击→当前窗口,工程在当前窗口打开。点击→新窗口,CodeArts IDE将打开新窗口并加载已创建的工程。创建工程实例:四、语言服务初始化与日志查看加载项目时,Java语言服务会进行初始化,右下角状态栏以及消息通知弹窗会有语言服务初始化过程提示信息。语言服务初始化过程中会启动相关服务、下载依赖的Jar包及进行Indexing,此过程受计算机性能、网速等因素影响会耗费一定的时间。点击“输出”视图,切换“SmartAssist Java”,可查看更多语言服务初始化过程的日志:语言服务初始化完成之前,语言服务相关功能(如代码补全、代码重构、查看类型定义等)将不会有很好的体验,此时需耐心等待语言服务初始化完成。当右下角弹出提示“Java Language Server is ready”(Java语言服务已就绪)时(见下图),说明语言服务已初始化完毕,此时可执行文件类和main方法会出现运行按钮,我们就可以开始正常使用语言服务的全部功能。五、代码补全语言服务初始化完成后,即可使用代码补全功能。代码补全列表中包含语言服务的补全及强大的AI智能补全(带有⊕图标),见下图:六、代码运行调试语言服务初始化完成后,在可运行的文件左侧会显示运行图标,选择“Run main in DemoApplication”即可开始运行,选择“Debug main in DemoApplication”即可开始调试,终端视图中将会显示调试运行的信息。也可以点击右上角的运行按钮和调试按钮分别发起运行和调试。可在代码左侧设置断点,Debug进入断点后,可在底部运行调试视图中查看调试相关信息(变量、监视、调用堆栈),也可以对断点进行管理。监视视图和变量视图在调试发起进入断点后,同样支持代码补全能力。运行调试实例:七、构建工程CodeArts IDE for Java会在代码调试运行前自动进行工程构建,也可以通过修改相关运行配置跳过自动构建,见下图:(备注:“SkipBuild”选项默认为false,当设置为true时,发起调试可以跳过构建直接调试,适用于已构建出产物、且代码没有其他修改的情形,可提升调试运行的速度。)我们可以通过点击相关构建菜单主动触发构建工程,“构建”视图右半部分将显示构建过程的相关信息。构建菜单入口如下:还可在构建视图的左侧视图中通过鼠标右键唤出构建菜单:构建工程实例:八、运行单元测试用例CodeArts IDE for Java在编辑器左侧栏和底部测试视图提供单元测试运行功能。运行当前文件所有测试用例,请点击类名所在行的左侧绿色按钮,或者鼠标右键此按钮并选择“运行测试”。底部栏自动打开测试视图,展示当前运行所有用例的状态,右侧输出运行日志。运行当前单个测试用例,请点击测试用例所在行的左侧绿色按钮,或者鼠标右键此按钮并选择“运行测试”,测试视图将展示当前运行的测试用例,点击测试用例后右侧可查看该测试用例的相关运行输出。在测试视图的包名、类名或者方法名上,可以运行该包下、该类下或者某个具体的测试用例,鼠标悬停上去后会显示相关的运行调试按钮,点击后即可发起运行或调试。点击测试视图左侧工具栏的的绿色运行按钮运行可以运行此工程下的所有测试用例。单击或者右键测试视图左侧工具栏的“视图和更多操作”按钮,可以展开更多测试视图相关的操作。如清除所有结果、排序查看、切换树形或列表查看等等。点击清除所有结果,可以清除当前已运行的所有测试用例的结果,相关图标恢复未执行用例前的状态。运行单元测试用例实例:九、Java相关设置CodeArts IDE for Java为Java相关的设置提供了单独的设置入口,点击编辑器左下角的“管理->Java助手设置”菜单可以唤出Java的设置界面:在这个设置界面中,你可以设置工程的SDK、语言级别,Maven、Gradle相关的构建工具的设置以及代码formatter规则设置等等:十、代码formatter规则设置说明CodeArts IDE for Java支持自定义代码formatter规则的校验,暂不支持formatter规则的一键导入和导出(后续会支持)。formatter规则设置好后,在编辑器中选中需要格式化的代码,通过右键唤出“格式化文档”的菜单(或者使用快捷键“Ctrl+Alt+L”)来格式化选中的代码:
HuaweiCloud开发工具
发表于2023-09-14 21:54:06
2023-09-14 21:54:06
最后回复
人工智能-张晨光
2024-12-04 11:15:35
6599 35 -
您好,我在使用OBS javasdk的文件上传API,使用中,我发现,对于大文件,使用官方提供的并发上传文件示例,比直接调 putObject方法速度还慢,具体代码如下:1.并发分段上传代码官方示例https://support.huaweicloud.com/sdk-java-devg-obs/obs_21_0607.html2.调用 putObject 上传文件public SaveObjectResult saveObject(String bucket, String objectKey, MultipartFile file) throws Exception { // 校验桶名称是否正确 bucket = checkBucketName(bucket); InputStream in = file.getInputStream(); SaveObjectResult saveObjectResult = new SaveObjectResult(); saveObjectResult.setOk(Boolean.FALSE); saveObjectResult.setStorageMediaType(OSSTypeEnum.OBS.name()); PutObjectResult putObjectResult = getClient().putObject(bucket, objectKey, in); if (putObjectResult != null && putObjectResult.getStatusCode() == HttpStatus.HTTP_OK) { saveObjectResult.setOk(Boolean.TRUE); saveObjectResult.setAccessUrl(putObjectResult.getObjectUrl()); } return saveObjectResult; }此处直接使用ObsCLient 的 PutObjectResult putObject(String bucketName, String objectKey, InputStream input) 方法。测试结果发现,使用并发分段上传,并没有节省时间,反而比直接使用putObject还慢,为什么会这样?怎么优化,才能让上传更快?
-
Spring工具类封装这里为了方便在非Spring管理下获取Bean对象所封装的工具类创建步骤如下在util包下创建SpringUtils工具类导入如下包import com.begin.utils.StringUtils;import org.springframework.aop.framework.AopContext;import org.springframework.beans.BeansException;import org.springframework.beans.factory.NoSuchBeanDefinitionException;import org.springframework.beans.factory.config.BeanFactoryPostProcessor;import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;其中import com.begin.utils.StringUtils; 为已经封装好的字符串处理工具类设置Spring应用上下文环境private static ConfigurableListableBeanFactory beanFactory;private static ApplicationContext applicationContext;@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException{ SpringUtils.beanFactory = beanFactory;}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException{ SpringUtils.applicationContext = applicationContext;}通过名称获取Bean对象 /** * 获取对象 * * @param name * @return Object 一个以所给名字注册的bean的实例 * @throws org.springframework.beans.BeansException * */ @SuppressWarnings("unchecked") public static <T> T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); }通过requiedType类型获取Bean对象 /** * 获取类型为requiredType的对象 * * @param clz * @return * @throws org.springframework.beans.BeansException * */ public static <T> T getBean(Class<T> clz) throws BeansException { T result = (T) beanFactory.getBean(clz); return result; }判断是否包含Bean对象 /** * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true * * @param name * @return boolean */ public static boolean containsBean(String name) { return beanFactory.containsBean(name); }判断以给定名字注册的bean定义是一个singleton还是一个prototype /** * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) * * @param name * @return boolean * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return beanFactory.isSingleton(name); }获取注册对象的类型 /** * @param name * @return Class 注册对象的类型 * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static Class<?> getType(String name) throws NoSuchBeanDefinitionException { return beanFactory.getType(name); }获取注册对象的别名 /** * 如果给定的bean名字在bean定义中有别名,则返回这些别名 * * @param name * @return * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { return beanFactory.getAliases(name); }获取aop代理对象 /** * 获取aop代理对象 * * @param invoker * @return */ @SuppressWarnings("unchecked") public static <T> T getAopProxy(T invoker) { return (T) AopContext.currentProxy(); }获取当前的环境配置 /** * 获取当前的环境配置,无配置返回null * * @return 当前的环境配置 */ public static String[] getActiveProfiles() { return applicationContext.getEnvironment().getActiveProfiles(); } /** * 获取当前的环境配置,当有多个环境配置时,只获取第一个 * * @return 当前的环境配置 */ public static String getActiveProfile() { final String[] activeProfiles = getActiveProfiles(); return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; }获取配置文件中的值 /** * 获取配置文件中的值 * * @param key 配置文件的key * @return 当前的配置文件的值 * */ public static String getRequiredProperty(String key) { return applicationContext.getEnvironment().getRequiredProperty(key); }全部代码如下package com.begin.utils.spring;import com.begin.utils.StringUtils;import org.springframework.aop.framework.AopContext;import org.springframework.beans.BeansException;import org.springframework.beans.factory.NoSuchBeanDefinitionException;import org.springframework.beans.factory.config.BeanFactoryPostProcessor;import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;/** * spring工具类 方便在非spring管理环境中获取bean * * @author ruoyi */@Componentpublic final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware{ /** Spring应用上下文环境 */ private static ConfigurableListableBeanFactory beanFactory; private static ApplicationContext applicationContext; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { SpringUtils.beanFactory = beanFactory; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringUtils.applicationContext = applicationContext; } /** * 获取对象 * * @param name * @return Object 一个以所给名字注册的bean的实例 * @throws org.springframework.beans.BeansException * */ @SuppressWarnings("unchecked") public static <T> T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); } /** * 获取类型为requiredType的对象 * * @param clz * @return * @throws org.springframework.beans.BeansException * */ public static <T> T getBean(Class<T> clz) throws BeansException { T result = (T) beanFactory.getBean(clz); return result; } /** * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true * * @param name * @return boolean */ public static boolean containsBean(String name) { return beanFactory.containsBean(name); } /** * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) * * @param name * @return boolean * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return beanFactory.isSingleton(name); } /** * @param name * @return Class 注册对象的类型 * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static Class<?> getType(String name) throws NoSuchBeanDefinitionException { return beanFactory.getType(name); } /** * 如果给定的bean名字在bean定义中有别名,则返回这些别名 * * @param name * @return * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { return beanFactory.getAliases(name); } /** * 获取aop代理对象 * * @param invoker * @return */ @SuppressWarnings("unchecked") public static <T> T getAopProxy(T invoker) { return (T) AopContext.currentProxy(); } /** * 获取当前的环境配置,无配置返回null * * @return 当前的环境配置 */ public static String[] getActiveProfiles() { return applicationContext.getEnvironment().getActiveProfiles(); } /** * 获取当前的环境配置,当有多个环境配置时,只获取第一个 * * @return 当前的环境配置 */ public static String getActiveProfile() { final String[] activeProfiles = getActiveProfiles(); return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; } /** * 获取配置文件中的值 * * @param key 配置文件的key * @return 当前的配置文件的值 * */ public static String getRequiredProperty(String key) { return applicationContext.getEnvironment().getRequiredProperty(key); }}
-
最近在开发一个新功能,拿到需求,分析需求,代码编写,单元测试。一看完美,发测试环境。结果测试提bug了。开发环境好好的,测试环境就怎么出问题了呢 。刚好之前听说过远程调试这个事情,但是从来还没验证过,这下刚好来学习下,顺便解决bug。在学习一个新东西前得先知道这个东西是什么,干什么的。所以我先去看了他的基本概念。远程调试(Remote Debugging)是一种在开发环境中通过网络连接到远程计算机或设备进行调试的技术。它允许开发人员在运行着的远程应用程序上设置断点、观察变量和执行代码,以便更好地理解和修复问题。说人话 就是,在我本地ide里面 去调试测试环境代码 。引用大佬的一句话:远程debug可以理解为一种JVM规范,当然其不是单独的一个规范,它是属于JDWP(Java Debug Wire Protocol)协议中的一部分,该协议定义了 Java 虚拟机(JVM)与调试器之间的交互方式。通过 JDWP,开发者可以在开发工具中设置断点、查看变量、执行代码等操作,以便对运行中的 Java 程序进行调试。说直白点就是遵循了该协议的JVM,就支持debug,也支持远程debug。了解完基本概念之后,进入实际操作环节 先说下我自己的环境,本地jdk1.8,idea2023.1,测试系统是centos7.9在服务器上启动远程调试模式:需要在服务器上运行代码,并将其启动为允许远程调试的模式。常见的方法是通过在启动脚本或命令行参数中添加调试选项来实现。例如,对于 Java 代码,可以使用以下命令启动服务器应用程序:java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar your-application.jar 将在服务器上启动一个使用5005端口的远程调试会话。在 IntelliJ IDEA 中配置远程调试:打开 IntelliJ IDEA,打开项目。然后,点击菜单栏,Run--》Edit Configurations弹出窗口中点击 "+" 号,Remote配置远程调试连接参数:Name: 给调试配置取一个名称。Host: 输入服务器的 IP 地址或主机名。Port: 输入服务器上的调试端口号(如上一步中的5005)。Transport: 选择调试传输协议,通常选择 "Socket"。点击保存配置。我本地是中文环境 ,对应的就是 运行,编辑配置,然后点击加号,选择远端。剩下的都一样了。开始远程调试:点击Debug按钮,或者使用快捷键 Shift + F9 启动远程调试会话。IDEA 将连接到服务器上的应用程序。至此就可以在本地远程打断点调试测试环境代码了 。在此做一个记录也希望可以帮到大家
-
大家好,8月份给大家整理带来的是codeArts版本最新的技术干货合集,涉及java,golang,python希望可以帮助到大家。 1.shell检查硬盘状态 https://bbs.huaweicloud.com/forum/thread-149840-1-1.html 2.Golang之sync.Pool对象池对象重用机制总结【转】 https://bbs.huaweicloud.com/forum/thread-0265128851946454378-1-1.html 3.Go defer 去掉闭包函数及用法分析【转】 https://bbs.huaweicloud.com/forum/thread-0236128851600101424-1-1.html 4.Golang中的闭包(Closures)详解【转】 https://bbs.huaweicloud.com/forum/thread-0297128851484457502-1-1.html 5.Go语言之io.ReadAtLeast函数的基本使用和原理解析【转】 https://bbs.huaweicloud.com/forum/thread-0299128851274052618-1-1.html 6.Go语言应用闭包之返回函数【转】 https://bbs.huaweicloud.com/forum/thread-0224128850654756516-1-1.html 7.一文详解Go语言io包中的discard类型【转】 https://bbs.huaweicloud.com/forum/thread-0265128850459435377-1-1.html 8.golang的tunny的用法示例教程【转】 https://bbs.huaweicloud.com/forum/thread-0263128850335015408-1-1.html 9.Go语言tunny的workerWrapper使用教程示例【转】 https://bbs.huaweicloud.com/forum/thread-0265128850197454376-1-1.html 10.GO语言实现日志切割的示例详解【转】 https://bbs.huaweicloud.com/forum/thread-0236128849958046421-1-1.html 11.一文带你了解Golang中的泛型【转】 https://bbs.huaweicloud.com/forum/thread-0263128849797446407-1-1.html 12.Go整合ElasticSearch的示例代码【转】 https://bbs.huaweicloud.com/forum/thread-0299128848232195616-1-1.html 13.Golang仿ps实现获取Linux进程信息【转】 https://bbs.huaweicloud.com/forum/thread-0286128848118160409-1-1.html 14.Go数据结构之HeapMap实现指定Key删除堆【转】 https://bbs.huaweicloud.com/forum/thread-0224128829750512487-1-1.html 15.基于Golang编写贪吃蛇游戏【转】 https://bbs.huaweicloud.com/forum/thread-0275128829535850518-1-1.html 16.用Go获取短信验证码的示例代码【转】 https://bbs.huaweicloud.com/forum/thread-0275128829460656517-1-1.html 17.golang使用sync.singleflight解决热点缓存穿透问题【转】 https://bbs.huaweicloud.com/forum/thread-0275128829216193516-1-1.html 18.vue 实现 pdf 预览功能 https://bbs.huaweicloud.com/forum/thread-0224128574304525257-1-1.html 19.Nacos部署中的一些常见问题 https://bbs.huaweicloud.com/forum/thread-0286128573822017203-1-1.html 20.用ngrok实现内网穿透,一行命令就搞定! https://bbs.huaweicloud.com/forum/thread-0224128573537080255-1-1.html 21.过滤器+slf4j(MDC)实现日志记录请求ID https://bbs.huaweicloud.com/forum/thread-0263128573250127242-1-1.html 22.阿里EasyExcel快速导出demo https://bbs.huaweicloud.com/forum/thread-0265128573093017187-1-1.html 23.业务开发时,接口不能对外暴露怎么办? https://bbs.huaweicloud.com/forum/thread-0224128572747042254-1-1.html 24.centos同步windows时间 https://bbs.huaweicloud.com/forum/thread-0286128572497851201-1-1.html 25.切面实现下单请求防重提交功能(自定义注释@repeatSubmit) https://bbs.huaweicloud.com/forum/thread-0263128572383761239-1-1.html
-
@Cacheable是Spring Boot中用于缓存方法结果的注解,它可以将方法的返回值缓存起来,当再次调用该方法时,如果缓存中存在对应的值,则直接从缓存中获取,而不需要再次执行方法。使用场景:对于计算量大或者耗时较长的方法,可以使用@Cacheable来提高系统性能。对于一些不经常变化的数据,可以使用@Cacheable来减少数据库查询次数。对于一些需要频繁调用的方法,可以使用@Cacheable来提高系统的响应速度。示例代码:首先,需要在pom.xml文件中添加spring-boot-starter-cache依赖:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>然后,在Spring Boot的主配置类上添加@EnableCaching注解,开启缓存功能:@SpringBootApplication @EnableCaching public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }接下来,在需要缓存的方法上添加@Cacheable注解:import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @Service public class UserService { @Cacheable(value = "users", key = "#id") public User getUserById(Long id) { // 模拟从数据库中查询用户信息 return new User(id, "张三"); } }在上面的示例代码中,getUserById方法被@Cacheable注解修饰,表示该方法的结果将被缓存。value属性表示缓存的名称,key属性表示缓存的键,这里使用了SpEL表达式,表示以方法参数id作为缓存的键。当调用getUserById方法时,如果缓存中存在对应的值,则直接从缓存中获取,否则会执行方法并将结果存入缓存。下面再举几个@Cacheable的使用示例:在方法中使用多个参数时,可以使用SpEL表达式来指定缓存的键。例如:@Service public class UserService { @Cacheable(value = "users", key = "#id + #name") public User getUserByIdAndName(Long id, String name) { // 模拟从数据库中查询用户信息 return new User(id, name); } }可以设置缓存的过期时间,例如:@Service public class UserService { @Cacheable(value = "users", key = "#id", unless = "#result == null") public User getUserById(Long id) { // 模拟从数据库中查询用户信息 User user = new User(id, "张三"); if (user.getAge() > 18) { user.setAge(0); } return user; } }在这个示例中,我们设置了缓存的过期时间为5分钟(300秒),并且只有在查询结果不为空时才会缓存。可以在类上使用@Cacheable注解,表示该类的所有方法的结果都会被缓存。例如:@Service @Cacheable public class UserService { public User getUserById(Long id) { // 模拟从数据库中查询用户信息 return new User(id, "张三"); } public User getUserByName(String name) { // 模拟从数据库中查询用户信息 return new User(1L, name); } }在这个示例中,我们使用了@Cacheable注解修饰UserService类,表示该类的所有方法的结果都会被缓存。代码整合示例package com.nezha.controller; import com.nezha.entity.Area; import com.nezha.service.area.AreaService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/area") public class AreaController { @Autowired private AreaService areaService; // @Cacheable添加缓存,相同条件的查询不再查询数据库,而是从缓存中查询 @Cacheable(cacheNames="area",key = "#id") @GetMapping("getAreaById") public Area getAreaById(Integer id) { return areaService.getAreaById(id); } // @CachePut每次都会访问数据库,并更新缓存 @CachePut(cacheNames="area",key = "#id") @PostMapping("updateAreaById") public void updateAreaById(Integer id) { areaService.updateAreaById(id); } // @CacheEvict清除缓存 @CacheEvict(cacheNames="area",key = "#id") @PostMapping("deleteAreaById") public void deleteAreaById(Integer id) { areaService.deleteAreaById(id); } }参考:cid:link_0
-
在Java中,可以使用接口和抽象类来实现多态。当需要选择一个实现类时,可以通过以下步骤进行:定义一个抽象类或接口,包含需要实现的方法签名。创建多个实现类,分别实现抽象类或接口中的方法。在需要选择实现类的地方,使用抽象类或接口类型的引用作为参数,调用方法。在运行时,Java会根据传入的参数类型自动选择合适的实现类。示例代码:// 定义一个抽象类 public abstract class Animal { public abstract void makeSound(); } // 创建两个实现类 public class Dog extends Animal { @Override public void makeSound() { System.out.println("汪汪汪"); } } public class Cat extends Animal { @Override public void makeSound() { System.out.println("喵喵喵"); } } // 在需要选择实现类的地方 public class Main { public static void main(String[] args) { // 使用抽象类类型的引用作为参数 Animal myAnimal = new Dog(); myAnimal.makeSound(); // 输出:汪汪汪 myAnimal = new Cat(); myAnimal.makeSound(); // 输出:喵喵喵 } }
-
以下是使用Java实现网络连通性检测的代码示例:import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; public class NetworkConnectivityTest { public static void main(String[] args) { String host = "www.example.com"; // 要检测的目标主机名或IP地址 try { InetAddress inetAddress = InetAddress.getByName(host); boolean isReachable = inetAddress.isReachable(5000); // 设置超时时间为5秒 if (isReachable) { System.out.println("网络连通性正常"); } else { System.out.println("网络连通性异常"); } } catch (UnknownHostException e) { System.out.println("无法解析主机名或IP地址: " + host); } catch (IOException e) { System.out.println("发生IO异常: " + e.getMessage()); } } }以上代码使用了InetAddress类的isReachable()方法来检测目标主机的网络连通性。该方法会尝试与目标主机建立连接,如果在指定的超时时间内成功建立连接,则判定为网络连通性正常;否则判定为网络连通性异常。在示例中,我们设置了超时时间为5秒,可以根据需要进行调整。如果目标主机名或IP地址无法解析,将会抛出UnknownHostException异常;如果发生其他IO异常,将会抛出IOException异常。
-
简单来说幂等保证了只要调用接口成功,外部多次调用对系统的影响是一致的,也就是一个请求多次重试的问题。需要考虑幂等的场景:客户端存在多次提交或者超时重试的情况;分布式架构中因网络波动采用重试机制,如Dubbo的重试机制;消息推送重试,如MQ重试;不幂等带来的影响:比如在支付场景下,消费者消费扣款消息,对一笔订单进行扣款操作,该扣款操作需要扣除100元,在不幂等的情况下,如果消费者多次发起请求,就会造成多次扣款。幂等性问题解决方案幂等问题演示:执行如下SQL,初始化金额为100;create table order_pay( id int default 0 not null primary key, amt decimal(9, 2) default 0.00 null comment '金额', status int null comment '状态:0已完成/1处理中')comment '订单支付表';INSERT INTO test.order_pak (id, amt, status) VALUES (1, 100.00, 0);pay接口:@RestController@RequestMapping("/test/order-pay")public class OrderPayController { @Autowired IOrderPayService orderPayService; @ApiOperation(httpMethod = "POST", value = "幂等性测试") @PostMapping("pay") public Resp pay(@RequestBody Req<PayReq> req) { PayReq payReq = req.getData(); OrderPay order = orderPayService.getOne(new LambdaQueryWrapper<OrderPay>().eq(OrderPay::getId, payReq.getBizId())); order.setAmt(order.getAmt().subtract(payReq.getAmt())); orderPayService.updateById(order); return Resp.success("剩余金额更新为"+order.getAmt()); }}在不控制幂等的情况下,对pay接口连续发起5次请求,每一次扣减金额为10.00:可以看到最后剩余金额更新为50 .00,我们发起一次请求,应该只扣除10.00,当遇到网络重复或系统bug在不控制幂等的情况下会导致系统进行了多次扣款。那如何进行幂等控制呢?有什么方法呢?幂等性的实现保证幂等性的措施包括但不限于:1、表单提交后按钮置灰限制客户端请求2、添加唯一索引把唯一标识作为唯一索引,在重复创建时会抛出唯一约束异常3、全局唯一ID针对业务操作和内容生产全局唯一ID,在执行操作时判断ID是否存在来判断是否已执行4、一锁二查三更新如果在流程处理过程中,业务要求不能并发执行,可以在流程执行之前根据业务ID获取锁,其他流程执行时获取锁就会失败,也就是同一时间该流程只能有一个能执行成功,执行完成后,释放锁,同时也需要在入口处增加业务状态的判断,以避免对请求的多次处理。这种方式不止可用于幂等的控制,也可以防止并发操作带来的异常。@ApiOperation(httpMethod = "POST", value = "幂等性测试")@PostMapping("pay")public Resp pay(@RequestBody Req<PayReq> req) throws Exception { PayReq payReq = req.getData(); // 获取分布式锁 redisTools.lock(payReq.getBizId().toString()); OrderPay order = orderPayService.getOne(new LambdaQueryWrapper<OrderPay>().eq(OrderPay::getId, payReq.getBizId())); if (order.getStatus()==1){ order.setAmt(order.getAmt().subtract(payReq.getAmt())); order.setStatus(0); orderPayService.updateById(order); } // 释防锁 redisTools.unlock(payReq.getBizId().toString()); return Resp.success("剩余金额为"+order.getAmt());}以下是对pay接口进行并发请求5次的结果:可以看到,只有1个请求可以请求成功,另外4个请求在 redisTools.lock(payReq.getBizId().toString())获取分布式锁的步骤中抛出异常。不加锁与加锁的区别如果把分布式锁的步骤去掉会发生什么样的情况呢?@ApiOperation(httpMethod = "POST", value = "幂等性测试")@PostMapping("pay")public Resp pay(@RequestBody Req<PayReq> req) throws Exception { PayReq payReq = req.getData(); OrderPay order = orderPayService.getOne(new LambdaQueryWrapper<OrderPay>().eq(OrderPay::getId, payReq.getBizId())); if (order.getStatus()==1){ order.setAmt(order.getAmt().subtract(payReq.getAmt())); order.setStatus(0); orderPayService.updateById(order); } return Resp.success("剩余金额为"+order.getAmt());}依旧对pay接口进行并发请求5次,查看日志:5次请求都成功了,但是逻辑执行了5遍,在复杂的业务流程下可能会引发其他不必要问题。总结:在系统设计中,实现幂等的方案有很多种,要根据自身系统的特性优先选择小而巧方案,避免形成过于复杂的方案。作者:sleeper链接:https://juejin.cn/post/6891545350050414605来源:稀土掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
-
问题一:Ubuntu下启动Nacos报错问题描述使用命令sh startup.sh -m standalone启动报错:./startup.sh: 78: ./startup.sh: [[: not found./startup.sh: 88: ./startup.sh: [[: not found./startup.sh: 90: ./startup.sh: [[: not found./startup.sh: 96: ./startup.sh: [[: not found/usr/lib/jvm/java-8-openjdk-amd64/bin/java -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/soft/nacos/logs/java_heapdump.hprof -XX:-UseLargePages -Djava.ext.dirs=/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext:/usr/lib/jvm/java-8-openjdk-amd64/lib/ext:/data/soft/nacos/plugins/cmdb:/data/soft/nacos/plugins/mysql -Xloggc:/data/soft/nacos/logs/nacos_gc.log -verbose:gc -XX:+PrintG***etails -XX:+Print***ateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dnacos.home=/data/soft/nacos -jar /data/soft/nacos/target/nacos-server.jar --spring.config.location=classpath:/,classpath:/config/,file:./,file:./config/,file:/data/soft/nacos/conf/ --logging.config=/data/soft/nacos/conf/nacos-logback.xml./startup.sh: 116: ./startup.sh: [[: not foundnacos is starting,you can check the /data/nacos/logs/start.out解决方法改用命令bash -f ./startup.sh -m standalone启动问题二:Failed to create database '/data/soft/nacos/data/derby-data'问题描述使用命令sh startup.sh -m standalone启动的时候,出现如下报错:Caused by: java.lang.RuntimeException: load schema.sql error.org.apache.commons.dbcp.SQLNestedException: Cannot create PoolableConnectionFactory (Failed to create database '/data/nacos/data/derby-data', see the next exception for details.) at com.alibaba.nacos.config.server.service.LocalDataSourceServiceImpl.reload(LocalDataSourceServiceImpl.java:110) at com.alibaba.nacos.config.server.service.LocalDataSourceServiceImpl.init(LocalDataSourceServiceImpl.java:94) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:363) at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:307) at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:136) ... 70 common frames omitted解决方法删除报错信息中的'/data/nacos/data/derby-data'目录,重新启动。
-
引入阿里easyExcel依赖 <!-- easyexcel --> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.6</version> <exclusions> <exclusion> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> </exclusion> </exclusions> </dependency>自定义的阿里easyexcel拦截器方法import com.alibaba.excel.write.handler.SheetWriteHandler;import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;import com.jerry.util.ExcelUtils;import org.apache.commons.lang.StringUtils;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.apache.poi.hssf.usermodel.HSSFWorkbook;import org.apache.poi.ss.usermodel.*;import org.apache.poi.ss.util.CellRangeAddress;import javax.imageio.ImageIO;import java.awt.image.BufferedImage;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.net.URL;public class SheetWriteHandlerUtil implements SheetWriteHandler { private String title; private String[] header; private String imageurl; private String sheetName; private final Log log = LogFactory.getLog(getClass()); public SheetWriteHandlerUtil(String title, String[] header, String imageurl, String sheetName) { this.title = title; this.header = header; this.imageurl = imageurl; this.sheetName = sheetName; } public SheetWriteHandlerUtil(String sheetName) { this.sheetName = sheetName; } @Override public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { } @Override public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { Workbook workbook = writeWorkbookHolder.getWorkbook(); Sheet sheet = workbook.getSheetAt(0); if (StringUtils.isNotEmpty(sheetName)){ writeWorkbookHolder.getCachedWorkbook().setSheetName(0, sheetName); } if (StringUtils.isNotEmpty(title)){ //设置标题 Row row1 = sheet.createRow(0); row1.setHeight((short) 800); Cell cell = row1.createCell(0); //设置单元格内容 cell.setCellValue(title); CellStyle cellStyle = workbook.createCellStyle(); cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); cellStyle.setAlignment(HorizontalAlignment.LEFT); Font font = workbook.createFont(); font.setBold(true); font.setFontHeight((short) 400); cellStyle.setFont(font); cell.setCellStyle(cellStyle); } if (header != null){ // 第一行大标题占位设置 sheet.addMergedRegionUnsafe(new CellRangeAddress(0, 0, 0, header.length-1)); } if(StringUtils.isNotEmpty(imageurl)){ try { imagewrite(writeWorkbookHolder,writeSheetHolder,imageurl); } catch (IOException e) { e.printStackTrace(); log.error("easyexcel拦截器图片流处理出错"+ e.getMessage()); } } } public void imagewrite(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder,String imageurl) throws IOException { Workbook workbook = writeWorkbookHolder.getWorkbook(); Sheet sheet = workbook.getSheetAt(0); try (ByteArrayOutputStream picOut = new ByteArrayOutputStream()) { //读图片并写入流 BufferedImage bufferedImage = ImageIO.read(new URL(imageurl)); ImageIO.write(bufferedImage, "png", picOut); ExcelUtils.addPictureToSheet(sheet, 3, 3, 0,0,workbook.addPicture(picOut.toByteArray(), HSSFWorkbook.PICTURE_TYPE_PNG),1.3,6.25); } catch (Exception e) { log.debug("", e); } }}自定义的EasyExcelUtils方法类import com.alibaba.excel.EasyExcel;import com.alibaba.excel.write.metadata.style.WriteCellStyle;import com.alibaba.excel.write.metadata.style.WriteFont;import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.apache.poi.ss.usermodel.*;import java.io.ByteArrayOutputStream;import java.util.*;/** * @author wangchaofan-n */public class EasyExcelUtils { private final Log log = LogFactory.getLog(getClass()); /** * * @param list 数据 * @param title 标题 * @param header 动态列 */ public static void exportDetailLeave(List<Map<String,Object>> list, String title, String[] header,ByteArrayOutputStream out,String imageurl) { // 标题样式 WriteCellStyle headWriteCellStyle = getHeadStyle(); // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现 HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, new WriteCellStyle()); EasyExcel.write(out) // 第一行大标题样式设置 .registerWriteHandler(new SheetWriteHandlerUtil(title,header, imageurl, null)) //设置默认样式及写入头信息开始的行数 .useDefaultStyle(true).relativeHeadRowIndex(1) // 表头、内容样式设置 .registerWriteHandler(horizontalCellStyleStrategy) // 统一列宽,如需设置自动列宽则new LongestMatchColumnWidthStyleStrategy() //.registerWriteHandler(new SimpleColumnWidthStyleStrategy(25)) .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) .sheet(title) // 这里放入动态头 .head(head(header)) // 当然这里数据也可以用 List<List<String>> 去传入 .doWrite(detail(list)); } private static List<List<Object>> detail(List<Map<String, Object>> mapList) { List<List<Object>> list = new ArrayList<>(); for (Map<String, Object> map : mapList) { List<Object> objectList = new ArrayList<>(); Set<Map.Entry<String,Object>> entrySet = map.entrySet(); for (Map.Entry<String,Object> entry :entrySet){ objectList.add(entry.getValue()); } list.add(objectList); } return list; } /**动态头传入*/ public static List<List<String>> head(String[] header) { List<String> head0 = null; List<List<String>> list = new LinkedList<>(); for (String h : header) { head0 = new LinkedList<>(); head0.add(h); list.add(head0); } return list; } public static WriteCellStyle getHeadStyle(){ // 头的策略 WriteCellStyle headWriteCellStyle = new WriteCellStyle(); // 背景颜色 headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex()); headWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); // 字体 WriteFont headWriteFont = new WriteFont(); headWriteFont.setFontName("黑体");//设置字体名字 headWriteFont.setFontHeightInPoints((short)15);//设置字体大小 headWriteFont.setBold(true);//字体加粗 headWriteCellStyle.setWriteFont(headWriteFont); //在样式用应用设置的字体; // 样式 headWriteCellStyle.setBorderBottom(BorderStyle.THIN);//设置底边框; headWriteCellStyle.setBottomBorderColor((short) 0);//设置底边框颜色; headWriteCellStyle.setBorderLeft(BorderStyle.THIN); //设置左边框; headWriteCellStyle.setLeftBorderColor((short) 0);//设置左边框颜色; headWriteCellStyle.setBorderRight(BorderStyle.THIN);//设置右边框; headWriteCellStyle.setRightBorderColor((short) 0);//设置右边框颜色; headWriteCellStyle.setBorderTop(BorderStyle.THIN);//设置顶边框; headWriteCellStyle.setTopBorderColor((short) 0); //设置顶边框颜色; headWriteCellStyle.setWrapped(true); //设置自动换行; headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);//设置水平对齐的样式为居中对齐; headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); //设置垂直对齐的样式为居中对齐; //headWriteCellStyle.setShrinkToFit(true);//设置文本收缩至合适 return headWriteCellStyle; }}调用示例 ByteArrayOutputStream out = new ByteArrayOutputStream() // 此处填写表的列名 String[] heads = new String[]{"列名1","学习","题干","选项","答案","解析"}; // 此处为查询数据库语句 List<Map<String,Object>> list = nmgtkmanagemapper.querytkinfobystbhs(stbhs); // 最后一位传参为电子章地址 若需要可传 EasyExcelUtils.exportDetailLeave(list,"表格的大标题",heads,out,null);转载自https://www.cnblogs.com/ruosu0909/p/17638076.html
-
1. 内外网接口微服务隔离 将对外暴露的接口和对内暴露的接口分别放到两个微服务上,一个服务里所有的接口均对外暴露,另一个服务的接口只能内网服务间调用。 该方案需要额外编写一个只对内部暴露接口的微服务,将所有只能对内暴露的业务接口聚合到这个微服务里,通过这个聚合的微服务,分别去各个业务侧获取资源。 该方案,新增一个微服务做请求转发,增加了系统的复杂性,增大了调用耗时以及后期的维护成本。 2. 网关 + redis 实现白名单机制 在 redis 里维护一套接口白名单列表,外部请求到达网关时,从 redis 获取接口白名单,在白名单内的接口放行,反之拒绝掉。 该方案的好处是,对业务代码零侵入,只需要维护好白名单列表即可; 不足之处在于,白名单的维护是一个持续性投入的工作,在很多公司,业务开发无法直接触及到 redis,只能提工单申请,增加了开发成本;另外,每次请求进来,都需要判断白名单,增加了系统响应耗时,考虑到正常情况下外部进来的请求大部分都是在白名单内的,只有极少数恶意请求才会被白名单机制所拦截,所以该方案的性价比很低。 3. 方案三 网关 + AOP 相比于方案二对接口进行白名单判断而言,方案三是对请求来源进行判断,并将该判断下沉到业务侧。避免了网关侧的逻辑判断,从而提升系统响应速度。 我们知道,外部进来的请求一定会经过网关再被分发到具体的业务侧,内部服务间的调用是不用走外部网关的(走 k8s 的 service)。 根据这个特点,我们可以对所有经过网关的请求的header里添加一个字段,业务侧接口收到请求后,判断header里是否有该字段,如果有,则说明该请求来自外部,没有,则属于内部服务的调用,再根据该接口是否属于内部接口来决定是否放行该请求。 该方案将内外网访问权限的处理分布到各个业务侧进行,消除了由网关来处理的系统性瓶颈;同时,开发者可以在业务侧直接确定接口的内外网访问权限,提升开发效率的同时,增加了代码的可读性。 当然该方案会对业务代码有一定的侵入性,不过可以通过注解的形式,最大限度的降低这种侵入性。 具体实操 下面就方案三,进行具体的代码演示。 首先在网关侧,需要对进来的请求header添加外网标识符: from=public @Componentpublic class AuthFilter implements GlobalFilter, Ordered { @Override public Mono < Void > filter ( ServerWebExchange exchange, GatewayFilterChain chain ) { return chain.filter( exchange.mutate().request( exchange.getRequest().mutate().header("id", "").header("from", "public").build()) .build() ); } @Override public int getOrder () { return 0; } }接着,编写内外网访问权限判断的AOP和注解@Aspect@Component@Slf4jpublic class OnlyIntranetAccessAspect { @Pointcut ( "@within(org.openmmlab.platform.common.annotation.OnlyIntranetAccess)" ) public void onlyIntranetAccessOnClass () {} @Pointcut ( "@annotation(org.openmmlab.platform.common.annotation.OnlyIntranetAccess)" ) public void onlyIntranetAccessOnMethed () { } @Before ( value = "onlyIntranetAccessOnMethed() || onlyIntranetAccessOnClass()" ) public void before () { HttpServletRequest hsr = (( ServletRequestAttributes ) RequestContextHolder.getRequestAttributes()) .getRequest (); String from = hsr.getHeader ( "from" ); if ( !StringUtils.isEmpty( from ) && "public".equals ( from )) { log.error ( "This api is only allowed invoked by intranet source" ); throw new MMException ( ReturnEnum.C_NETWORK_INTERNET_ACCESS_NOT_ALLOWED_ERROR); } } } @Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface OnlyIntranetAccess {}最后,在只能内网访问的接口上加上@OnlyIntranetAccess注解即可@GetMapping ( "/role/add" )@OnlyIntranetAccesspublic String onlyIntranetAccess() { return "该接口只允许内部服务调用";版权声明:本文为CSDN博主「肥肥技术宅」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:cid:link_0
-
切面实现下单请求防重提交功能(自定义注释@repeatSubmit) 该切面功能适用场景 下单请求多次提交,导致生成多个相同的订单 解决方案 前端解决:限制点击下单按钮为1次后失效。不足:用户体验下降,能绕过前端 后端解决:防重提交切面解决,自定义注释实现该功能(如下) 步骤: 自定义注释类RepeatSubmit 创建切面并有该注释绑定,在切面类实现防重提交功能: 方式一:引入redission进行加锁5秒,原理redis的setAbsent 方式二:将token存入redis中,下单成功删除token,下单前需要调用获取token接口才能成功下单(类似于加锁,和方式一原理相同) RepeatSubmit /** * 自定义防重提交 */@Documented@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface RepeatSubmit { /** * 防重提交类型。 方法、令牌 */ enum Type {PARAM, TOKEN} /** * 默认防重提交,是方法参数 * @return */ Type limitType() default Type.PARAM; /** * 加锁过期时间,默认5秒 * @return */ long lockTime() default 5;} 自定义切面类/** * 定义一个切面类 */@Aspect@Component@Slf4jpublic class RepeatSubmitAspect { @Autowired private StringRedisTemplate redisTemplate; @Autowired private RedissonClient redissonClient; /** * 定义 @Pointcut注解表达式, * 方式一:@annotation:当执行的方法上拥有指定的注解时生效(我们采用这) */ @Pointcut("@annotation(repeatSubmit)") public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit) { } /** * 环绕通知, 围绕着方法执行 * * @param joinPoint * @param repeatSubmit * @return * @throws Throwable * @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务。 */ @Around("pointCutNoRepeatSubmit(repeatSubmit)") public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo(); // 记录成功或者失败 Boolean res = false; // 防重提交类型 String type = repeatSubmit.limitType().name(); if (type.equalsIgnoreCase(RepeatSubmit.Type.PARAM.name())) { //方式一,参数形式防重提交 long lockTime = repeatSubmit.lockTime(); String ipAddr = CommonUtil.getIpAddr(request); MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); String className = method.getDeclaringClass().getName(); String key = "order-server:repeat_submit"+CommonUtil.MD5(String.format("%s-%s-%s-%s", ipAddr, className, method, accountNo)); // 加锁 //res = redisTemplate.opsForValue().setIfAbsent(key,"1",lockTime, TimeUnit.SECONDS); RLock lock = redissonClient.getLock(key); // 尝试加锁,最多等待2秒,上锁以后5秒自动解锁 [lockTime默认为5s, 可以自定义] res = lock.tryLock(2, lockTime, TimeUnit.SECONDS); } else if (type.equalsIgnoreCase(RepeatSubmit.Type.TOKEN.name())) { //方式二,令牌形式防重提交 String requestToken = request.getHeader("request-token"); if (StringUtils.isBlank(requestToken)) { throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL); } String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, requestToken); /** * 提交表单的token key * key是 order:submit:accountNo:token,然后直接删除成功则完成 */ res = redisTemplate.delete(key); } if (!res) { log.error("订单请求重复提交"); return null; } log.info("环绕通知执行前"); Object obj = joinPoint.proceed(); log.info("环绕通知执行后"); return obj; }}RedissionConfiguration配置类(用于加锁)@Configurationpublic class RedissionConfiguration { @Value("${spring.redis.host}") private String redisHost; @Value("${spring.redis.port}") private String redisPort; @Value("${spring.redis.password}") private String redisPwd; /** * 配置分布式锁的redisson * @return */ @Bean public RedissonClient redissonClient(){ Config config = new Config(); //单机方式 config.useSingleServer().setPassword(redisPwd).setAddress("redis://"+redisHost+":"+redisPort); //集群 //config.useClusterServers().addNodeAddress("redis://192.31.21.1:6379","redis://192.31.21.2:6379") RedissonClient redissonClient = Redisson.create(config); return redissonClient; } /** * 集群模式 * 备注:可以用"rediss://"来启用SSL连接 */ /*@Bean public RedissonClient redissonClusterClient() { Config config = new Config(); config.useClusterServers().setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒 .addNodeAddress("redis://127.0.0.1:7000") .addNodeAddress("redis://127.0.0.1:7002"); RedissonClient redisson = Redisson.create(config); return redisson; }*/}使用说明:在下单接口标注@RepeatSubmit(limitType = RepeatSubmit.Type.TOKEN) 或者@RepeatSubmit(limitType = RepeatSubmit.Type.PARAM) /** * 下单前获取令牌,用于防重提交 * @return */ @GetMapping("token") public JsonData getOrderToken() { Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo(); String token = CommonUtil.getStringNumRandom(32); String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, token); // token 过期时间30分钟 redisTemplate.opsForValue().set(key, String.valueOf(Thread.currentThread().getId()), 30, TimeUnit.MINUTES); return JsonData.buildSuccess(token); } @PostMapping("confirm") @RepeatSubmit(limitType = RepeatSubmit.Type.TOKEN) public void confirmOrder(@RequestBody ConfirmOrderRequest orderRequest, HttpServletResponse response) { // TODO 下单业务 }转载自https://www.cnblogs.com/xietingwei/p/17615725.html
-
什么是JWT JWT 是一个开放标准,它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名 简单来说: 就是通过一定规范来生成token,然后可以通过解密算法逆向解密token,这样就可以获取用户信息 优点 生产的token可以包含基本信息,比如id、用户昵称、头像等信息,避免再次查库 存储在客户端,不占用服务端的内存资源 缺点 token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,如用户权限,密码等 如果没有服务端存储,则不能做登录失效处理,除非服务端改秘钥 JWT格式组成 头部、负载、签名 header+payload+signature 头部:主要是描述签名算法 负载:主要描述是加密对象的信息,如用户的id等,也可以加些规范里面的东西,如iss签发者,exp 过期时间,sub 面向的用户 签名:主要是把前面两部分进行加密,防止别人拿到token进行base解密后篡改token 关于jwt客户端存储 可以存储在cookie,localstorage和sessionStorage里面 代码实现 依赖引入 <!-- JWT相关 --><dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version></dependency>JWTUtil类,封装了生产token方法和校验token方法@Slf4jpublic class JWTUtil { /** * 主题 */ private static final String SUBJECT = "xtw"; /** * 加密秘钥 */ private static final String SECRET = "xtw.com"; /** * 令牌前缀 */ private static final String TOKEN_PREFIX = "xtwcloud-link"; /** * 过期时间 7天 */ private static final long EXPIRED = 1000*60*60*24*7; /** * 生成token * @param loginUser * @return */ public static String geneJsonWebToken(LoginUser loginUser){ if(loginUser == null){ throw new NullPointerException("对象为空"); } String token = Jwts.builder().setSubject(SUBJECT) // 配置payload .claim("head_img",loginUser.getHeadImg()) .claim("account_no",loginUser.getAccountNo()) .claim("username",loginUser.getUserName()) .claim("mail",loginUser.getMail()) .claim("phone",loginUser.getPhone()) .claim("auth",loginUser.getAuth()) .setIssuedAt(new Date()) .setExpiration(new Date(CommonUtil.getCurrentTimestamp()+EXPIRED)) .signWith(SignatureAlgorithm.HS256,SECRET).compact(); token = TOKEN_PREFIX + token; return token; } /** * 校验token * @param token * @return */ public static Claims checkJWT(String token){ try { final Claims claims = Jwts.parser().setSigningKey(SECRET) .parseClaimsJws(token.replace(TOKEN_PREFIX, "")) .getBody(); return claims; }catch (Exception e){ log.info("jwt 校验失败"); return null; } }}案例 用户初次登录或者token过期,登录成功返回JWT令牌 public JsonData login(AccountLoginRequest loginRequest) { // 校验用户账号和密码,等... TODO // 对象变迁 loginRequest -> DO -> DTO(loginUser) // loginRequest:前端输入 // DO:根据loginRequest从数据库中提取 // DTO(loginUser):剔除了DO中的敏感字段,用于生成token // 密码正确 生成JWT_token String token = JWTUtil.geneJsonWebToken(loginUser); return JsonData.buildSuccess(token);}登录后,客户端存储了token,网页跳转只需要校验token//拦截器配置@Configuration@Slf4jpublic class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) // 拦截路径,需要token的接口 .addPathPatterns("[拦截url]") // 放行路径,不需要token的接口,如注册登录接口等... .excludePathPatterns("[放行url]"); }}// 登录拦截器--进行用户的token校验(用户登录成功后网页跳转)@Slf4jpublic class LoginInterceptor implements HandlerInterceptor { //本地线程:在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。 public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行OPTIONS请求 if(HttpMethod.OPTIONS.toString().equalsIgnoreCase(request.getMethod())){ response.setStatus(HttpStatus.NO_CONTENT.value()); return true; } // 从前端获取token String accountToken = request.getHeader("token"); if(StringUtils.isBlank(accountToken)){ accountToken = request.getParameter("token"); } if(StringUtils.isNotBlank(accountToken)){ // 校验token,获取claims Claims claims = JWTUtil.checkJWT(accountToken); if(claims == null){ // 未登录 CommonUtil.sendJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN)); return false; } // 获取用户字段信息 Long accountNo = Long.parseLong(claims.get("account_no").toString()); String headImg = (String) claims.get("head_img"); String username = (String) claims.get("username"); String mail = (String) claims.get("mail"); String phone = (String) claims.get("phone"); String auth = (String) claims.get("auth"); // LoginUser对象需加@Data@Builder@AllArgsConstructor@NoArgsConstructor LoginUser loginUser = LoginUser.builder() .accountNo(accountNo) .auth(auth) .phone(phone) .headImg(headImg) .mail(mail) .userName(username) .build(); //request.setAttribute("loginUser",loginUser); //通过threadlocal threadLocal.set(loginUser); return true; } return false; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { }}转载自https://www.cnblogs.com/xietingwei/p/17567018.html
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签