• [技术干货] 【Spring教程12】Spring框架实战:Spring整合Mybatis全面深入详解 -转载
    学习到这里,已经对Spring有一个简单的认识了,Spring有一个容器,叫做IoC容器,里面保存bean。在进行企业级开发的时候,其实除了将自己写的类让Spring管理之外,还有一部分重要的工作就是使用第三方的技术。前面已经讲了如何管理第三方bean了,下面结合IoC和DI,整合Mybatis,进一步加深对Spring的使用理解。  1 Spring整合Mybatis思路分析 1.1 环境准备 在准备环境的过程中,我们也来回顾下Mybatis开发的相关内容:  1.1.1 步骤1:准备数据库表 Mybatis是来操作数据库表,所以先创建一个数据库及表  create database spring_db character set utf8; use spring_db; create table tbl_account(     id int primary key auto_increment,     name varchar(35),     money double ); 1.1.2 步骤2:创建项目导入jar包 项目的pom.xml添加相关依赖  <dependencies>     <dependency>         <groupId>org.springframework</groupId>         <artifactId>spring-context</artifactId>         <version>5.2.10.RELEASE</version>     </dependency>     <dependency>         <groupId>com.alibaba</groupId>         <artifactId>druid</artifactId>         <version>1.1.16</version>     </dependency>     <dependency>         <groupId>org.mybatis</groupId>         <artifactId>mybatis</artifactId>         <version>3.5.6</version>     </dependency>     <dependency>         <groupId>mysql</groupId>         <artifactId>mysql-connector-java</artifactId>         <version>5.1.47</version>     </dependency> </dependencies> 1.1.3 步骤3:根据表创建模型类 public class Account implements Serializable {     private Integer id;     private String name;     private Double money;     //setter...getter...toString...方法略 } 1.1.4 步骤4:创建Dao接口 public interface AccountDao {     @Insert("insert into tbl_account(name,money)values(#{name},#{money})")     void save(Account account);     @Delete("delete from tbl_account where id = #{id} ")     void delete(Integer id);     @Update("update tbl_account set name = #{name} , money = #{money} where     id = #{id} ")     void update(Account account);     @Select("select * from tbl_account")     List<Account> findAll();     @Select("select * from tbl_account where id = #{id} ")     Account findById(Integer id); } 1.1.5 步骤5:创建Service接口和实现类  public interface AccountService {     void save(Account account);     void delete(Integer id);     void update(Account account);     List<Account> findAll();     Account findById(Integer id); } @Service public class AccountServiceImpl implements AccountService {     @Autowired     private AccountDao accountDao;     public void save(Account account) {         accountDao.save(account);     }     public void update(Account account){         accountDao.update(account);     }     public void delete(Integer id) {         accountDao.delete(id);     }     public Account findById(Integer id) {         return accountDao.findById(id);     }     public List<Account> findAll() {         return accountDao.findAll();     } } 1.1.6 步骤6:添加jdbc.properties文件 resources目录下添加,用于配置数据库连接四要素  jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false jdbc.username=root jdbc.password=root useSSL:关闭MySQL的SSL连接  1.1.7 步骤7:添加Mybatis核心配置文件 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration>     <!--读取外部properties配置文件-->     <properties resource="jdbc.properties"></properties>     <!--别名扫描的包路径-->     <typeAliases>         <package name="com.itheima.domain"/>     </typeAliases>     <!--数据源-->     <environments default="mysql">         <environment id="mysql">         <transactionManager type="JDBC"></transactionManager>         <dataSource type="POOLED">             <property name="driver" value="${jdbc.driver}"></property>             <property name="url" value="${jdbc.url}"></property>             <property name="username" value="${jdbc.username}">             </property>             <property name="password" value="${jdbc.password}">             </property>         </dataSource>         </environment>     </environments>     <!--映射文件扫描包路径-->     <mappers>         <package name="com.itheima.dao"></package>     </mappers> </configuration> 1.1.8 步骤8:编写应用程序 public class App {     public static void main(String[] args) throws IOException {         // 1. 创建SqlSessionFactoryBuilder对象         SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new         SqlSessionFactoryBuilder();         // 2. 加载SqlMapConfig.xml配置文件         InputStream inputStream =         Resources.getResourceAsStream("SqlMapConfig.xml.bak");         // 3. 创建SqlSessionFactory对象         SqlSessionFactory sqlSessionFactory =         sqlSessionFactoryBuilder.build(inputStream);         // 4. 获取SqlSession         SqlSession sqlSession = sqlSessionFactory.openSession();         // 5. 执行SqlSession对象执行查询,获取结果User         AccountDao accountDao = sqlSession.getMapper(AccountDao.class);         Account ac = accountDao.findById(1);         System.out.println(ac);         // 6. 释放资源         sqlSession.close();     } } 1.1.9 步骤9:运行程序  1.2 整合思路分析 Mybatis的基础环境我们已经准备好了,接下来就得分析下在上述的内容中,哪些对象可以交给Spring来管理?  Mybatis程序核心对象分析  从图中可以获取到,真正需要交给Spring管理的是SqlSessionFactory 整合Mybatis,就是将Mybatis用到的内容交给Spring管理,分析下配置文件  说明: 第一行读取外部properties配置文件,Spring有提供具体的解决方案@PropertySource ,需要交给Spring 第二行起别名包扫描,为SqlSessionFactory服务的,需要交给Spring 第三行主要用于做连接池,Spring之前我们已经整合了Druid连接池,这块也需要交给Spring 前面三行一起都是为了创建SqlSession对象用的,那么用Spring管理SqlSession对象吗?回忆下SqlSession是由SqlSessionFactory创建出来的,所以只需要将SqlSessionFactory交给Spring管理即可。 第四行是Mapper接口和映射文件[如果使用注解就没有该映射文件],这个是在获取到SqlSession以后执行具体操作的时候用,所以它和SqlSessionFactory创建的时机都不在同一个时间,可能需要单独管理。 2 Spring整合Mybatis 前面我们已经分析了Spring与Mybatis的整合,大体需要做两件事, 第一件事是:Spring要管理MyBatis中的SqlSessionFactory 第二件事是:Spring要管理Mapper接口的扫描 具体该如何实现,具体的步骤为:  2.1 步骤1:项目中导入整合需要的jar包 <dependency>     <!--Spring操作数据库需要该jar包-->     <groupId>org.springframework</groupId>     <artifactId>spring-jdbc</artifactId>     <version>5.2.10.RELEASE</version> </dependency> <dependency>     <!--     Spring与Mybatis整合的jar包     这个jar包mybatis在前面,是Mybatis提供的     -->     <groupId>org.mybatis</groupId>     <artifactId>mybatis-spring</artifactId>     <version>1.3.0</version> </dependency> 2.2 步骤2:创建Spring的主配置类 //配置类注解 @Configuration //包扫描,主要扫描的是项目中的AccountServiceImpl类 @ComponentScan("com.itheima") public class SpringConfig { } 2.3 步骤3:创建数据源的配置类 在配置类中完成数据源的创建  public class JdbcConfig {     @Value("${jdbc.driver}")     private String driver;     @Value("${jdbc.url}")     private String url;     @Value("${jdbc.username}")     private String userName;     @Value("${jdbc.password}")     private String password;     @Bean     public DataSource dataSource(){         DruidDataSource ds = new DruidDataSource();         ds.setDriverClassName(driver);         ds.setUrl(url);         ds.setUsername(userName);         ds.setPassword(password);         return ds;     } } 2.4 步骤4:主配置类中读properties并引入数据源配置类 @Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import(JdbcConfig.class) public class SpringConfig { } 2.5 步骤5:创建Mybatis配置类并配置SqlSessionFactory public class MybatisConfig {     //定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象     @Bean     public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource)    {         SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();         //设置模型类的别名扫描         ssfb.setTypeAliasesPackage("com.itheima.domain");         //设置数据源         ssfb.setDataSource(dataSource);         return ssfb;     }     //定义bean,返回MapperScannerConfigurer对象     @Bean     public MapperScannerConfigurer mapperScannerConfigurer(){         MapperScannerConfigurer msc = new MapperScannerConfigurer();         msc.setBasePackage("com.itheima.dao");         return msc;     } } 说明:  使用SqlSessionFactoryBean封装SqlSessionFactory需要的环境信息  SqlSessionFactoryBean是前面我们讲解FactoryBean的一个子类,在该类中将SqlSessionFactory的创建进行了封装,简化对象的创建,我们只需要将其需要的内容设置即可。 方法中有一个参数为dataSource,当前Spring容器中已经创建了Druid数据源,类型刚好是DataSource类型,此时在初始化SqlSessionFactoryBean这个对象的时候,发现需要使用DataSource对象,而容器中刚好有这么一个对象,就自动加载了DruidDataSource对象。 使用MapperScannerConfigurer加载Dao接口,创建代理对象保存到IOC容器中  这个MapperScannerConfigurer对象也是MyBatis提供的专用于整合的jar包中的类,用来处理原始配置文件中的mappers相关配置,加载数据层的Mapper接口类 MapperScannerConfigurer有一个核心属性basePackage,就是用来设置所扫描的包路径 2.6 步骤6:主配置类中引入Mybatis配置类 @Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) public class SpringConfig { } 2.7 步骤7:编写运行类 在运行类中,从IOC容器中获取Service对象,调用方法获取结果  public class App2 {     public static void main(String[] args) {         ApplicationContext ctx = new         AnnotationConfigApplicationContext(SpringConfig.class);         AccountService accountService = ctx.getBean(AccountService.class);         Account ac = accountService.findById(1);         System.out.println(ac);     } } 2.8 步骤8:运行程序  支持Spring与Mybatis的整合就已经完成了,其中主要用到的两个类分别是: ———————————————— 版权声明:本文为CSDN博主「老牛源码」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/shenchengyv/article/details/134818779 
  • [技术干货] SpringBoot进行自然语言处理,利用Hanlp进行文本情感分析-转载
     一、说明 自然语言处理已经进入大模型时代,然而从业人员必须了解整个知识体系、发展过程、知识结构,应用范围等一系列知识。本篇将报道此类概况。  二、自然语言处理简介 自然语言处理,或简称NLP,是处理和转换文本的计算机科学学科。它由几个任务组成,这些任务从标记化开始,将文本分成单独的意义单位,应用句法和语义分析来生成抽象的知识表示,然后再次将该表示转换为文本,用于翻译、问答或对话等目的。  三、Hanlp文本分类与情感分析基本概念 语料库 本文语料库特指文本分类语料库,对应IDataSet接口。而文本分类语料库包含两个概念:文档和类目。一个文档只属于一个类目,一个类目可能含有多个文档。  用Map描述 这种关系可以用Java的Map<String, String[]>来描述,其key代表类目,value代表该类目下的所有文档。用户可以利用自己的文本读取模块构造一个Map<String, String[]>形式的中间语料库,然后利用IDataSet#add(java.util.Map<java.lang.String,java.lang.String[]>)接口将其加入到训练语料库中。  用文件夹描述 这种树形结构也很适合用文件夹描述,即:  /**  * 加载数据集  *  * @param folderPath  分类语料的根目录.目录必须满足如下结构:<br>  *                    根目录<br>  *                    ├── 分类A<br>  *                    │   └── 1.txt<br>  *                    │   └── 2.txt<br>  *                    │   └── 3.txt<br>  *                    ├── 分类B<br>  *                    │   └── 1.txt<br>  *                    │   └── ...<br>  *                    └── ...<br>  * 每个分类里面都是一些文本文档。任何满足此格式的语料库都可以直接加载。  数据集实现 考虑到大规模训练的时候,文本数量达到千万级,无法全部加载到内存中,所以本系统实现了基于文件系统的FileDataSet。同时,在服务器资源许可的情况下,可以使用基于内存的MemoryDataSet,提高加载速度。两者的继承关系如下:   训练 训练指的是,利用给定训练集寻找一个能描述这种语言现象的模型的过程。开发者只需调用train接口即可,但在实现中,有许多细节。  分词 目前,本系统中的分词器接口一共有两种实现:   但文本分类是否一定需要分词?答案是否定的。 ​ 我们可以顺序选取文中相邻的两个字,作为一个“词”(术语叫bigram)。这两个字在数量很多的时候可以反映文章的主题(参考清华大学2016年的一篇论文《Zhipeng Guo, Yu Zhao, Yabin Zheng, Xiance Si, Zhiyuan Liu, Maosong Sun. THUCTC: An Efficient Chinese Text Classifier. 2016》)。这在代码中对应BigramTokenizer. ​ 当然,也可以采用传统的分词器,如HanLPTokenizer。 ​ 另外,用户也可以通过实现ITokenizer来实现自己的分词器,并通过IDataSet#setTokenizer来使其生效。  特征提取 特征提取指的是从所有词中,选取最有助于分类决策的词语。理想状态下所有词语都有助于分类决策,但现实情况是,如果将所有词语都纳入计算,则训练速度将非常慢,内存开销非常大且最终模型的体积非常大。 本系统采取的是卡方检测,通过卡方检测去掉卡方值低于一个阈值的特征,并且限定最终特征数不超过100万。  调参调参 对于贝叶斯模型,没有超参数需要调节。  训练 本系统实现的训练算法是朴素贝叶斯法,无需用户关心内部细节。另有一个子项目实现了支持向量机文本分类器,可供参考。由于依赖了第三方库,所以没有集成在本项目中。  模型 训练之后,我们就得到了一个模型,可以通过IClassifier#getModel获取到模型的引用。该接口返回一个AbstractModel对象,该对象实现了Serializable接口,可以序列化到任何地方以供部署。 ​ 反序列化后的模型可以通过如下方式加载并构造分类器: ​  NaiveBayesModel model = (NaiveBayesModel) IOUtil.readObjectFrom(MODEL_PATH); NaiveBayesClassifier naiveBayesClassifier = new NaiveBayesClassifier(model);  分类 通过加载模型,我们可以得到一个分类器,利用该分类器,我们就可以进行文本分类了。  IClassifier classifier = new NaiveBayesClassifier(model);  1 目前分类器接口中与文本分类有关的接口有如下三种: ​  /**  * 预测分类  *  * @param text 文本  * @return 所有分类对应的分值(或概率, 需要enableProbability)  * @throws IllegalArgumentException 参数错误  * @throws IllegalStateException    未训练模型  */ Map<String, Double> predict(String text) throws IllegalArgumentException, IllegalStateException;  /**  * 预测分类  * @param document  * @return  */ Map<String, Double> predict(Document document) throws IllegalArgumentException, IllegalStateException;  /**  * 预测分类  * @param document  * @return  * @throws IllegalArgumentException  * @throws IllegalStateException  */ double[] categorize(Document document) throws IllegalArgumentException, IllegalStateException;  /**  * 预测最可能的分类  * @param document  * @return  * @throws IllegalArgumentException  * @throws IllegalStateException  */ int label(Document document) throws IllegalArgumentException, IllegalStateException;  /**  * 预测最可能的分类  * @param text 文本  * @return 最可能的分类  * @throws IllegalArgumentException  * @throws IllegalStateException  */ String classify(String text) throws IllegalArgumentException, IllegalStateException;  /**  * 预测最可能的分类  * @param document 一个结构化的文档(注意!这是一个底层数据结构,请谨慎操作)  * @return 最可能的分类  * @throws IllegalArgumentException  * @throws IllegalStateException  */ String classify(Document document) throws IllegalArgumentException, IllegalStateException;  classify方法直接返回最可能的类别的String形式,而predict方法返回所有类别的得分(是一个Map形式,键是类目,值是分数或概率),categorize方法返回所有类目的得分(是一个double数组,分类得分按照分类名称的字典序排列),label方法返回最可能类目的字典序。  情感分析 可以利用文本分类在情感极性语料上训练的模型做浅层情感分析。目前公开的情感分析语料库有:中文情感挖掘语料-ChnSentiCorp,语料发布者为谭松波。  接口与文本分类完全一致,请参考com.hankcs.demo.DemoSentimentAnalysis。  四、具体流程 特征提取 本系统采取的是卡方检测,通过卡方检测去掉卡方值低于一个阈值的特征,并且限定最终特征数不超过100万。    训练  测试结果  HanLP Github地址:https://github.com/hankcs/HanLP  HanLP文档地址:https://hanlp.hankcs.com/docs/api/hanlp/pretrained/index.html ———————————————— 版权声明:本文为CSDN博主「放风讲故事」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/2301_80092713/article/details/134832808 
  • [技术干货] 【Springboot系列】SpringBoot整合WebSocket,既然如此简单(含源码) -转载
     前言: 在当今互联网时代,实时通信已经成为了许多应用程序的基本需求。  而WebSocket作为一种全双工通信协议,为开发者提供了一种简单、高效的实时通信解决方案。  本文将介绍如何使用Spring Boot框架来实现WebSocket的集成,快速搭建实时通信功能。  什么是WebSocket? WebSocket是一种在单个TCP连接上进行全双工通信的协议。与传统的HTTP请求-响应模式不同,WebSocket允许服务器主动向客户端推送数据,实现了实时通信的功能。WebSocket协议基于HTTP协议,通过在握手阶段升级协议,使得服务器和客户端可以直接进行数据交换,而无需频繁的HTTP请求。  Spring Boot中的WebSocket支持 Spring Boot提供了对WebSocket的支持,通过集成Spring WebSocket模块,我们可以轻松地实现WebSocket功能。在Spring Boot中,我们可以使用注解来定义WebSocket的处理器和消息处理方法,从而实现实时通信。  WebSocket和HTTP优劣势  WebSocket的优势: 1.实时性: ​ WebSocket是一种全双工通信协议,可以实现服务器主动向客户端推送数据,实现实时通信。相比之下,HTTP是一种请求-响应模式 的协议,需要客户端主动发起请求才能获取数据。  2.较低的延迟: ​ 由于WebSocket使用单个TCP连接进行通信,避免了HTTP的握手和头部信息的重复传输,因此具有较低的延迟。  3.较小的数据传输量: ​ WebSocket使用二进制数据帧进行传输,相比于HTTP的文本数据传输,可以减少数据传输量,提高传输效率。  4.更好的兼容性: ​ WebSocket协议可以在多种浏览器和平台上使用,具有较好的兼容性。  HTTP的优势: 1.简单易用: ​ HTTP是一种简单的请求-响应协议,易于理解和使用。相比之下,WebSocket需要进行握手和协议升级等复杂操作。  2.更广泛的应用: ​ HTTP协议广泛应用于Web开发中,支持各种类型的请求和响应,可以用于传输文本、图片、视频等多种数据格式。  3.更好的安全性: ​ HTTP协议支持HTTPS加密传输,可以保证数据的安全性。  综上,WebSocket适用于需要实时通信和较低延迟的场景,而HTTP适用于传输各种类型的数据和简单的请求-响应模式。在实际应用中,可以根据具体需求选择合适的协议。  示例 版本依赖 模块    版本 SpringBoot    3.1.0 JDK    17 代码 WebSocketConfig @Configuration public class WebSocketConfig {     @Bean     public ServerEndpointExporter serverEndpointExporter() {         return new ServerEndpointExporter();     } } WebSocketServer @Component @ServerEndpoint("/server/{uid}") @Slf4j public class WebSocketServer {      /**      * 记录当前在线连接数      */     private static int onlineCount = 0;      /**      * 使用线程安全的ConcurrentHashMap来存放每个客户端对应的WebSocket对象      */     private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();      /**      * 与某个客户端的连接会话,需要通过它来给客户端发送数据      */     private Session session;      /**      * 接收客户端消息的uid      */     private String uid = "";      /**      * 连接建立成功调用的方法      * @param session      * @param uid      */     @OnOpen     public void onOpen(Session session, @PathParam("uid") String uid) {         this.session = session;         this.uid = uid;         if (webSocketMap.containsKey(uid)) {             webSocketMap.remove(uid);             //加入到set中             webSocketMap.put(uid, this);         } else {             //加入set中             webSocketMap.put(uid, this);             //在线数加1             addOnlineCount();         }          log.info("用户【" + uid + "】连接成功,当前在线人数为:" + getOnlineCount());         try {             sendMsg("连接成功");         } catch (IOException e) {             log.error("用户【" + uid + "】网络异常!", e);         }     }      /**      * 连接关闭调用的方法      */     @OnClose     public void onClose() {         if (webSocketMap.containsKey(uid)) {             webSocketMap.remove(uid);             //从set中删除             subOnlineCount();         }         log.info("用户【" + uid + "】退出,当前在线人数为:" + getOnlineCount());     }      /**      * 收到客户端消息后调用的方法      * @param message 客户端发送过来的消息      * @param session 会话      */     @OnMessage     public void onMessage(String message, Session session) {         log.info("用户【" + uid + "】发送报文:" + message);         //群发消息         //消息保存到数据库或者redis         if (StringUtils.isNotBlank(message)) {             try {                 //解析发送的报文                 ObjectMapper objectMapper = new ObjectMapper();                 Map<String, String> map = objectMapper.readValue(message, new TypeReference<Map<String, String>>(){});                 //追加发送人(防止串改)                 map.put("fromUID", this.uid);                 String toUID = map.get("toUID");                 //传送给对应的toUserId用户的WebSocket                 if (StringUtils.isNotBlank(toUID) && webSocketMap.containsKey(toUID)) {                     webSocketMap.get(toUID).sendMsg(objectMapper.writeValueAsString(map));                 } else {                     //若果不在这个服务器上,可以考虑发送到mysql或者redis                     log.error("请求目标用户【" + toUID + "】不在该服务器上");                 }             } catch (Exception e) {                 log.error("用户【" + uid + "】发送消息异常!", e);             }         }     }      /**      * 处理错误      * @param session      * @param error      */     @OnError     public void onError(Session session, Throwable error) {         log.error("用户【" + this.uid + "】处理消息错误,原因:" + error.getMessage());         error.printStackTrace();     }      /**      * 实现服务器主动推送      * @param msg      * @throws IOException      */     private void sendMsg(String msg) throws IOException {         this.session.getBasicRemote().sendText(msg);     }      /**      * 发送自定义消息      * @param message      * @param uid      * @throws IOException      */     public static void sendInfo(String message, @PathParam("uid") String uid) throws IOException {         log.info("发送消息到用户【" + uid + "】发送的报文:" + message);         if (!StringUtils.isEmpty(uid) && webSocketMap.containsKey(uid)) {             webSocketMap.get(uid).sendMsg(message);         } else {             log.error("用户【" + uid + "】不在线!");         }     }      private static synchronized int getOnlineCount() {         return onlineCount;     }      private static synchronized void addOnlineCount() {         WebSocketServer.onlineCount++;     }      private static synchronized void subOnlineCount() {         WebSocketServer.onlineCount--;     } WebSocketController @RestController public class WebSocketController {      @GetMapping("/page")     public ModelAndView page() {         return new ModelAndView("webSocket");     }      @RequestMapping("/push/{toUID}")     public ResponseEntity<String> pushToClient(String message, @PathVariable String toUID) throws Exception {         WebSocketServer.sendInfo(message, toUID);         return ResponseEntity.ok("Send Success!");     } } webSocket.html <!DOCTYPE html> <html> <head>     <meta charset="utf-8">     <title>WebSocket消息通知</title> </head> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script> <script>     var socket;      //打开WebSocket     function openSocket() {         if (typeof (WebSocket) === "undefined") {             console.log("您的浏览器不支持WebSocket");         } else {             console.log("您的浏览器支持WebSocket");             //实现化WebSocket对象,指定要连接的服务器地址与端口,建立连接.             var socketUrl = "http://localhost:8080/socket/server/" + $("#uid").val();             //将https与http协议替换为ws协议             socketUrl = socketUrl.replace("https", "ws").replace("http", "ws");             console.log(socketUrl);             if (socket != null) {                 socket.close();                 socket = null;             }             socket = new WebSocket(socketUrl);             //打开事件             socket.onopen = function () {                 console.log("WebSocket已打开");                 //socket.send("这是来自客户端的消息" + location.href + new Date());             };             //获得消息事件             socket.onmessage = function (msg) {                 console.log(msg.data);                 //发现消息进入,开始处理前端触发逻辑             };             //关闭事件             socket.onclose = function () {                 console.log("WebSocket已关闭");             };             //发生了错误事件             socket.onerror = function () {                 console.log("WebSocket发生了错误");             }         }     }      //发送消息     function sendMessage() {         if (typeof (WebSocket) === "undefined") {             console.log("您的浏览器不支持WebSocket");         } else {             console.log("您的浏览器支持WebSocket");             console.log('{"toUID":"' + $("#toUID").val() + '","Msg":"' + $("#msg").val() + '"}');             socket.send('{"toUID":"' + $("#toUID").val() + '","Msg":"' + $("#msg").val() + '"}');         }     } </script> <body> <p>【uid】: <div><input id="uid" name="uid" type="text" value="1"></div> <p>【toUID】: <div><input id="toUID" name="toUID" type="text" value="2"></div> <p>【Msg】: <div><input id="msg" name="msg" type="text" value="hello WebSocket2"></div> <p>【第一步操作:】: <div>     <button onclick="openSocket()">开启socket</button> </div> <p>【第二步操作:】: <div>     <button onclick="sendMessage()">发送消息</button> </div> </body>  </html> 
  • [技术干货] Spring Cache(缓存框架)
    学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您: 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持,想组团高效学习… 想写博客但无从下手,急需写作干货注入能量… 热爱写作,愿意让自己成为更好的人…一、Spring Cache是什么?Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。 Spring Cache提供了一层抽象,底层可以切换不同的缓存实现,例如:EHCacheCaffeineRedis二、常用注解三、使用步骤1.导入依赖Spring Cache缓存框架的maven导入:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>Spring Cache缓存中间件的导入:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>总体pom文件如下:<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.3</version> <relativePath/> </parent> <groupId>com.itheima</groupId> <artifactId>springcache-demo</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.1</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.7.3</version> </plugin> </plugins> </build> </project>2.@CachePut的使用@CachePut:将方法的返回值放到缓存中 案例代码: @PostMapping //@CachePut(cacheNames = "userCache",key = "#user.id") @CachePut(cacheNames = "userCache",key = "#result.id") public User save(@RequestBody User user){ userMapper.insert(user); return user; }其中#这种写法是spring规范的。 cacheName:缓存名称,是个字符串 key:缓存数据 如果使用Spring Cache缓存数据,key的生成:userCache::缓存数据3.@Cacheable的使用@Cacheable:在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中 案例代码: @GetMapping @Cacheable(cacheNames = "userCache",key = "#id")//key的生成:userCache::10 public User getById(Long id){ User user = userMapper.getById(id); return user; }4.@CacheEvict的使用@CacheEvict:将一条或多条数据从缓存中删除 清理一条数据案例代码: @DeleteMapping @CacheEvict(cacheNames = "userCache",key="#id")//key的生成:userCache::10 public void deleteById(Long id){ userMapper.deleteById(id); }清理多条数据(cacheNames定义的名字下的所有数据都删除)案例代码:@DeleteMapping("/delAll") @CacheEvict(cacheNames = "userCache",allEntries = true) public void deleteAll(){ userMapper.deleteAll(); }5.@EnableCaching的使用@EnableCaching:开启缓存注解功能,通常加在启动类上 案例代码:@SpringBootApplication @EnableTransactionManagement //开启注解方式的事务管理 @Slf4j @EnableCaching//开启缓存注解功能 public class SkyApplication { public static void main(String[] args) { SpringApplication.run(SkyApplication.class, args); log.info("server started"); } }总结以上就是Spring Cache(缓存框架)的相关知识点,希望对你有所帮助。 积跬步以至千里,积怠惰以至深渊。时代在这跟着你一起努力哦!
  • [技术干货] 记一次安全整改之Jasypt加密
    最近接到安全整改要求对明文配置进行加密这个东西不难但是还是整理记录下,一是可以给自己做一个备份,二是可以希望能提供帮助给有需要的人。下面我按照实际集成中的步骤从到尾整理了一下步骤,引入依赖在pom.xml文件中添加以下依赖:<dependency>     <groupId>com.github.ulisesbocchio</groupId>     <artifactId>jasypt-spring-boot-starter</artifactId>     <version>3.0.3</version> </dependency> 这个依赖将引入jasypt-spring-boot-starter,它提供了Spring Boot集成Jasypt的自动配置。添加加密配置在application.properties文件中添加以下配置:jasypt.encryptor.password=这个是自己设定的密码如果是yml就按照yml的格式自己写一下上面这个密钥可以是任何字符串,只要你能记住就行了。后面我们会用它来加密配置文件中的敏感信息。添加需要加密的属性在application.properties文件中添加需要加密的属性,如下所示:spring.datasource.url=jdbc:mysql://localhost:3306/mydb spring.datasource.username=myuser spring.datasource.password=mypassword 我们将数据库连接信息添加到了配置文件中,并且将密码明文存储在了配置文件中。为了加密这个敏感信息,我们需要将它们改为加密后的形式。加密属性先下载Jasypt的命令行工具:http://www.jasypt.org/download.html解压后,进入bin目录,(windows进入目录不用说了吧 。简单点就是找到文件夹在地址栏cmd)执行以下命令:./encrypt.sh input=明文密码 password=你的秘钥 algorithm=PBEWithMD5AndDES  后面这个是加密算法执行完命令后,会返回一串加密后的密码复制到密码的地方就可以。如果你不想使用命令行工具,可以使用Jasypt的API来加密属性。具体做法是在代码中创建一个JasyptStringEncryptor bean,然后用它来加密属性。例如: @Configuration public class JasyptConfig {     @Value("${jasypt.encryptor.password}")     private String encryptKey;      @Bean("jasyptStringEncryptor")     public StringEncryptor jasyptStringEncryptor() {         PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();         SimpleStringPBEConfig config = new SimpleStringPBEConfig();         config.setPassword(encryptKey);         config.setAlgorithm("PBEWithMD5AndDES");         config.setKeyObtentionIterations("1000");         config.setPoolSize("1");         config.setProviderName("SunJCE");         config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");         config.setStringOutputType("base64");         encryptor.setConfig(config);         return encryptor;     } } 在代码中使用加密后的属性的方式如下: @Component public class MyComponent {     @Value("${spring.datasource.url}")     private String dbUrl;      @Value("${spring.datasource.username}")     private String dbUser;      @Value("${spring.datasource.password}")     private String dbPassword;      // ... } 另外还有一些踩坑记录1. 遇到报错 org.jasypt.exceptions.EncryptionOperationNotPossibleException: Encryption raised an exception. A possible cause is you are using strong encryption algorithms and you have not installed the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files in this Java Virtual Machine 我使用的jdk是152的。刚开始我使用的算法是PBEWITHHMACSHA512ANDAES_256。  但从Java 1.8.0_151和1.8.0_152开始,为JVM启用无限制强度管辖策略 有了一种新的更简单的方法。如果不启用此功能,则不能使用AES-256:  在 jre/lib/security 文件夹中查找文件 java.security,现在用文本编辑器打开java.security,并找到定义java安全性属性crypto.policy的行,它可以有两个值limited或unlimited - 默认值是limited。将其设置为: crypto.policy=unlimited 2.配置热更新失败使用的 jasypt-spring-boot-starter 版本为2.1.0,apollo-client版本为1.5.0。遇到的问题,在Apollo Server端修改配置并发布后,配置不能实现热更新。解决方法:升级jasypt-spring-boot-starter。经过验证,升级到3.0.5可以解决配置热更新问题,新增两个配置即可:jasypt.encryptor.algorithm=PBEWithMD5AndDES jasypt.encryptor.iv-generator-classname=org.jasypt.iv.NoIvGenerator 后面2个坑,都是看网上大佬的方法解决的。 也算是站在巨人的肩膀上了 。 总结完毕 收工,希望可以帮助大家 。
  • [专题汇总] 12月份CodeArts板块技术干货合集,提前放送。
     随着鸿蒙系统的愈发壮大,鸿蒙知识的学习也就变得越发迫切。本月给大家带来的技术干货合集就包括鸿蒙,spring,springboot,java,nginx,redis等众多内容,希望可以帮到大家。  1.Centos搭建KMS(vlmcsd)激活服务器的步骤【转】 https://bbs.huaweicloud.com/forum/thread-0251137381864278028-1-1.html  2.鸿蒙极速入门(一)-HarmonyOS简介 https://bbs.huaweicloud.com/forum/thread-0217137149871563005-1-1.html  3.鸿蒙极速入门(二)-开发准备和HelloWorld https://bbs.huaweicloud.com/forum/thread-0212137150055432004-1-1.html  4.鸿蒙极速入门(三)-TypeScript语言简介 https://bbs.huaweicloud.com/forum/thread-0217137150099699006-1-1.html  5.鸿蒙极速入门(四)-通过登录Demo了解ArkTS https://bbs.huaweicloud.com/forum/thread-0212137150569231005-1-1.html  6.鸿蒙极速入门(五)-路由管理(Router) https://bbs.huaweicloud.com/forum/thread-0217137150812608007-1-1.html      7.HarmonyOS 实战项目 https://bbs.huaweicloud.com/forum/thread-0298137147742910003-1-1.html  8.HarmonyOS 高级特性 https://bbs.huaweicloud.com/forum/thread-0263137147693666005-1-1.html  9.HarmonyOS应用开发 https://bbs.huaweicloud.com/forum/thread-02125137147620736007-1-1.html  10.HarmonyOS UI 开发 https://bbs.huaweicloud.com/forum/thread-0251137147552748004-1-1.html  11.准备HarmonyOS开发环境 https://bbs.huaweicloud.com/forum/thread-0217137147386754003-1-1.html  12.Nginx服务器安装配置SSL证书流程(实现HTTPS安装) https://bbs.huaweicloud.com/forum/thread-0224137038725392014-1-1.html  13.几款值得选的SSH客户端软件 https://bbs.huaweicloud.com/forum/thread-02107137038433887013-1-1.html  14.8个站酷免费字体且可商用 不担心字体版权 https://bbs.huaweicloud.com/forum/thread-0235137038369347011-1-1.html  15.7个加速搜索引擎收录网站实用有效方法 https://bbs.huaweicloud.com/forum/thread-02112137038294122014-1-1.html  16. Java 算法篇-深入理解递归(递归实现:青蛙爬楼梯)-转载 https://bbs.huaweicloud.com/forum/thread-0213136969605169006-1-1.html  17.【内网穿透】搭建我的世界Java版服务器,公网远程联机-转载 https://bbs.huaweicloud.com/forum/thread-02112136969569637009-1-1.html  18.【Nginx篇】Nginx轻松上手-转载 https://bbs.huaweicloud.com/forum/thread-0213136969448107005-1-1.html  19.【SpringBoot(IDEA)+Vue(Vscode)前后端交互】-转载 https://bbs.huaweicloud.com/forum/thread-02107136969215321009-1-1.html  20.【SpringBoot】| SpringBoot 集成 Redis-转载 https://bbs.huaweicloud.com/forum/thread-0240136952804547006-1-1.html  21.基于SpringBoot的个人博客管理系统的设计与实现 毕业设计开题报告-转载 https://bbs.huaweicloud.com/forum/thread-02112136952724159004-1-1.html  22.【SpringBoot篇】Spring_Task定时任务框架-转载 https://bbs.huaweicloud.com/forum/thread-02127136952689543005-1-1.html  23.【SpringCloud】Eureka基于Ribbon负载均衡的调用链路流程分析-转载 https://bbs.huaweicloud.com/forum/thread-0224136952632959005-1-1.html  24. Spring Boot单元测试-转载 https://bbs.huaweicloud.com/forum/thread-0213136952529904002-1-1.html  25.【Springboot系列】SpringBoot整合Jpa-转载 https://bbs.huaweicloud.com/forum/thread-0240136952500264005-1-1.html  26. Spring Boot + MyBatis-Plus实现数据库读写分离 -转载 https://bbs.huaweicloud.com/forum/thread-02107136952426802006-1-1.html 
  • [技术干货] 【内网穿透】搭建我的世界Java版服务器,公网远程联机-转载
     前言 本次教程将在windows本地搭建java版的MC服务器,并用cpolar内网穿透突破局域网限制,实现在公网环境下跟小伙伴远程联机,超简单配置,无需公网IP,也不用设置路由器。  如果你的服务器已经搭建成功,并可以正常在局域网内联机,可以直接跳到第三步,在本地配置cpolar内网穿透,创建隧道映射25565端口,实现异地远程联机。  1. 搭建我的世界服务器 以windows10系统为例,配置java环境,搭建服务器。  1.1 服务器安装java环境 下载java17  Java Downloads | Oracle  选择exe文件,下载完成后双击安装包一路默认安装即可。   java安装完成后,打开文件夹,找到java,将jdk安装路径复制下来,本例中为C:\Program Files\Java\jdk-17.0.5   在开始菜单栏搜索高级系统设置并打开系统属性,点击环境变量   点击新建一个系统环境变量   变量名:JAVA_HOME 变量值:JDK的安装路径,本例中为C:\Program Files\Java\jdk-17.0.5  在系统变量列表中,双击Path变量   点击右侧的新建,在变量名值前面加%JAVA_HOME%\bin,点击确认   校验是否成功:开始菜单栏搜索cmd,打开命令提示符,输入javac,出现以下内容则说明配置成功。   1.2 配置服务端 下载MC服务端,最新版的服务器端可以官网下载  Download server for Minecraft | Minecraft   下载完成后,在文件所在的文件夹新建一个文本文档   然后打开这个文本文档输入以下信息  java -Xms1G -Xmx2G -jar server.jar nogui pause Xmx1024M:给服务器分配的最大内存 Xms1024M:给服务器分配的最小内存 server.jar:服务端名字 请务必保证服务端名字和指令中的相同  然后保存这个文本文档并关闭,重命名把后缀改成.bat(windows默认看不到后缀,可以设置一下)   双击打开.bat文件,这时候你会看到一个命令窗口,过一会它会自动关闭,你会看到文件夹有多出来一些文件,找到eula.txt这个文件并打开   将eula=false改为eula=true,然后保存   打开server.properties文件,做如下改动:online-mode=false(关闭正版验证),其他改动可以按照自己的喜好。至此服务器已经配置完毕,服务器的设置详情都在server.properties。  重新点击.bat文件,即可打开服务器。   2. 测试局域网联机 打开我的世界启动器,点击进入游戏,选择多人游戏   点击添加服务器   服务器名称 服务器地址:填写本地ip地址+mc端口号(默认为25565),如127.0.0.1:25565 点击完成   选择刚刚创建的服务器后,点击加入服务器   连接成功,接下来测试在公网环境下远程联机。   3. 公网远程联机 上面我们实现了在局域网内的联机,接下来我们将突破局域网的限制,实现在公网环境下的远程联机,通过cpolar内网穿透,将内网端口映射到公网上,其会生成相应的公网地址,异地小伙伴就可以通过该公网地址远程联机一起玩了,不需要公网ip,也不用设置路由器,操作简单。  3.1 安装cpolar内网穿透 cpolar官网:cpolar - 安全的内网穿透工具  3.1.1 windows系统 windows系统可以直接在官网下载适用于Windows平台的zip压缩包,解压后得到cpolar安装包,然后双击安装包一路默认安装即可。  3.1.2 linux系统(支持一键自动安装脚本) 如果您的服务器在linux系统上,请参考下面这个教程来安装cpolar内网穿透  linux系统安装cpolar内网穿透 3.2 创建隧道映射内网端口 cpolar安装成功后,在浏览器上访问cpolar web UI管理界面(默认为本地9200端口),以【 http://本地ip地址:9200 】形式访问,如http://127.0.0.1:9200/,并使用cpolar账号登录   登录成功后,点击左侧的隧道管理——创建隧道:  隧道名称:可自定义命名,注意不要与已有隧道名称重复 协议:选择tcp协议 本地地址:25565(我的世界默认端口号) 端口类型:随机临时TCP端口 地区:China vip 点击创建   隧道创建成功后,页面自动跳转至隧道列表页面,可以看到状态正常显示为active,为正常在线状态   点击左侧仪表盘的状态——在线隧道列表,可以看到刚刚创建成功的我的世界隧道已经有生成了相应的公网地址,我们将其复制下来,注意tcp://无需复制,本例中为3.tcp.vip.cpolar.cn:10786   3.3 测试公网远程联机 启动我的世界,可以直接添加一个新的服务器,或者编辑刚刚添加的本地服务器   在服务器地址栏,输入cpolar所生成的公网地址3.tcp.vip.cpolar.cn:10786,点击完成   加入服务器,公网远程联机成功   4. 配置固定TCP端口地址 需要注意的是,以上步骤使用的是随机临时tcp端口地址,所生成的公网地址为随机临时地址,该公网地址24小时内会随机变化。为了方便小伙伴远程联机,我们接下来为其配置固定的TCP端口地址,该地址不会变化,方便小伙伴远程联机,而无需每天重复修改服务器地址。  配置固定tcp端口地址需要将cpolar升级到专业版套餐或以上。  4.1 保留一个固定tcp地址 登录cpolar官网,点击左侧的预留,找到保留的tcp地址,我们来为我的世界保留一个固定tcp地址:  地区:选择China vip 描述:即备注,可自定义 点击保留   地址保留成功后,系统会生成相应的固定公网地址,本例为5.tcp.vip.cpolar.cn:12637,将其复制下来   4.2 配置固定tcp地址 在浏览器上访问9200端口,http://127.0.0.1:9200/,登录cpolar web ui管理界面,点击左侧仪表盘的隧道管理——隧道列表,找到我的世界隧道,点击右侧的编辑   修改隧道信息,将保留成功的固定tcp地址配置到隧道中  端口类型:修改为固定tcp端口 预留的tcp地址:填写保留成功的地址,本例为5.tcp.vip.cpolar.cn:12637 点击更新   隧道更新成功后,点击左侧仪表盘的状态——在线隧道列表,找到我的世界隧道,可以看到公网地址已经更新成为了固定tcp地址。   测试使用固定公网TCP端口地址远程联机   公网远程联机成功!现在,该公网地址不会再随机变化了。  ———————————————— 版权声明:本文为CSDN博主「库库的里昂」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_68662723/article/details/134359413 
  • [技术干货] 【Nginx篇】Nginx轻松上手-转载
     一.简单介绍 1.介绍 Nginx(发音为"engine x")是一款高性能、轻量级的开源 Web 服务器和反向代理服务器,它具有以下特点和功能:  高性能:Nginx 被设计为能够处理大量并发连接而不显著增加系统负担。它采用异步事件驱动的架构,可以有效地处理高流量的 Web 请求。 负载均衡:Nginx 支持负载均衡,可以将请求分发到多个后端服务器,以提高网站性能和可用性。 反向代理:Nginx 可以充当反向代理,将客户端请求转发到后端服务器,隐藏后端服务器的真实 IP 地址,增加安全性和可扩展性。 静态文件服务:Nginx 可以高效地提供静态文件(如 HTML、CSS、JavaScript、图像等)的服务,减轻应用服务器的负担。 SSL/TLS 支持:Nginx 支持加密通信,可以配置 SSL/TLS 来保护数据传输的安全性。 URL 重写:Nginx 允许对 URL 进行灵活的重写和转发,以实现友好的 URL 结构和路由。 缓存:Nginx 可以缓存静态内容和动态内容,提高响应速度并减轻后端服务器的负担。 访问控制:Nginx 可以实施访问控制、IP 黑名单、基本身份验证等安全性措施。 模块化架构:Nginx 使用模块化的架构,允许用户通过插件和模块来扩展其功能。 Nginx 通常被用于构建高性能的 Web 服务器环境,特别适合处理大流量的网站和应用。它在互联网上广泛使用,并已成为许多大型网站和应用的关键组件之一。  2.功能 以下是 Nginx 的一些主要功能:  Web 服务器: Nginx 可以作为一个快速、高性能的 Web 服务器,可以直接处理和提供静态和动态内容。  反向代理: Nginx 可以作为反向代理服务器,接收客户端请求并将其转发到后端服务器,用于负载均衡、缓存、SSL 终端等。  负载均衡: Nginx 可以将来自客户端的请求分发到多个后端服务器,实现负载均衡,提高应用的可扩展性和性能。  HTTP 缓存: Nginx 可以缓存静态和动态内容,减轻后端服务器的负载,提高响应速度。  SSL 终端: Nginx 可以处理 SSL/TLS 连接,用于终止和处理加密通信,减轻后端服务器的计算负担。  反向代理缓存: Nginx 可以作为反向代理缓存,将动态内容缓存到本地,减少对后端服务器的请求,提高响应速度。  动态内容处理: Nginx 可以处理 FastCGI、uWSGI、SCGI 等协议,用于处理动态内容如 PHP、Python、Ruby 等。  限流和速率控制: Nginx 可以实施请求速率控制和限流,以防止服务器过载。  安全性: Nginx 提供安全功能如防止恶意请求、DDoS 攻击防护等。  虚拟主机: Nginx 支持虚拟主机配置,可以在一个服务器上托管多个域名。  自定义模块: Nginx 支持自定义模块开发,可以根据需求添加自定义的功能和扩展。  事件驱动: Nginx 使用事件驱动的异步架构,可以处理大量并发连接,提供出色的性能。  日志记录: Nginx 可以记录详细的访问日志,方便监控和分析。  Nginx 是一个功能强大的服务器软件,适用于多种应用场景,如 Web 服务器、反向代理、负载均衡、缓存、安全保护等。它在大型互联网公司和企业中被广泛使用,提供了高性能和稳定性。  3.安装步骤 #查看nginx在哪 whereis nginx  #yum 安装ningx #nginx添加yum repro库中 #下载nginx包 wget https://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm  # 建立nginx的yum仓库 rpm -ivh nginx-release-centos-7-0.el7.ngx.noarch.rpm  #查看nginx的信息 yum info nginx  #查看yum源仓库中nginx版本 yum --showduplicates list nginx | expand  #安装nginx,默认安装最新的稳定版本 及 nginx 1.20.2 yum install nginx  #查看版本 nginx -V  #查看日志 tail -f /var/log/nginx/error.log  #配置文件地址 /etc/nginx/nginx.conf 二.常用命令 1.启动 nginx #查看nginx位置 whereis nginx  #nginx可用 systemctl enable nginx  #启动nginx systemctl start nginx  #查看进程号 netstat -nltp  #查看状态 systemctl status nginx 2.停止 nginx #停止 nginx systemctl stop nginx 3.重启 nginx #重启 nginx systemctl restart nginx 4.重新加载配置 #重新加载配置 systemctl reload nginx 5.设置开机启动 #设置开机启动 systemctl enable nginx 6.关闭开机启动设置 #关闭开机启动设置 7.检查配置是否正确 #检查配置是否正确(常用) nginx -t 8.检查加载模块 ./nginx -V 9.重新加载 #进入nginx可执行目录sbin下,输入命令,不用重启 ./nginx -s reload 10.指定启动 #启动nginx,并挂在配置文件目录到宿主机 docker run -d --name deepexi-belle-damo-nginx --privileged=true --dns 114.114.114.114 -p 80:80  \ -v /etc/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf \  --restart=always deploy.deepexi.com/datasense-test/deepexi-belle-damo-nginx:v1.0.0 三.常见配置 Nginx 的主配置文件是 nginx.conf,这个配置文件一共由三部分组成,分别为全局块、events 块和 http 块。在 http 块中,又包含 http 全局块、多个 server 块。每个 server 块中,可以包含 server 全局块和多个 location 块。在同一配置块中嵌套的配置块,各个之间不存在次序关系。  1.配置目录 #从 / 根目录下查找文件名为 nginx.conf 的文件 find / -name nginx.conf  #从/etc 目录下查找文件名为 nginx.conf 的文件 find /etc -name nginx.conf  #检查配置文件 nginx -t  #目录 /etc/nginx 2.执行目录 /usr/sbin/nginx 1 3.站点目录 /usr/share/nginx/html 1 4.我的 nginx 配置 server {         listen       80;                 server_name  qinyingjie.top;         root         /usr/share/nginx/html;         include /etc/nginx/default.d/*.conf;         location / {                root   html;               index  index.html index.htm;                proxy_pass http://localhost:8080/;            }                 location ~ .*\.(gif|jpg|jpeg|png|jfif)$ {              root  /kwan/;            autoindex    on ;         }         error_page 404 /404.html;             location = /40x.html {         }         error_page 500 502 503 504 /50x.html;             location = /50x.html {         } }  = :用于不含正则表达式的 uri 前,要求请求字符串与 uri 严格匹配,如果匹配成功,就停止继续向下搜索并立即处理该请求 ~ :用于表示 uri 包含正则表达式,并且区分大小写 ~* :用于表示 uri 包含正则表达式,并且不区分大小写 ^~ :用于不含正则表达式的 uri 前,要求 Nginx 服务器找到标识 uri 和请求。字符串匹配度最高的 location 后,立即使用此 location 处理请求,而不再使用 location 块中的正则 uri 和请求字符串做匹配。 5.服务器 43.139.90.182 qinyingjie.top 1 6.配置文件的优先级 启动时可以指定配置文件  首先,说明结论:同一目录下存在多个配置文件时,Nginx是按照文件的名称先后顺序依次读取的!  #验证配置文件 /usr/local/nginx/sbin/nginx -tc /usr/local/nginx/conf/nginx.conf /usr/local/nginx/sbin/nginx -t -c /usr/local/nginx/conf/nginx.conf  #指定配置文件启动 /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf  #指定配置文件重启 /usr/local/nginx/sbin/nginx -s reload -c /usr/local/nginx/conf/nginx.conf  #说明:-c 参数指定运行nginx系统的自定义配置文件。 #若加:使用自定义配置文件。 #若不加:使用默认的nginx.conf(一般位于/usr/local/conf/nginx.conf,具体以实际情况为准) nginx -c /usr/local/nginx/conf/nginx.conf 7.websocket 要在 Nginx 中开通 WebSocket,需要在 Nginx 配置文件中添加相应的配置。  以下是一个简单的 Nginx 配置文件示例,其中开通了 WebSocket:  http {     # ...      map $http_upgrade $connection_upgrade {         default upgrade;         ''      close;     }      server {         listen 80;         server_name example.com;          location /ws/ {             proxy_pass http://backend_server/ws/;             proxy_http_version 1.1;             proxy_set_header Upgrade $http_upgrade;             proxy_set_header Connection "upgrade";         }     } } 在这个配置文件中,map 指令用于将 HTTP 请求头中的 Upgrade 和 Connection 字段映射到 Nginx 的变量中,proxy_pass 指令用于将请求转发到后端服务器,proxy_http_version 指令用于指定使用 HTTP 1.1 协议进行转发,proxy_set_header 指令用于设置请求头字段。  具体来说,proxy_set_header Upgrade $http_upgrade; 指令用于将 HTTP 请求头中的 Upgrade 字段设置为 $http_upgrade 变量的值,proxy_set_header Connection $connection_upgrade; 指令用于将 HTTP 请求头中的 Connection 字段设置为 $connection_upgrade 变量的值。  这样,当客户端发送一个 WebSocket 请求时,Nginx 就会将其转发到后端服务器,并将请求头中的 Upgrade 和 Connection 字段设置为 WebSocket 需要的值,从而成功开通了 WebSocket。  四.牛逼特性 1.域名配置 upstream qinyingjie.top {   server localhost:8080; } location / {   proxy_pass https://www.qinyingjie.top; } server_name  qinyingjie.top; 1 2.图片不展示 location ~ .*\.(gif|jpg|jpeg|png|jfif)$ {   proxy_pass https://www.qinyingjie.top;   root /kwan/;   autoindex on; } 3.负载均衡 1.轮训  upstream open-api-test-category{     server 10.250.16.111:8195;     server 10.250.16.111:8196; }  location / {     proxy_pass http://open-api-test-category/; } 2.权重  upstream open-api-test-category{     server 10.250.16.111:8195 weight=1;     server 10.250.16.111:8196 weight=3; }  location / {     proxy_pass http://open-api-test-category/; } 3.hash  upstream open-api-test-category{     server 10.250.16.111:8195;     server 10.250.16.111:8196;     ip_hash; }  location / {     proxy_pass http://open-api-test-category/; } 4.动静分离 location ~ .*\.(gif|jpg|jpeg|png|jfif)$ {     root   /kwan/ ;     autoindex    on ; } 5.正向代理 正向代理是代理服务器位于客户端和目标服务器之间的一种代理方式。 客户端请求发送到正向代理服务器,然后由代理服务器将请求转发到目标服务器。 正向代理隐藏了客户端的真实 IP 地址,使目标服务器只能看到代理服务器的 IP 地址。 正向代理通常用于访问被封锁或受限制的内容,或者用于增加安全性和隐私。 比如 VPN,原理就是使用了正向代理,隐藏了客户端的信息。 6.反向代理 反向代理是代理服务器位于目标服务器和客户端之间的一种代理方式。 客户端将请求发送到反向代理服务器,然后代理服务器根据规则将请求转发给后端的目标服务器。 反向代理隐藏了目标服务器的真实 IP 地址,客户端只能看到代理服务器的 IP 地址。 反向代理通常用于负载均衡、缓存、安全性和高可用性方面,可以提高网站的性能和可靠性。 比如部署的服务,使用反向代理,隐藏服务端信息,避免被攻击。 location / {     root   html;     index  index.html index.htm;     proxy_pass http://localhost:8080/; } 7.root 和 alias 区别 最基本的区别:alias 指定的目录是准确的,root 是指定目录的上级目录,并且该上级目录要含有 location 指定名称的同名目录。另外,根据前文所述,使用 alias 标签的目录块中不能使用 rewrite 的 break。  alias 虚拟目录配置中,location 匹配的 path 目录如果后面不带"/“,那么访问的 url 地址中这个 path 目录后面加不加”/“不影响访问,访问时它会自动加上”/“;  但是如果 location 匹配的 path 目录后面加上”/“,那么访问的 url 地址中这个 path 目录必须要加上”/“,访问时它不会自动加上”/“。如果不加上”/“,访问就会失败!  root 目录配置中,location 匹配的 path 目录后面带不带”/",都不会影响访问。  在 nginx 配置中的良好习惯是:  在 location /中配置 root 目录; 在 location /path 中配置 alias 虚拟目录。 #直观理解如下形式: location /dev/{     alias /web/dev/doc/; #这个查找文件的路径直接是/web/dev/doc/ }  location /dev/{     root /web/dev/doc/; #这个查找文件的路径应该是/web/dev/doc/dev } # 这里使用root配置 如果访问 192.168.2.3/pak/a.html  则对应的路径为:/usr/local/pak/a.html # 通过root配置则location配置的/pak/一定是要在root对应的/usr/local/目录下要有的目录 8.proxy_pass 结论:  如果 proxy_pass 末尾有斜杠/,proxy_pass 不拼接 location 的路径 如果 proxy_pass 末尾无斜杠/,proxy_pass 会拼接 location 的路径 1.proxy_pass 末尾有斜杠  location  /api/ {     proxy_pass http://127.0.0.1:8000/; } 请求地址:http://localhost/api/test 转发地址:http://127.0.0.1:8000/test  2.proxy_pass 末尾无斜杠  location  /api/ {     proxy_pass http://127.0.0.1:8000; } 请求地址:http://localhost/api/test 转发地址:http://127.0.0.1:8000/api/test  3.proxy_pass 包含路径,且末尾有斜杠  location  /api/ {     proxy_pass http://127.0.0.1:8000/user/; } 请求地址:http://localhost/api/test 转发地址:http://127.0.0.1:8000/user/test  4.proxy_pass 包含路径,末尾无斜杠  location  /api/ {     proxy_pass http://127.0.0.1:8000/user; } 请求地址:http://localhost/api/test 转发地址:http://127.0.0.1:8000/usertest  9.限流功能 nginx 提供了两种限流方式,  一种是限制请求速率 一种是限制连接数量 限制请求速率:nginx 的 ngx_http_limit_req_module 模块提供限制请求处理速率的能力,使用了漏桶算法(leaky bucket algorithm)。我们可以想像有一只上面进水、下面匀速出水的桶,如果桶里面有水,那刚进去的水就要存在桶里等下面的水流完之后才会流出,如果进水的速度大于水流出的速度,桶里的水就会满,这时水就不会进到桶里,而是直接从桶的上面溢出。对应到处理网络请求,水代表从客户端来的请求,而桶代表一个队列,请求在该队列中依据先进先出(FIFO)算法等待被处理。漏的水代表请求离开缓冲区并被服务器处理,溢出代表了请求被丢弃并且永不被服务。  漏桶算法limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;  server {     location ~* \.(html)$ {         limit_req zone=mylimit;     } } limit_req_zone 用于设置限流和共享内存区域的参数,格式为:limit_req_zone key zone rate。  key:定义限流对象,$binaryremote_addr 是 nginx 中的变量,表示基于 remote_addr(客户端 IP) 来做限流,binary 是二进制存储。使用 $binary_remote_addr 而不是 $remote_addr 是因为二进制存储可以压缩内存占用量。 $remote_addr 变量的大小从 7 到 15 个字节不等,而 $binary_remote_addr 变量的大小对于 IPv4 始终为 4 个字节,对于 IPv6 地址则为 16 个字节。 zone:定义共享内存区来存储访问信息,访问信息包括每个 IP 地址状态和访问受限请求 URL 的频率等。zone 的定义又分为两个部分:由 zone= 关键字标识的区域名称,以及冒号后面的区域大小。myLimit:10m 表示一个大小为 10M,名字为 myLimit 的内存区域。1M 能存储 16000 个 IP 地址的访问信息,myLimit 大概可以存储约 160000 个地址。nginx 创建新记录的时候,会移除前 60 秒内没有被使用的记录,如果释放的空间还是存储不了新的记录,会返回 503 的状态码。 rate:设置最大的访问速率。rate=2r/s(为了好模拟,rate 设置的值比较小),表示每秒最多处理 2 个请求。事实上 nginx 是以毫秒为粒度追踪请求的,rate=2r/s 实际上是每 500 毫秒 1 个请求,也就是说,上一个请求完成后,如果 500 毫秒内还有请求到达,这些请求会被拒绝(默认返回 503,如果想修改返回值,可以设置 limit_req_status)。 limit_req_zone 只是设置限流参数,如果要生效的话,必须和 limit_req 配合使用。limit_req 的格式为:limit_req zone=name [burst=number] [nodelay]。 上面的例子只简单指定了 zone=mylimit,表示使用 mylimit 这个区域的配置,在请求 html 文件时进行限流。我们可以理解为这个桶目前没有任何储存水滴的能力,到达的所有不能立即漏出的请求都会被拒绝。如果我 1 秒内发送了 10 次请求,其中前 500 毫秒 1 次,后 500 毫秒 9 次,那么只有前 500 毫秒的请求和后 500 毫秒的第一次请求会响应,其余请求都会被拒绝。  limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;  server {     location ~* \.(html)$ {         limit_req zone=mylimit burst=5;     } } burst 表示在超过设定的访问速率后能额外处理的请求数。当 rate=2r/s 时,表示每 500ms 可以处理一个请求。burst=5 时,如果同时有 10 个请求到达,nginx 会处理第 1 个请求,剩余 9 个请求中,会有 5 个被放入队列,剩余的 4 个请求会直接被拒绝。然后每隔 500ms 从队列中获取一个请求进行处理,此时如果后面继续有请求进来,如果队列中的请求数目超过了 5,会被拒绝,不足 5 的时候会添加到队列中进行等待。我们可以理解为现在的桶可以存 5 滴水:  limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;  server {     location ~* \.(html)$ {         limit_req zone=mylimit burst=5 nodelay;     } } nodelay 表示不延迟。设置 nodelay 后,第一个到达的请求和队列中的请求会立即进行处理,不会出现等待的请求。需要注意的是,虽然队列中的 5 个请求立即被处理了,但是队列中的位置依旧是按照 500ms 的速度依次被释放的。后面的 4 个请求依旧是被拒绝的,长期来看并不会提高吞吐量的上限,长期吞吐量的上限是由设置的 rate 决定的。  nginx 限流使用了令牌桶算法: 漏桶算法和令牌桶算法都是用于实现流量控制和限流的算法,但它们的原理和适用场景略有不同。  漏桶算法: 漏桶算法模拟了一个漏桶,请求被认为是水滴,以固定的速率流出(漏出)系统。如果流入的请求速率超过了漏桶的容量,多余的请求将会被丢弃或者等待下一个时间段。漏桶算法适用于平滑流量,防止系统被大量突发流量压垮。但是,对于短时间内的突发流量,漏桶算法并不是特别有效,因为它无法快速处理瞬时大量的请求,而只能以固定的速率处理。  令牌桶算法: 令牌桶算法也是一种流量控制算法,它基于一个令牌桶的概念。令牌以一定的速率被添加到桶中,每个请求在处理之前需要获取一个令牌,如果没有可用的令牌,则请求将被暂时阻塞或丢弃。令牌桶算法可以适应瞬时的突发流量,因为当桶中积累足够多的令牌时,可以快速处理突发请求,而不会受限于固定的速率。这使得令牌桶算法更适合处理突发流量的情况。  所以,总的来说,漏桶算法更适合平滑流量,而令牌桶算法更适合应对突发流量。在实际应用中,可以根据需求选择合适的算法来实现流量控制和限流。  五.docker 安装 nginx 1.安装步骤 #查找nginx docker search nginx  #下载镜像 docker pull nginx:latest  #查看本地镜像 docker images  #运行容器 docker run --name nginx-test -p 8080:80 -d nginx 参数说明: --name nginx-test:容器名称。 -p 8080:80: 端口进行映射,将本地 8080 端口映射到容器内部的 80 端口。 -d nginx: 设置容器在在后台一直运行。  #访问是否成功 127.0.01:8080 2.启动命令 #启动nginx,并挂在配置文件目录到宿主机 docker run -d --name deepexi-belle-damo-nginx \ --privileged=true \ --dns 114.114.114.114 \ -p 80:80  \ -v /etc/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf \ --restart=always deploy.deepexi.com/datasense-test/deepexi-belle-damo-nginx:v1.0.0 六.配置 https 1.上传证书 scp  /Users/qinyingjie/Downloads/www.qinyingjie.top_nginx/www.qinyingjie.top_bundle.crt root@43.139.90.182:/etc/nginx scp  /Users/qinyingjie/Downloads/www.qinyingjie.top_nginx/www.qinyingjie.top.key root@43.139.90.182:/etc/nginx 2.http 的配置 # For more information on configuration, see: #   * Official English Documentation: http://nginx.org/en/docs/ #   * Official Russian Documentation: http://nginx.org/ru/docs/  user nginx; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf;  events {     worker_connections 1024; }  http {     log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '                       '$status $body_bytes_sent "$http_referer" '                       '"$http_user_agent" "$http_x_forwarded_for"';      access_log  /var/log/nginx/access.log  main;      sendfile            on;     tcp_nopush          on;     tcp_nodelay         on;     keepalive_timeout   65;     types_hash_max_size 2048;      include             /etc/nginx/mime.types;     default_type        application/octet-stream;      # Load modular configuration files from the /etc/nginx/conf.d directory.     # See http://nginx.org/en/docs/ngx_core_module.html#include     # for more information.     include /etc/nginx/conf.d/*.conf;      server {         listen       80;                 server_name  qinyingjie.top;         root         /usr/share/nginx/html;         # Load configuration files for the default server block.         include /etc/nginx/default.d/*.conf;         location / {            root   html;                index  index.html index.htm;                     proxy_pass http://127.0.0.1:8080/;            }     location ~ .*\.(gif|jpg|jpeg|png|jfif|webp)$ {              root  /kwan/;                  autoindex    on ;         }         error_page 404 /404.html;             location = /40x.html {         }          error_page 500 502 503 504 /50x.html;             location = /50x.html {         }     }  # Settings for a TLS enabled server. # #    server { #        listen       443 ssl http2 default_server; #        listen       [::]:443 ssl http2 default_server; #        server_name  _; #        root         /usr/share/nginx/html; # #        ssl_certificate "/etc/pki/nginx/server.crt"; #        ssl_certificate_key "/etc/pki/nginx/private/server.key"; #        ssl_session_cache shared:SSL:1m; #        ssl_session_timeout  10m; #        ssl_ciphers PROFILE=SYSTEM; #        ssl_prefer_server_ciphers on; # #        # Load configuration files for the default server block. #        include /etc/nginx/default.d/*.conf; # #        location / { #        } # #        error_page 404 /404.html; #            location = /40x.html { #        } # #        error_page 500 502 503 504 /50x.html; #            location = /50x.html { #        } #    }  } 3.证书文件名称 www.qinyingjie.top_bundle.crt www.qinyingjie.top.key 4.https 的配置 # For more information on configuration, see: #   * Official English Documentation: http://nginx.org/en/docs/ #   * Official Russian Documentation: http://nginx.org/ru/docs/  user nginx; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid;  # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf;  events {     worker_connections 1024; }  http {     log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '                       '$status $body_bytes_sent "$http_referer" '                       '"$http_user_agent" "$http_x_forwarded_for"';      access_log  /var/log/nginx/access.log  main;      sendfile            on;     tcp_nopush          on;     tcp_nodelay         on;     keepalive_timeout   65;     types_hash_max_size 2048;      include             /etc/nginx/mime.types;     default_type        application/octet-stream;      # Load modular configuration files from the /etc/nginx/conf.d directory.     # See http://nginx.org/en/docs/ngx_core_module.html#include     # for more information.     include /etc/nginx/conf.d/*.conf;      server {        listen 80;        #请填写绑定证书的域名        server_name www.qinyingjie.top;        #把http的域名请求转成https        return 301 https://$host$request_uri;     }     server {      #SSL 默认访问端口号为 443      listen 443 ssl;      #请填写绑定证书的域名      server_name www.qinyingjie.top;      #请填写证书文件的相对路径或绝对路径      ssl_certificate  www.qinyingjie.top_bundle.crt;      #请填写私钥文件的相对路径或绝对路径      ssl_certificate_key www.qinyingjie.top.key;      ssl_session_timeout 5m;      #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。      ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;      #请按照以下协议配置      ssl_protocols TLSv1.2 TLSv1.3;      ssl_prefer_server_ciphers on;      location / {        #网站主页路径。此路径仅供参考,具体请您按照实际目录操作。        #例如,您的网站主页在 Nginx 服务器的 /etc/www 目录下,则请修改 root 后面的 html 为 /etc/www。        root html;        index index.html index.htm;         proxy_pass http://127.0.0.1:8080/;      }      location ~ .*\.(gif|jpg|jpeg|png|jfif|webp)$ {        root  /kwan/;        autoindex    on ;      }     }  } 5.验证 #检查配置是否正确(常用) nginx -t 1 2 6.加载配置 nginx -s reload 1 7.查看日志 #全局查看 cat  /var/log/nginx/error.log  #尾部查看 tail -f  /var/log/nginx/error.log ———————————————— 版权声明:本文为CSDN博主「檀越剑指大厂」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qyj19920704/article/details/134259159 
  • [技术干货] 【SpringBoot(IDEA)+Vue(Vscode)前后端交互】-转载
     (一)、环境介绍 在SpringBoot+Vue的整合项目中我们使用的编译软件分别有: Vscode来编写Vue脚手架和IDEA来编写SpringBoot。vue脚手架是3.0以上的。  (二)、Vscode部分 1.静态资源 1.App.vue 这里的router-view一定要写出来,因为我们最终呈现到App.vue这个组件中去的  <template>   <div>     <router-view></router-view>   </div> </template>  <script> export default {   name:'App', } </script>  <style>  </style> 2.src/pages/Login.vue 将前端数据传递给后端,后端并响应的操作  <template>     <div>         用户名:<input type="text" v-model="loginForm.username" placeholder="请输入用户名"/>         <br>         <br>         密码: <input type="password" v-model="loginForm.password" placeholder="请输入密码"/>         <br><br>         <button v-on:click="login">登录</button>     </div> </template>  <script> export default {     name:'Login',     data(){         return{             loginForm:{                 username:'',                 password:''             },             responseResult: []         }    },    methods:{        login(){             this.$axios            .post('/login',{  // 传递给后端路径为/login的组件中去                 username: this.loginForm.username,                 password: this.loginForm.password             })             .then(successResponse => {                 if(successResponse.data.code ===200){  // 假如说后端传递过来的状态码是200,那么就成功登入。                     console.log(111111)                     this.$router.replace({path:'/index'})                 }             })             .catch(failResponse => {              })        }         }      } </script> <style scoped>  </style> 3.src/pages/Index.vue 页面假如登入成功,那么我们就跳转到这个页面  <template>     <div>         <h2>HelloWorld</h2>     </div> </template>  <script> export default {     name:'Index' } </script>  <style scoped>  </style> 2.配置route路由和axios异步 1.安装路由:在vscode的控制台下输入: npm install  vue-router@3 2.安装axios: 在vscode的控制台下输入:   npm add axios 1  3.配置路由: src/router/index.js  // TODO: 该文件是Vue路由器文件,路由管理着所有的路由 import Vue from 'vue'  // 引入阉割版本的vue import VueRouter from 'vue-router'  // 引入路由插件  // TODO:引入我们需要的组件 import Login from '../pages/Login.vue' import Index from '../pages/Index.vue' Vue.use(VueRouter) // 使用路由  // 创建一个路由器,管理所有的路由 const router = new VueRouter({     routes: [// 一堆路由。一个对象就是一个路由     {         path: '/login',         component: Login       },       {         path: '/index',         component: Index       }     ], }) // 抛出我们创建的路由器 export default router 4.注册axios和route路由: src/main.js 设置反向代理和全局注册axios,这里路径末尾的api目的是为了和后端开头的api是连接的桥梁。  import Vue from 'vue'  // 引入阉割版本的vue import App from './App.vue' // 引入App.vue组件  //引入我们刚才编写的router配置 import router from './router/index'     //设置反向代理,前端请求默认发送到 http://localhost:8443/api var axios = require('axios') axios.defaults.baseURL = 'http://localhost:8443/api' //全局注册,之后可在其他组件中通过 this.$axios 发送数据 Vue.prototype.$axios= axios  Vue.config.productionTip = false; const vm=new Vue({     router: router, // 传入路由     render: h => h(App) }).$mount('#app'); console.log('vm',vm) 3.配置跨域支持 vue.config.js 配置这个跨域支持是为了让后端能够访问到前端的资源。  const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({     transpileDependencies: true,     lintOnSave: false,// 关闭语法检查     // 配置跨域支持     devServer:{       proxy: {         '/api':{           target:'http://localhost:8443',//跨域支持的端口           changeOrihin: true,           pathRewrite:{             '^/api':''           }         }       },     } }, ) (三)、IDEA部分 1.创建SpringBoot项目 创建SpringBoot并引入web依赖。  2.创建两个实体类 1.com/jsxs/pojo/User.java 用于用户的认证  package com.jsxs.pojo;  /**  * @Author Jsxs  * @Date 2023/5/13 19:51  * @PackageName:com.jsxs.pojo  * @ClassName: User  * @Description: TODO  * @Version 1.0  */ public class User {     int id;     String username;     String password;      public int getId(){         return id;     }      public void setId(int id){         this.id = id;     }      public String getUsername(){         return username;     }      public void setUsername(String username){         this.username = username;     }      public String getPassword(){         return password;     }      public void setPassword(String password){         this.password = password;     } }  2.com/jsxs/pojo/Result.java 用于返回状态码  package com.jsxs.result;  /**  * @Author Jsxs  * @Date 2023/5/13 19:53  * @PackageName:com.jsxs.result  * @ClassName: Result  * @Description: TODO  * @Version 1.0  */ public class Result {     //响应码     private int code;      public Result(int code){         this.code = code;     }      public int getCode(){         return code;     }      public void setCode(int code){         this.code = code;     } } 3.创建控制层 controller/LoginController.java  package com.jsxs.controller;  import com.jsxs.pojo.User; import com.jsxs.result.Result; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.util.HtmlUtils;  import java.util.Objects;  /**  * @Author Jsxs  * @Date 2023/5/13 19:53  * @PackageName:com.jsxs.controller  * @ClassName: LoginController  * @Description: TODO  * @Version 1.0  */ @Controller public class LoginController {      @CrossOrigin     @PostMapping(value = "api/login")     @ResponseBody     public Result login(@RequestBody User requestUser){         //对html 标签进行转义,防止XSS攻击         //分别与接收到的User类的username和password进行比较,根据结果的不停Result(不同的响应码)         String username = requestUser.getUsername();         username = HtmlUtils.htmlEscape(username);          if(!Objects.equals("1",username) || !Objects.equals("1",requestUser.getPassword())){             String message = "账号密码错误";             System.out.println("test");             return new Result(400);         }else{             System.out.println("成功!!");             return new Result(200);         }     } } 4.配置后端响应的端口 server.port=8443 1 (四)、Vue和SpringBoot交互 1.同时运行IDEA和Vscode IDEA  Vscode: npm run serve  2.访问登入界面 访问: http://localhost:8080/#/login  密码输入正确后 显示成功。  ———————————————— 版权声明:本文为CSDN博主「吉士先生」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_69683957/article/details/130665088 
  • [技术干货] 【SpringBoot】| SpringBoot 集成 Redis-转载
     tips:前些天突然发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家,感兴趣的同学可以点击网站进行学习人工智能学习网站  一:SpringBoot 集成 Redis  ①Redis是一个 NoSQL(not only)数据库, 常作用缓存 Cache 使用。  ②Redis是一个中间件、是一个独立的服务器;常用的数据类型: string , hash ,set ,zset , list  ③通过Redis客户端可以使用多种语言在程序中,访问 Redis 数据;java 语言中使用的客户端库有 Jedis,lettuce,  Redisson 等。  ④SpringBoot中使用 RedisTemplate(和StringRedisTemplate) 模版类操作 Redis 数据。  ⑤Redis只要是运用在Linux服务器上,其中有两个重要的二进制文件  redis-server:服务端:在src目录下,执行./redis-server启动服务器端,不要关闭。  redis-cli:客户端: 在src目录下,执行./redis-cli启动客户端,访问redis中的数据。  第一步:创建SpingBoot项目,选择Web、Redis模块    pom.xml配置  <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">     <modelVersion>4.0.0</modelVersion>     <parent>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-parent</artifactId>         <version>2.7.9</version>         <relativePath/>     </parent>     <groupId>com.zl</groupId>     <artifactId>study-springboot-redis</artifactId>     <version>0.0.1-SNAPSHOT</version>       <properties>         <java.version>1.8</java.version>     </properties>     <dependencies>         <!--redis起步依赖:直接在项目中使用RedisTemplate(或者StringRedisTemplate)-->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-data-redis</artifactId>         </dependency>         <!--web起步依赖-->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>           <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-test</artifactId>             <scope>test</scope>         </dependency>     </dependencies>       <build>         <plugins>             <plugin>                 <groupId>org.springframework.boot</groupId>                 <artifactId>spring-boot-maven-plugin</artifactId>             </plugin>         </plugins>     </build>   </project> Redis使用是lettuce客户端  在程序中使用RedisTemplate类的方法去操作redis数据, 实际就是调用的lettuce客户端的中的方法!   第二步:配置核心配置文件 application.properties  主要配置三个部分:host(ip地址)、port(端口号)、password(密码)  server.port=8082 server.servlet.context-path=/myredis   #指定redis(host/port/password) spring.redis.host=192.168.2.129 #windows系统就使用localhost,Linux就适应虚拟地址的IP spring.redis.port=6379 #spring.redis.password=123456 没有密码就不用设置 第三步:Linux中进行处理  ①首先关闭防火墙  systemctl status firewalld.service  #查看关闭防火墙 systemctl disable firewalld.service #永久关闭防火墙 ②打开redis.conf修改配置  将bind中的#去掉,并改为bind 0.0.0.0 protected-mode yes改为no ③杀死已经开启的redis  ps -ef | grep redis #查出进程 kiil -9 进程id #根据进程强制杀死已经开启的redis ④重启服务,让修改的配置生效,重新开启redis服务  src/redis-server ./redis.conf #在redis目录下执行该命令,开启redis服务即可 具体更加详细的redis配置与启动可以参考这篇博客:http://t.csdn.cn/P71zi  第四步:编写Controller  在controller中执行两个操作:添加数据到redis、从redis获取数据!  package com.zl.controller;   import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController;   import javax.annotation.Resource;   @RestController public class RedisController {       // 使用RedisTemplate模板,泛型有三种形式:全是Object、全是String、不写     @Resource     private RedisTemplate redisTemplate;       // 添加数据到redis     @PostMapping("/redis/add")     public String addToRedis(String name,String value){         // 获取ValueOperations对象,这个对象是用来处理String类型的对象添加和取出         ValueOperations valueOperations = redisTemplate.opsForValue();         // 添加数据到redis         valueOperations.set("myname",name);         return "向redis添加数据";     }       // 从redis取出数据     @GetMapping("/redis/get")     public String getToRedis(String key){         ValueOperations valueOperations = redisTemplate.opsForValue();         Object value = valueOperations.get(key);         return "向redis获取数据:"+key+"=="+value;       } } 添加数据 取出数据 查看客户端的数据(查出的是key) 通过key获取value  二:对比 StringRedisTemplate 和 RedisTemplate   通过Restful风格编写例子直观感受StringRedisTemplate 和 RedisTemplate存储数据的区别!  第一种:使用RedisTemplate存储数据  package com.zl.controller;   import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController;   import javax.annotation.Resource;   @RestController public class ControllerRedis {       @Resource     private RedisTemplate redisTemplate;     // restful风格     @PostMapping("/redis/template/{key}/{value}")     public String addByRedisTemplate(@PathVariable String key, @PathVariable String value){         // 使用使用RedisTemplate添加数据         redisTemplate.opsForValue().set(key,value);         return "使用RedisTemplate添加数据";     }   } 发送请求,进行数据的存储   通过key取出value数据;无论是key还是value可读性都很差! 第二种:使用StringRedisTemplate存储数据  package com.zl.controller;   import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController;   import javax.annotation.Resource;   @RestController public class ControllerRedis {       @Resource     private StringRedisTemplate stringRedisTemplate;       @PostMapping("/redis/template/{key}/{value}")     public String addByStringRedisTemplate(@PathVariable String key, @PathVariable String value){         // 使用使用stringRedisTemplate添加数据         stringRedisTemplate.opsForValue().set(key,value);         return "使用stringRedisTemplate添加数据";     }   } 发送请求,进行数据的存储   通过key取出value数据;无论是key还是value都很直观,可读性好 对比 StringRedisTemplate 和 RedisTemplate StringRedisTemplate:把k,v 都是作为String类型处理,使用的是String的序列化 ,可读性好。  RedisTemplate: 把k,v 经过了序列化存到redis;k,v 是序列化后的内容, 不能直接识别,可读性差。  (1)序列化定义  序列化:把对象转化为可传输的字节序列过程称为序列化。  反序列化:把字节序列还原为对象的过程称为反序列化。  注:默认使用的jdk序列化, 可以修改为其它的序列化!  (2)为什么需要序列化?  序列化最终的目的是为了对象可以跨平台存储,和进行网络传输!而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组。我们必须在把对象转成字节数组的时候就制定一种规则(序列化),那么我们从IO流里面读出数据的时候再以这种规则把对象还原回来(反序列化)。  (3)序列化的方式  序列化只是一种拆装组装对象的规则,那么这种规则肯定也可能有多种多样,比如现在常见的序列化方式有:JDK(不支持跨语言)、JSON(常用)、XML、Hessian、Kryo(高性能)、Thrift、Protofbuff。  java的序列化:把java对象转为byte[], 二进制数据。  json序列化:json序列化功能将对象转换为 JSON 格式或从 JSON 格式转换对象。例如把一个Student对象转换为JSON字符串{"name":"李四", "age":29} ),反序列化(将JSON字符串 {"name":"李四", "age":29} 转换为Student对象)  (4)设置RedisTemplate或者StringRedisTemplate的序列化方式  注:默认采用的是JDK的序列化机制,引入spring-boot-starter-data-redis依赖后由SpringBoot创建RedisTemplate或者StringRedisTemplate对象!可以设置Key的序列化,可以设置value的序列化,也可以同时设置。  package com.zl.controller;   import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController;   import javax.annotation.Resource;   @RestController public class RedisController {     @Resource     private RedisTemplate redisTemplate;       @PostMapping("/redis/addrstr")     public String addString(String key,String value){         // 修改序列化方式         // 设置key,value为String的序列化         redisTemplate.setKeySerializer(new StringRedisSerializer());         redisTemplate.setValueSerializer(new StringRedisSerializer());         // 存数据         redisTemplate.opsForValue().set(key,value);         return "定义RedisTemplate对象的key,value的序列化";     } } 调用setKeySerializer设置key的序列化,参数是RedisSerializer接口对象  这个接口有很多实现类,其中一个就是默认采用的JDK序列化。   总结:实际上RedisTemplate使用范围比StringRedisTemplate要大。后者采用的就是String的序列化,专门用来处理字符串的。前者比较通用比如当前是一个Java对象,就需要使用RedisTemplate,然后修改序列化的方式即可!  (5) json序列化  第一步:准备一个可序列化的Java类,设置IDEA自动生成  如果我们不想手动输入序列化版本号,想让IDEA自动给我们生成一个怎们办呢?需要进行设置:File--->Settings--->Editor--->Code Style--->Inspections--->JVM languages--->把下面这个打上对勾--->Apply--->OK最终回到我们继承Serializable接口的类名上,alt+Enter即可自动生成序列版本号!   package com.zl.pojo;   import java.io.Serializable;     public class Student implements Serializable {     private static final long serialVersionUID = -8589050940385007834L;     private Integer id;     private String name;     private Integer age;       public Student() {     }       public Student(Integer id, String name, Integer age) {         this.id = id;         this.name = name;         this.age = age;     }       public Integer getId() {         return id;     }       public void setId(Integer id) {         this.id = id;     }       public String getName() {         return name;     }       public void setName(String name) {         this.name = name;     }       public Integer getAge() {         return age;     }       public void setAge(Integer age) {         this.age = age;     } } 第二步:把Java对象以json格式存储到redis package com.zl.controller; import com.zl.pojo.Student; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController;   import javax.annotation.Resource;     @RestController public class RedisController {     @Resource     private RedisTemplate redisTemplate;       // Java对象序列化为json格式     @PostMapping("/redis/addjson")     public String javaBeanToJson(){         Student student = new Student(100,"zhangsan",20);         // key使用String的序列化         redisTemplate.setKeySerializer(new StringRedisSerializer());         // value使用json的序列化         redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Student.class));         // 存储到redis当中         ValueOperations valueOperations = redisTemplate.opsForValue();         valueOperations.set("mystudent",student);         return "json序列化";       } } 第三:postman进行访问 成功存储后,使用redis的图形化界面进行查看 第四步:进行反序列化      // 反序列化     @PostMapping("/redis/getjson")     public String JsonTojavaBean(){         // key使用String的序列化         redisTemplate.setKeySerializer(new StringRedisSerializer());         // value使用json的序列化         redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Student.class));         // 进行反序列化         Object obj = redisTemplate.opsForValue().get("mystudent");         return "json反序列化="+obj;     } 成功反序列化为Java对象  ———————————————— 版权声明:本文为CSDN博主「@每天都要敲代码」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_61933976/article/details/129385457 
  • [技术干货] 基于SpringBoot的个人博客管理系统的设计与实现 毕业设计开题报告-转载
     基于SpringBoot的个人博客管理系统的设计与实现——毕业设计开题报告  一、研究背景与意义  在互联网时代,个人博客已经成为人们表达观点、分享经验、交流思想的重要平台。对于大学生而言,个人博客不仅是展示自我、锻炼表达能力的重要途径,还可以作为学术研究与交流的载体,促进学术创新与进步。然而,传统的个人博客搭建方式往往繁琐复杂,缺乏便捷的管理系统。因此,设计并实现一款基于SpringBoot的个人博客管理系统,对于提升大学生个人博客的搭建与管理效率、促进学术交流与创新具有重要意义。本研究旨在解决这一问题,为大学生提供一个简单易用、功能强大的个人博客管理系统。  二、国内外研究现状  在国内外,个人博客管理系统已经得到了一定的研究和发展。国外的研究现状中,一些开源的博客管理系统如WordPress、Jekyll等得到了广泛应用,它们提供了丰富的主题和插件,使用户能够便捷地搭建和管理个人博客。国内的研究现状中,也有一些类似的博客管理系统,如Typecho、Emlog等,它们在功能性和易用性方面也有一定的优势。然而,这些现有的博客管理系统在某些方面仍存在不足之处,如系统性能、定制化需求等。因此,本研究将基于SpringBoot框架,设计并实现一个高性能、可定制的个人博客管理系统,以满足大学生的实际需求。  三、研究思路与方法  本研究将采用以下研究思路和方法:  需求分析:通过调研和分析大学生个人博客的实际需求,明确系统的功能性和非功能性需求,为后续设计和开发提供基础。  系统设计:基于SpringBoot框架,设计系统的整体架构、数据库结构、前后台交互接口等。同时,进行系统的界面设计和用户体验优化。  系统实现:采用Java语言和SpringBoot开发框架,完成系统的前后台功能实现。包括用户管理、文章发布与管理、评论管理、主题定制等功能。  系统测试与优化:对系统进行全面的测试,包括功能测试、性能测试、安全测试等。根据测试结果和用户反馈,对系统进行优化和改进。  四、研究内容与创新点  本研究的内容包括基于SpringBoot的个人博客管理系统的需求分析、设计、实现和测试。创新点主要体现在以下几个方面:  基于SpringBoot框架的开发:利用SpringBoot框架的简化开发和自动配置特性,提高开发效率,减少手动配置和代码量。  前后端分离架构:采用前后端分离的设计模式,实现前后端代码的解耦和独立开发。前端使用流行的前端框架,提供良好的用户体验,后端专注于业务逻辑处理和数据存储。  定制化需求满足:提供灵活的主题定制功能,用户可以根据自己的喜好和需求,定制博客的界面样式和布局。  优化系统性能:通过合理的系统设计和优化措施,提高系统的性能和响应速度,确保用户可以流畅地管理和访问个人博客。  五、前后台功能详细介绍  前台功能主要包括用户注册登录、文章浏览与评论、搜索功能、主题切换等。后台功能主要包括文章发布与管理、评论审核与管理、用户管理、数据统计与分析等。通过前后台功能的协同作用,实现个人博客的全面管理和优化。  六、研究思路与研究方法可行性  本研究采用SpringBoot框架进行开发,该框架在Web应用开发领域具有成熟的应用和广泛的支持。同时,研究团队具备Java开发和Web开发的技术能力,能够顺利完成研究任务。因此,本研究的研究思路和研究方法是可行的。  七、研究进度安排  第一阶段:需求调研与分析(2周) 第二阶段:系统设计与界面设计(4周) 第三阶段:系统实现与测试(8周) 第四阶段:系统优化与改进(3周) 第五阶段:论文写作与整理(3周) 第六阶段:答辩准备与答辩(1周) 八、论文(设计)写作提纲 (这部分需要您自己根据学校的要求和论文写作规范进行编写,提纲通常包括摘要、引言、相关工作、方法论、实验/评估、结论等部分。)  九、主要参考文献 [此处列出与本研究相关的主要参考文献]  当然,以下是对基于SpringBoot的个人博客管理系统的详细前后台功能描述:  前台功能描述:  用户注册与登录:提供注册页面供未注册用户填写信息进行注册,已注册用户可通过登录页面进行身份验证,进入个人博客主页。 文章浏览与阅读:展示博客中的文章列表,供用户浏览;提供文章详情页面,展示文章内容,支持文章内容的格式化展示,如字体、颜色、图片、代码块等。 评论与互动:在每篇文章下方提供评论框,已登录用户可以在此发表对文章的评论,其他用户可以对评论进行回复,形成讨论区。 主题切换:提供多个博客主题供用户选择,用户可以根据自己的喜好选择不同的主题风格。 搜索功能:提供搜索框,用户可以通过关键词搜索博客内的文章。 后台功能描述:  文章发布与管理:提供文章编辑页面,用户可以撰写新文章,设定文章的标题、标签、摘要等信息,并可选择发布或保存为草稿。已发布的文章可以进行编辑、删除操作。 评论审核与管理:对用户提交的评论进行审核,可以选择通过或拒绝。对已通过的评论可以进行删除操作。提供评论管理列表,方便管理员统一查看和管理。 用户管理:展示注册用户的列表,管理员可以查看用户的详细信息,如用户名、邮箱、注册时间等。管理员还可以对用户进行禁用、启用操作。 数据统计与分析:统计博客的访问量、文章阅读量、评论数量等数据,以图表的形式展示给管理员,帮助管理员了解博客的运营情况。 系统设置:允许管理员进行基本的系统设置,如博客名称、副标题、网站Logo等。还提供对首页布局、侧边栏内容等进行自定义设置的功能。 这些功能将覆盖个人博客的主要使用场景,既满足了普通用户的浏览、评论需求,也提供了管理员对博客进行全面管理的功能。通过前后台功能的协同,将实现一个功能完整、易用性强的个人博客管理系统。 ———————————————— 版权声明:本文为CSDN博主「黄菊华老师」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/u013818205/article/details/134434140 
  • [技术干货] 【SpringBoot篇】Spring_Task定时任务框架-转载
     🌹概述 Spring Task 是 Spring 框架提供的一种任务调度和异步处理的解决方案。可以按照约定的时间自动执行某个代码逻辑它可以帮助开发者在 Spring 应用中轻松地实现定时任务、异步任务等功能,提高应用的效率和可维护性。  Spring Task 的主要特点包括:  简单易用:Spring Task 提供了简洁的注解和配置方式,使得任务调度和异步处理变得非常容易上手。 内置支持:Spring Task 内置于 Spring 框架中,无需额外的依赖,开发者可以直接在 Spring 应用中使用。 灵活的任务调度:Spring Task 支持基于 cron 表达式的定时任务调度,能够满足各种复杂的调度需求。 异步任务支持:除了定时任务,Spring Task 也支持异步任务的处理,能够在后台线程中执行耗时操作,提高系统的响应速度。 集成注解:Spring Task 提供了 @Scheduled 注解用于标识定时任务的方法,以及 @Async 注解用于标识异步任务的方法,使用起来非常方便。 监控和管理:Spring Task 支持任务的监控和管理,可以通过 JMX 或者 Spring Boot Actuator 进行任务的查看和控制。 🌺应用场景 信用卡每月还款提醒  银行贷款每月还款提醒  自动续费短信提醒  火车票售票系统处理未支付订单  入职纪念日为用户发送通知  下面我们来学习cron表达式,通过cron表达式可以定义任务的触发时间  🎄cron表达式 cron其实就是一个字符串,可以用来定义任务触发的时间 (之前讲Linux的文章中有提到cron表达式crond的基本操作)   其实我们不用自己手写cron表达式 我们可以通过在线生成器来生成cron表达式https://cron.qqe2.com/  🛸入门案例  使用的是黑马程序员的《苍穹外卖》项目的代码来进行学习  在启动类中加上@EnableScheduling   新建一个task包和MyTask类  package com.sky.task;  import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;  import java.util.Date;  @Component @Slf4j public class MyTask {      //定时任务     @Scheduled(cron = "0/5 * * * * ?")     public void executeTask(){         log.info("定时任务执行{}",new Date());     } } 每隔5秒触发一次 上我们完成了SpringTask入门案例的编写,下面我们来讲解在《苍穹外卖》中的应用 🎍实际应用 我们创建一个类OrderTask package com.sky.task; import com.sky.entity.Orders; import com.sky.mapper.OrderMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;  import java.time.LocalDateTime; import java.util.List;  @Component @Slf4j public class OrderTask {      @Autowired     private OrderMapper orderMapper;      //处理超时订单的方法     @Scheduled(cron = "0 * * * * ? ")//每分钟触发一次     public void processTimeoutOrder(){         log.info("定时处理超时订单{}", LocalDateTime.now());          //获取当前时间,并在当前时间的基础上减去 15 分钟         LocalDateTime time=LocalDateTime.now().plusMinutes(-15);          List<Orders> ordersList=orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT,time);          if (ordersList!=null&&ordersList.size()>0){             for (Orders orders:ordersList){                 orders.setStatus(Orders.CANCELLED);                 orders.setRejectionReason("订单超时,已取消");                 orders.setCancelTime(LocalDateTime.now());                 orderMapper.update(orders);             }         }     }      //处理一直处于派送中状态的订单     @Scheduled(cron = "0 0 1 * * ?")//每天凌晨一点触发一次     public void processDeliveryOrder(){         log.info("定时处理处于派送中的订单{}",LocalDateTime.now());          //获取当前时间,并在当前时间的基础上减去 60 分钟         LocalDateTime time=LocalDateTime.now().plusMinutes(-60);          List<Orders>ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.DELIVERY_IN_PROGRESS,time);          if (ordersList!=null&&ordersList.size()>0){             for (Orders orders:ordersList){                 orders.setStatus(Orders.CANCELLED);                 orderMapper.update(orders);             }         }     } } 进入OrderMapper接口里面编写sql 根据订单状态和下单时间查询订单 这样子我们就完成了 ———————————————— 版权声明:本文为CSDN博主「在下小吉.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_72853403/article/details/134574825 
  • [技术干货] 【SpringCloud】Eureka基于Ribbon负载均衡的调用链路流程分析-转载
     前言 微服务间相互调用的基础上,服务间的调用更多是以调用某多实例服务下的某个实例的形式。而这就需要用到负载均衡技术。对于开发者而言,只要通过@LoadBalance注解就开启了负载均衡。如此简单的操作底层究竟是什么样的,我想你也很想知道。  1.调用形式 在《SpringCloud集成Eureka并实现负载均衡》的基础之上,我们可以进行一个小小的实验,debug运行程序,通过postman发起一个请求,A服务会去远程调用B服务,debug发现发送的url为:http://user-service/user/1,毫无疑问的是,这就是A调用B的途径  同样地,拿到这个url我们去postman里发送请求:  发现请求无法发送出去,路径出了问题。观察路径中的参数user-service发现他是B服务的服务名称,那为什么在A服务里向B服务发送“服务名称-接口路径-参数”形式的请求就能够正常响应? 结合集成负载均衡的过程,这一定是Ribbon在发挥作用  2.LoadBalancerInterceptor 负载均衡的前提不是传递一个具体的url,肯定是Ribbon做了某种解析,通过服务名称得到了服务下的实例列表,从而拉取Eureka-Server中的服务注册表来将请求映射到指定的某个实例上。 结合曾经前后端分离的web开发经验,后端经常会在拦截器中拦截前端发来的请求来对请求做一些操作,比如校验、拼接、鉴权…调用方发送请求和接收方收到的请求并不一致,这其中会不会也是有一个类似于拦截器的东西拦截了请求,并且转换了请求呢? 答案是必然的,那是谁——LoadBalancerInterceptor  可以看到的是,他实现了ClientHttpRequestInterceptor接口,具体用法细节直接去看接口中声明的方法 直观的看出接口中声明了一个intercept()方法并且接受了HttpRequest参数来拦截了客户端的http请求,并且修改的请求体!这么一看URL更改的谜底就在此处揭晓了,那么方法底层具体是怎么实现的呢:  3.负载均衡流程分析 3.1 调用流程图 Debug源码之前先来看一下源码中的调用链路总体流程图(手图):  概括来看则是:拦截请求—读取服务—拉取服务列表—选择规则—返回服务实例  3.2 intercept()方法 下面我们开始Debug: 1.当发送请求使得服务间发生调用关系,调用请求会先传递到拦截器中的intercept方法,可以看到的是目前还和发送是保持一致  2.继续向下执行,开始解析请求,拿到了请求中的URI——通过getHost()方法拿到了主机地址(服务的名称)  3.3 execute()方法 3.Ribbon开始做负载均衡处理  4.两次步入之后进入到execute()方法内部,发现传递进来的服务名称作为服务Id进入到了getLoadBalance()方法,并且得到了一个ILoadbalance接口对象,而在该对象中封装了很多的信息:  这里记住服务实例id的值:host.docker.internal:8084,这就是Eureka客户端接收到的实例信息  3.4 getServer()方法 5.接口对象作为参数传递到了getServer()方法,得到了一个server对象进入到方法内部。发现与此同时传递了一个Object类型的对象用于指定服务器的规则或条件,不过到目前为止,这个参数一直都是null作为传递,即loadBalancer.chooseServer()方法采用的是‘default’的方式进行选择  3.4 子类的chooseServer()方法 6.再次步入到chooseServer()方法,发现是在一个名为BaseLoadBalancer类(这个类是负载均衡器的具体实现后面会具体分析)下重写的父类方法 此时:可以判断的是getLoadBalancerStats().getAvailableZones().size() <= 1为TRUE  3.5 getLoadBalancerStats().getAvailableZones().size() <= 1 对于表达式:getLoadBalancerStats().getAvailableZones().size() <= 1进行分析 发现在BaseLoadBalancer类中通过继承抽象类AbstractLoadBalancer并重写getLoadBalancerStats()抽象方法,获取到了一个loadbalancer统计信息集合LoadBalancerStats  而封装在LoadBalancerStats中的信息里有一个ConcurrentHashMap类型的集合属性,即  volatile Map<String, List<? extends Server>> upServerListZoneMap = new ConcurrentHashMap<String, List<? extends Server>>(); 1 用于存储可用的服务列表,这个集合中的每个条目都代表一个区域,键是区域名称,值是该区域下可用服务器的列表。  后续的.getAvailableZones()方法则是获取这一属性值中所有的键,也就是可用的服务区域,并作为Set集合返回来进行判断  很显然,这里进一步论证getLoadBalancerStats().getAvailableZones().size() <= 1是为true的,后续就会去调用父类的chooseServer()方法  3.6 父类的chooseServer()方法 7.步入到父类的chooseServer()方法中,发现最后返回了一个Server类型的对象,这肯定就是具体的服务实例信息了。  3.7 IRule接口下的实例 去追踪rule变量,发现是一个IRule接口的实例,即为负载均衡提供规则的接口  并且此接口下有大量的规则实现,而默认的规则方式则为轮询调度:  可是看到上图在debug时,rule变量右侧灰色显示的是rule:RandomRule@12045这是因为我通过配置IRule类型的Bean指定了负载均衡的规则:      @Bean     public IRule randomRule() {         return new RandomRule();     } 1 2 3 4 只要把他注释掉,程序就会继续去采用默认的规则即RoundRobinRule  3.8 最终的choose()方法—return server 8.了解了IRule接口的rule实例,再去看他最终调用的choose()方法。同样地步入进去,由于是默认规则,则按照流程进入到了RoundRobinRule规则实现中的choose方法(其实IRule接口下的每一个规则实现类都有choose方法)  实现了ILoadBalancer接口的负载均衡器对象作为参数传递到了方法中,与此同时key为default。开始为随机选择预热。  3.9 choose()方法内部分析 9.进入到while循环中,不断选择服务器,直到找到一个可用的服务器。随后会判断线程是否中断,如果中断了,则直接返回null。  这样的情况出现频率还是很高,由此可见,这个小设计会减少很多不必要计算,提升了程序运行的效率。 而后这是分别获取两个服务列表 从列表中选择一个  兜底操作,对选择的做判断 最后成功返回Server实例给chooseServer()方法,服务发起者发送http://user-service/user请求通过Ribbon最后轮询到了localhost:8084服务实例上  4. 彩蛋 现在有很多公司都在用Nacos替换Eureka,因为感知服务列表的变化不够敏感,感知下线服务太过迟钝,就像下面这种情况:  服务实例已经下线,时间大约过了一分钟,却还是把下线的服务加载到了可用服务列表里(upList),其实这并不怪Ribbon,都是Eureka的错 针对这种情况我们留个彩蛋,下次再来talk about~ ———————————————— 版权声明:本文为CSDN博主「懒羊羊.java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_57535055/article/details/134449104 
  • [技术干货] Spring Boot单元测试-转载
     1.概述 所谓单元测试就是对功能最小粒度的测试,落实到JAVA中就是对单个方法的测试。对单个方法的测试用junit即可,关于junit作者另一位篇文章中有详细介绍,感兴趣的小伙伴可以去看看:  详解junit-CSDN博客  junit可以完成单个方法的测试,但是对于Spring体系下的web应用的单元测试是无能为力的。因为spring体系下的web应用都采用了MVC三层架构,依托于IOC,层级之间采用了依赖注入的方式来进行调用。如果应用不启动、IOC容器不进行初始化、依赖没有被注入进IOC容器,根本就没办法正常的使用。调controller,会由于service没注入而报空指针异常,调service,会由于dao没注入而报空指针异常。  所以我们发现想要对Spring Boot进行单元测试,前置条件就是需要启动应用,或者说准确点是需要启动IOC。但很明显我们又不可能每次都去直接启动应用来进行测试,一些架构糟糕、很重的老应用启动,动不动就是几分钟甚至十几分钟。我们不可能每次写一点新的代码,想要测试就去重启一下,这样效率太低了。  为此spring boot test为我们提供了@SpringBootTest来对SpringBoot应用进行快捷的单元测试。  2.基本使用 一般单元测试类我们都放在test路径下,一个@Test就是一个case(用例):   在从 Spring Boot 1.4.0 版本以前@SpringBootTest需要和@RunWith配合来使用  @RunWith(SpringRunner.class)用来指定启动类是哪个?@SpringBootTest用来加载应用上下文  依赖:  <dependency>          <groupId>org.springframework.boot</groupId>          <artifactId>spring-boot-starter-test</artifactId>          <scope>test</scope>  </dependency>  代码示例:  import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner;   @RunWith(SpringRunner.class) @SpringBootTest public class MyApplicationTests {       // 测试内容   } 在从 Spring Boot 1.4.0 版本开始就不用加上@RunWith了,直接使用@SpringBootTest就可以了。  代码示例:  import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import com.example.MyApplication; // 替换成实际的主启动类所在的包   @SpringBootTest(classes = MyApplication.class) public class MyApplicationTests {       // 测试内容   } 3.优势 我猜到这儿很多小伙伴有所疑惑:  既然@SpringBootTest也要启动主启动类,那和我们直接启动SpringBoot应用来测试有什么区别喃?@SpringBootTest的优势在哪儿?  答案是——轻量级:  @SpringBootTest 注解提供了轻量级的测试环境,相比直接启动整个应用程序,它可以只加载所需的配置和组件,从而使测试更加迅速。这对于单元测试是非常有利的,因为它们通常关注的是某个组件或模块的功能,而不是整个应用程序。  4.常用属性 @SpringBootTest 注解提供了许多属性,用于配置测试的上下文加载和应用程序启动。以下是一些常用的 @SpringBootTest 属性:  classes:  指定启动测试时加载的配置类或主启动类。可以使用其中一个属性,根据需要选择。  webEnvironment:  指定测试的 Web 环境。可以设置为 WebEnvironment.MOCK(默认值,使用 MockMvc)或 WebEnvironment.RANDOM_PORT(随机端口,真正启动嵌入式容器)等。  @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) WebEnvironment.MOCK:  在这种模式下,不会启动真正的嵌入式 Web 服务器,而是使用 MockMvc 框架模拟 HTTP 请求和响应。这样可以在没有真实服务器的情况下测试控制器的行为。适用于测试控制器层的逻辑,但不需要测试整个 Web 应用程序的场景。  WebEnvironment.RANDOM_PORT:  在这种模式下,会启动嵌入式 Web 服务器,并使用一个随机的端口。这允许测试整个应用程序的集成,包括 HTTP 请求和响应。适用于测试整个应用程序的场景,但不会影响外部系统。  WebEnvironment.DEFINED_PORT:  与 RANDOM_PORT 类似,但是在这种模式下,使用的端口是固定的,可以在属性中配置。这适用于需要预定义端口的场景,例如与外部系统集成时。  WebEnvironment.NONE:  在这种模式下,不会启动任何嵌入式 Web 服务器,也不会模拟 HTTP 请求和响应。这种模式适用于单元测试,主要测试业务逻辑,而不涉及与 Web 环境的交互。  properties:  指定配置属性。  @SpringBootTest(properties = "my.property=value") exclude:  指定要排除的配置类。  @SpringBootTest(exclude = {SomeConfiguration.class, AnotherConfiguration.class}) ———————————————— 版权声明:本文为CSDN博主「_BugMan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/Joker_ZJN/article/details/134542387 
  • [技术干货] 【Springboot系列】SpringBoot整合Jpa-转载
     前言: Spring Boot是一种快速开发框架,它简化了Java应用程序的开发过程。而Jpa(Java Persistence API)是Java持久化规范的一种实现,它提供了一种方便的方式来访问和操作数据库。将Spring Boot与Jpa整合可以更加方便地进行数据库操作,提高开发效率。本文将介绍如何使用Spring Boot整合Jpa,帮助读者快速上手并应用于实际项目中。  什么是JPA? Jpa(Java Persistence API)是Java持久化规范的一种实现,它提供了一种方便的方式来访问和操作数据库。下面将介绍Jpa的优点和缺点。  JPA优缺点 优点  1.简化开发: Jpa提供了一种面向对象的方式来进行数据库操作,开发人员可以使用Java对象来表示数据库表和记录,而不需要编写复杂的SQL语句。这样可以大大简化开发过程,提高开发效率。  2.高度抽象: Jpa提供了一套高度抽象的API,隐藏了底层数据库的细节,开发人员可以更加专注于业务逻辑的实现,而不需要关注数据库的具体实现细节。这样可以降低开发的复杂性,提高代码的可维护性。  3.跨数据库支持: Jpa支持多种数据库,开发人员可以在不同的数据库之间切换,而不需要修改大量的代码。这样可以提高系统的灵活性和可扩展性。  4.自动化的事务管理: Jpa提供了自动化的事务管理机制,开发人员可以使用注解来标识事务的边界,Jpa会自动处理事务的提交和回滚。这样可以简化事务管理的代码,提高系统的稳定性和可靠性。 缺点 1.学习成本较高: Jpa是一种复杂的技术,需要开发人员具备一定的数据库和ORM(对象关系映射)的知识。对于初学者来说,学习和掌握Jpa可能需要一定的时间和精力。  2.性能问题: 由于Jpa是一种高度抽象的技术,它会对数据库的访问和操作进行一定的封装和转换,这可能会导致一定的性能损失。在对性能要求较高的场景下,可能需要使用原生的SQL语句来进行数据库操作。  3.灵活性受限: Jpa提供了一套标准的API,开发人员需要按照这套API来进行开发,这可能会限制一些特定的需求和场景。在一些复杂的业务场景下,可能需要使用原生的SQL语句或其他ORM框架来实现。  示例 版本依赖 模块    版本 SpringBoot    3.1.0 JDK    17 代码 UserDO @Entity @Data @Table(name = "user") public class UserDO  {     private static final long serialVersionUID = -2952735933715107252L;      @Id     @GeneratedValue(strategy = GenerationType.IDENTITY)     private Long id;      private String name;      private Integer age;      private String email;  } UserRepository /**  * 查询方法:  * findBy/getBy/queryBy/readBy 后面跟要查询的字段名,用于精确匹配。  * find/get/query/read 后面跟要查询的字段名,使用条件表达式进行模糊匹配。  * findAll/getAll 后面不跟字段名,表示查询所有记录。  *  * 支持的关键字:  * And:连接多个查询条件,相当于 SQL 中的 AND。  * Or:连接多个查询条件,相当于 SQL 中的 OR。  * Between:用于查询字段在某个范围内的记录。  * LessThan/LessThanEqual:用于查询字段小于某个值的记录。  * GreaterThan/GreaterThanEqual:用于查询字段大于某个值的记录。  * IsNull/IsNotNull:用于查询字段为空或不为空的记录。  * Like/NotLike:用于模糊查询字段值。  * OrderBy:用于指定查询结果的排序方式。  *  * 删除方法:  * deleteBy/removeBy 后面跟要查询的字段名,用于精确匹配。  * delete/remove 后面跟要查询的字段名,使用条件表达式进行模糊匹配。  *  * 统计方法:  * countBy 后面跟要查询的字段名,用于精确匹配。  * count 后面不跟字段名,表示统计所有记录数。  *  * 更新方法:  * updateBy 后面跟要查询的字段名,用于精确匹配。  * update 后面跟要查询的字段名,使用条件表达式进行模糊匹配。  *  * 支持的关键字:  * Set:用于设置要更新的字段的值。  * Where:用于指定更新操作的条件。  *  * 部分查询关键字映射示例  * 关键字                使用示例  * And                    findByLastnameAndFirstname  * Or                    findByLastnameOrFirstname  * Is,Equals            findByFirstnameIs,findByFirstnameEquals  * Between                findByStartDateBetween  * LessThan                findByAgeLessThan  * LessThanEqual        findByAgeLessThanEqual  * GreaterThan            findByAgeGreaterThan  * GreaterThanEqual        findByAgeGreaterThanEqual  * After                findByStartDateAfter  * Before                findByStartDateBefore  * IsNull                findByAgeIsNull  * IsNotNull,NotNull    findByAge(Is)NotNull  * Like                    findByFirstnameLike  * NotLike                findByFirstnameNotLike  * StartingWith            findByFirstnameStartingWith  * EndingWith            findByFirstnameEndingWith  * Containing            findByFirstnameContaining  * OrderBy                findByAgeOrderByLastnameDesc  * Not                    findByLastnameNot  * In                    findByAgeIn(Collection ages)  * NotIn                findByAgeNotIn(Collection age)  * TRUE                    findByActiveTrue()  * FALSE                findByActiveFalse()  * IgnoreCase            findByFirstnameIgnoreCase  */ public interface UserRepository extends JpaRepository<UserDO, Long> {      UserDO findByName(String name);      UserDO findByNameOrEmail(String name, String email);      Long countByName(String name);      List<UserDO> findByNameLike(String name);      UserDO findByNameIgnoreCase(String name);      List<UserDO> findByNameContainingOrderByAgeDesc(String name);      Page<UserDO> findByName(String name,Pageable pageable);  } JpaController /**  * jpa测试  */ @RestController public class JpaController {      @Autowired     private UserRepository userRepository;      @GetMapping("/findByName")     public UserDO findByName(String name) {         return userRepository.findByName(name);     }  } 测试 至此示例发送完成 总结 通过本文的介绍,我们了解了如何使用Spring Boot整合Jpa来进行数据库操作。  Spring Boot提供了简洁的配置和便捷的开发方式,而Jpa则提供了方便的数据库访问和操作方式。  将二者结合起来,可以更加高效地进行数据库开发。  希望本文对读者有所帮助,让大家能够快速上手并应用于实际项目中。  源码获取 如果需要完整源码请关注公众号"架构殿堂" ,回复 "SpringBoot+Jpa"即可获得  ———————————————— 版权声明:本文为CSDN博主「fking86」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/jinxinxin1314/article/details/134702470 
总条数:234 到第
上滑加载中