commit b2fc562a8cb820e9147d92c7f886a19fb5afa1d3 Author: huoxue1 <3343780376@qq.com> Date: Wed Jul 12 15:08:58 2023 +0800 init repo diff --git a/_hadoop的使用.md b/_hadoop的使用.md new file mode 100644 index 0000000..66f9988 --- /dev/null +++ b/_hadoop的使用.md @@ -0,0 +1,102 @@ +--- +title: hadoop的使用 +tags: default +categories: default +date: 2022-04-08 10:39:43 + +--- + +# 什么是hadoop + +> 分布式大数据平台 + +# hadoop组件 + ++ hdfs -- `分布式文件系统` ++ have -- `数据仓库` ++ HBase -- `分布式数据库` + +# hadoop的搭建 + +> master ---主要节点 -- 内存4500MB --cpu一个 -- 硬盘40Gb -- ip地址 10.0.0.100 + +> slave --- 从节点 --内存 2500MB --cpu一个 --硬盘40Gb -- ip地址 10.0.0.200 + +``` +systemtcl stop firewalled && systemtcl disable firewalled关闭防火墙 +/etc/selinux/config 关闭linux +setenforce 0 刷新配置文件 +/etc/hosts 本地host文件 +/etc/ntp.conf 时间配置文件 +scp source目录 用户名@ip:源文件目录 # 远程copy文件 +``` + +``` +ntpdate # 修改时间的工具 +ntp1.aliyun.com 阿里时间服务器 +ntpdate ntp1.aliyun.com 配置时间服务器 +clock -w 保存当前时间 +``` + +## 修改主机名 + +``` +hostnamectl set-hostname 主机名 +bash #刷新 +``` + +## 任务计划 + +``` +crontab -e 书写任务计划 +crontab -l 查询任务计划 +*/1 * * * * /sbin/ntp +systemctl restart crond 重启计划任务 +PATH=$JAVA_HOME/bin:$PATh +``` + +## 设置本地yum源 + +*/etc/yum.repo.d* + +``` +[hadoop] +name=hadoop +baseurl=file:/// +enable=1 +gpgcheck=1 + + +yum respse-list 查看yun源 +``` + +``` +09:52 +Absinthe +任务计划 crontab -e 书写任务计划 + crontab -l 查询任务计划 + + ntpdate + +*/1 * * * * /sbin/ntpdate s1 >> /var/log/ntpdate.log + + +hadoop java ---jdk + + + + + + +mariadb mariadb-server mysql-connector-java +ambari-server + + +create database ambari; ---创建一个库 + +grant all privileges on ambari.* to 'ambari'@'localhost' identified by 'bigdata'; + +---为数据库创建一个用户 ambari 密码为 bigdata +grant all privileges on ambari.* to 'ambari'@'%' identified by 'bigdata'; +---把权限交给ambari +``` diff --git a/git常用命令.md b/git常用命令.md new file mode 100644 index 0000000..5fdfd50 --- /dev/null +++ b/git常用命令.md @@ -0,0 +1,127 @@ +--- +title: git常用命令 +date: 2021-08-12 17:22:28 +tags: git +--- + ++++ + +## 1. git基本配置 + + + +### 1.1 配置邮箱和用户名 + +``` +# 这里的“huoxue1" 可以替换成自己的用户名 +git config --global user.name "huoxue1" +# 这里的邮箱 123456@163.com 替换成自己的邮箱 +git config --global user.email "123456@163.com" +``` + +### 1.2 生成ssh密钥 + +``` +# 这里的邮箱 123456@163.com 替换成自己的邮箱 +ssh-keygen -t rsa -C "123456@163.com" +``` + +默认密钥存储位置为 “C:\\\Users\你的用户名\.ssh\” + +### 1.3 配置到github + +将id_rsa.pub的内容复制下来然后在github的setting里面配置 + + + +## 2. 基本使用 + + + +### 2.1.初始化项目 + +```apl +git init #初始化项目git配置 + +git remote add origin git@github.com:huoxue1/LeafBot.git # 关联远程仓库,将远程仓库命名为origin + +``` + +```apl +# 也可以直接clone远程仓库 +git clone git@github.com:huoxue1/leafBot.git +``` + + + +### 2.2. 提交本地更改 + +```apl +git add . # .代表提交所有修改的文件,可以替换成对应的文件或者通配符 + +git commit -m “ 第一次提交” # m参数表示对这次提交的描述 + +git push -u origin master # 表示将本地当前分支内容提交的远程的master分支,第一次使用之后以后可以直接git push +``` + + + +### 2.3. 分支管理 + ++ 一般项目会将master或者main作为主要分支 + ++ 然后开发者会在dev分支进行开发,当需要发布版本时合并到主分支发布版本 + ++ 当线上版本出现bug需要修复时,会创建一个fix分支进行修复,修复完后将其合并到主分支,然后删除fix分支 + +```apl +git branch # 查看本地所有分支 + +git branch -a # 查看所有分支,包括远程分支 + +git checkout dev # 创建一个dev分支 + +git checkout -b dev # 创建一个dev分支并切换到该分支 + +git branch –set-upstream dev origin/dev # 把本地dev分支各远程dev分支进行关联 + +git merge --no-ff dev # 将dev分支合并到当前分支,使用--no-ff参数后,会执行正常合并,在当前分支上生成一个新节点 +``` + + + +### 2.4. tag管理 + ++ git作为版本管理,可以在特定时刻为其打上标签 + ++ Git 支持两种标签:轻量标签(lightweight)与附注标签(annotated)。 + + 轻量标签很像一个不会改变的分支——它只是某个特定提交的引用。 + + 而附注标签是存储在 Git 数据库中的一个完整对象, 它们是可以被校验的,其中包含打标签者的名字、电子邮件地址、日期时间, 此外还有一个标签信息,并且可以使用 GNU Privacy Guard (GPG)签名并验证。 通常会建议创建附注标签,这样你可以拥有以上所有信息。但是如果你只是想用一个临时的标签, 或者因为某些原因不想要保存这些信息,那么也可以用轻量标签。 + + ```apl + git tag -a v1.0 -m "我的第一个标签" + + # -a表示为附注标签,-m表示为其加上描述 + + git tag v1.1 + + # 默认为轻量标签 + + git tag show v1.0 #使用git tag show可以显示标签的信息 + + git push origin v1.0 # 将本地标签提交到远程成为共享标签 + + git tag -d # 删除本地标签 + + git push origin --delete # 删除远程标签 + + git checkout v1.0 # checkout命令可以使当前文件指向标签位置 + + ``` + + + + + diff --git a/java相关/java文件操作.md b/java相关/java文件操作.md new file mode 100644 index 0000000..fc10324 --- /dev/null +++ b/java相关/java文件操作.md @@ -0,0 +1,46 @@ +--- +title: java文件操作 +tags: java + +categories: 毕业设计 + +date: 2022-12-07 12:36:32 + +--- + +# java文件操作 + +### 1. 文件字符流 + +​ reader + +​ **writer** + + + *write()* + + *append()* + + *flush()* + + *close()* + +​ + ++ ### 文件字符输入流 + + FileReader + ++ ### 文件字符输出流 + + FileWriter + +### 2. 文件文件字节流 + +inputstream + +outputstream + ++ #### 文件字节输入流 + + ​ FileInputStream + ++ #### 文件字节输出流 + + ​ FileOutputStream diff --git a/java相关/springMvc整合undertow.md b/java相关/springMvc整合undertow.md new file mode 100644 index 0000000..6bb674d --- /dev/null +++ b/java相关/springMvc整合undertow.md @@ -0,0 +1,247 @@ +--- +title: springMvc整合undertow容器 + +categories: 毕业设计 + +date: 2023-02-09 10:39:24 + +tags: + + - java + + - spring mvc + + - undertow + +--- + +>         因为项目要求用springMvc,但是默认以war包发布到tomact下每一次启动都会很慢,所以希望能用一个嵌入式的web容器,了解到undertow对于小型项目还不错,就使用了undertow. + +>         最开始用的Jfinal-undertow,但是突然看到出了spring6.0,就想体验一下,spring6.0对应的servlet版本比较高,jfinal-undertow并不兼容,所以就自己找到了undertow进行使用,以下就是折腾的经历。 + +# 1. 依赖安装 + +```xml + + + io.undertow + undertow-servlet + 2.3.3.Final + + + + + io.undertow + undertow-core + 2.3.3.Final + +``` + +        在项目原本的依赖下加入以上两个依赖,版本需要对应一样,分别是undertow的核心依赖和undertow的servlet相关实现。 + +# 2. 运行整合 + +## 2.1 添加servlet + +```java +// 创建servletInfo,指定servlet的命名和servlet对应的class +ServletInfo info = new ServletInfo("mvc", DispatcherServlet.class) + // 添加映射路径 + .addMapping("/") + // 设置InitParam,制定了spring-mvc的配置文件位置 + .addInitParam("contextConfigLocation","classpath:spring-config/spring-mvc.xml") + // 设置什么时候进行初始化 + .setLoadOnStartup(1); +``` + +## 2.2 创建容器 + +   undertow的一个web容器就是一个**DeploymentManager**,该接口的默认实现类就是**DeploymentManagerImpl**,然后需要两个参数,分别是**DeploymentInfo**和**ServletContainer**,所以需要先创建DeploymentInfo,然后创建ServletContainer,因为ServletContainer也需要DeploymentInfo作为参数,代码如下所示。 + +```java +// 创建DeploymentInfo 对象 +DeploymentInfo deploymentInfo = new DeploymentInfo() + // 指定类加载器 + .setClassLoader(TestServer.class.getClassLoader()) + // 设置contextPath + .setContextPath("/") + // 设置容器名称 + .setDeploymentName("myUndertow") + // 设置初始化参数,制定了spring的配置文件的位置 + .addInitParameter("contextConfigLocation","classpath:spring-config/spring.xml") + // 设置了一个监听器,用于初始化spring + .addListener(new ListenerInfo(ContextLoaderListener.class)) + // 添加servlet + .addServlet(info) + // 添加一个过滤器,放置乱码问题 + .addFilter( + new FilterInfo("Encoding", CharacterEncodingFilter.class) + .addInitParam("encoding","utf-8") + ); + // 创建一个ServletContainer,并添加deploymentInfo + ServletContainerImpl container = new ServletContainerImpl(); + container.addDeployment(deploymentInfo); + // 创建一个DeploymentManagerImpl + DeploymentManagerImpl manager = new DeploymentManagerImpl(deploymentInfo, container); + // 部署该容器,一定要部署否则会报各种空指针 + manager.deploy(); +``` + +## 2.3 添加HttpHandler + +        因为是web项目,所以使用HttpHandler,HttpHandler默认有很多实现类,这里使用了最好理解的**PathHandler**,创建完之后添加到**Undertow**的Builder参数中,作为一个Listener,然后启动Undertow。 + +```java + // 创建一个PathHandler,然后添加一个前缀匹配,DeploymentManagerImpl 调用start方法后会返回一个HttpHandler类型 + PathHandler root = new PathHandler().addPrefixPath(deploymentInfo.getContextPath(), manager.start()); + // 创建一个Undertow对象并配置启动的端口等参数,然后调用start方法启动 + Undertow.Builder builder = Undertow.builder().addHttpListener(port, host, root); + Undertow undertow = builder.build(); + undertow.start(); + logger.info(" the undertow success listen http://"+host+":"+port); +``` + +## 2.4 读取配置文件 + +        我也希望向springBoot项目一样可以直接读取端口等参数直接启动,但是因为容器运行时spring容器还没有运行,所以我才用的方法是手动读取文件并注入字段。 + +```java + public void initConfig(String configPath) throws IllegalAccessException { + if (configPath.equals("")){ + configPath = "config/application.yml"; + } + // 读取yaml文件并转换为propperties + YamlPropertiesFactoryBean propertiesFactoryBean = new YamlPropertiesFactoryBean(); + propertiesFactoryBean.setResources(new ClassPathResource(configPath)); + // 配置log4j基本信息 + PropertyConfigurator.configure(propertiesFactoryBean.getObject()); + // 读取当前类的字段 + Field[] fields = this.getClass().getDeclaredFields(); + for (Field field:fields + ) { + // 检查是否包含Value注解 + if (field.getAnnotation(Value.class) != null) { + String value = field.getAnnotation(Value.class).value(); + value = value.substring(2).substring(0, value.length() - 3); + // 注入参数 + field.set(this,propertiesFactoryBean.getObject().get(value)); + } + } + } + + @Value("${server.host}") + public String host; + + @Value("${server.port}") + public Integer port; +``` + +# 3. 最后 + +完整代码如下 + +```java +package org.gjs; + +import io.undertow.Undertow; +import io.undertow.server.handlers.PathHandler; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.FilterInfo; +import io.undertow.servlet.api.ListenerInfo; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.core.DeploymentImpl; +import io.undertow.servlet.core.DeploymentManagerImpl; +import io.undertow.servlet.core.ManagedServlet; +import io.undertow.servlet.core.ServletContainerImpl; +import io.undertow.servlet.handlers.ServletHandler; +import io.undertow.servlet.handlers.ServletInitialHandler; +import io.undertow.servlet.handlers.ServletPathMatches; +import io.undertow.servlet.spec.ServletContextImpl; +import jakarta.servlet.ServletException; +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.gjs.config.MyUndertowConfig; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.filter.CharacterEncodingFilter; +import org.springframework.web.servlet.DispatcherServlet; + +import java.lang.reflect.Field; + +public class TestServer { + + private final Logger logger = Logger.getLogger(this.getClass()); + + + + public static void main(String[] args) throws ServletException, IllegalAccessException { + TestServer server = new TestServer(); + server.initConfig(""); + server.start(); + } + + + + public void initConfig(String configPath) throws IllegalAccessException { + if (configPath.equals("")){ + configPath = "config/application.yml"; + } + YamlPropertiesFactoryBean propertiesFactoryBean = new YamlPropertiesFactoryBean(); + propertiesFactoryBean.setResources(new ClassPathResource(configPath)); + // 配置log4j基本信息 + PropertyConfigurator.configure(propertiesFactoryBean.getObject()); + + Field[] fields = this.getClass().getDeclaredFields(); + for (Field field:fields + ) { + if (field.getAnnotation(Value.class) != null) { + String value = field.getAnnotation(Value.class).value(); + value = value.substring(2).substring(0, value.length() - 3); + field.set(this,propertiesFactoryBean.getObject().get(value)); + } + } + } + + @Value("${server.host}") + public String host; + + @Value("${server.port}") + public Integer port; + + public void start() throws ServletException { + + ServletInfo info = new ServletInfo("mvc", DispatcherServlet.class) + .addMapping("/") + .addInitParam("contextConfigLocation","classpath:spring-config/spring-mvc.xml") + .setLoadOnStartup(1); + + DeploymentInfo deploymentInfo = new DeploymentInfo() + .setClassLoader(TestServer.class.getClassLoader()) + .setContextPath("/") + .setDeploymentName("myUndertow") + .addInitParameter("contextConfigLocation","classpath:spring-config/spring.xml") + .addListener(new ListenerInfo(ContextLoaderListener.class)) + .addServlet(info) + .addFilter( + new FilterInfo("Encoding", CharacterEncodingFilter.class) + .addInitParam("encoding","utf-8") + ); + + ServletContainerImpl container = new ServletContainerImpl(); + container.addDeployment(deploymentInfo); + DeploymentManagerImpl manager = new DeploymentManagerImpl(deploymentInfo, container); + manager.deploy(); + + PathHandler root = new PathHandler().addPrefixPath(deploymentInfo.getContextPath(), manager.start()); + Undertow.Builder builder = Undertow.builder().addHttpListener(port, host, root); + Undertow undertow = builder.build(); + undertow.start(); + logger.info(" the undertow success listen http://"+host+":"+port); + } +} + +``` diff --git a/java相关/第一次使用springMvc的记录.md b/java相关/第一次使用springMvc的记录.md new file mode 100644 index 0000000..ae32c31 --- /dev/null +++ b/java相关/第一次使用springMvc的记录.md @@ -0,0 +1,197 @@ +--- +title: 第一次使用springMvc的记录 +date: 2021-08-12 17:25:07 +tags: java + +--- + ++++ + +# 第一次使用springmvc的记录 + +## 1. webxml配置文件 + +```xml + + + + org.springframework.web.context.ContextLoaderListener + + + + contextConfigLocation + classpath:spring/applicationContext.xml + + + + springmvc + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + classpath:spring/springmvc.xml + + 1 + + + springmvc + / + + +``` + +#### 1.1. listener的作用 + +配置listener的作用是在启动时初始化ioc容器 + +#### 1.2. context-param + +param-name为关键字:contextConfigLocation,当初始化ioc时,会从这个给关键字位置寻找spring的配置文件位置,param-value就为spring配置文件的位置 + +#### 1.3. 初始化servlet + +其中init-param中指定springMvc配置文件的位置 + +``` +1 +``` + +的意思为指定初始化的时间,如果不指定,当在第一个用户访问时才会进行初始化,会导致第一个用户访问时间变得特别长 + +#### 1.4. 指定拦截器 + +seevlet-mapping为指定一个过滤器,url-pattern的参数为拦截的url,servlet为拦截后交个哪一个servlet进行处理 + +## 2. 前端控制器的配置 + +```xml + + + + + + + + + + + + + +``` + +#### 2.1. 指定静态文件资源路径 + +```xml + +``` + +mapping 为映射后的路径,location为相对于web目录的路径 + +例如**"/page/"**表示将page文件夹放在web根目录下面 + +#### 2.2. 设置控制器扫描路径 + +```xml + +``` + +**base-package**为mvc控制器类的路径 + +#### 2.3. 配置mvc的试图模板 + +```xml + + + + +``` + +**prefix**参数为试图模板的前缀路径 + +**suffix**参数为视图模板的后缀 + +## 3. mybatis与spring的整合 + +```xml + + + + + + + + + + + + + + + + + + + + + +``` + +#### 3.1. 加载property配置文件 + +```xml + +``` + + **location**为需要加载的文件的位置 + +#### 3.2. 配置mybatis数据源 + +```xml + +``` + ++ *username* 数据库的用户名 + ++ *password* 数据库的密码 + ++ *url* 数据库连接的网址 + ++ *driverClassName* 数据库连接的驱动名 + ++ *maxActive* 数据库连接池的最大数量 + ++ *minIdle* 数据库连接池的最小空闲数量 + +#### 3.3. 配置mybatis的注解扫描类 + +```xml + +``` + +**basePackage**为mybatis类的权限类名 + +#### 3.4. 配置mybatis的SqlSessionFactoryBean + +```xml + +``` + ++ **dataSource**为mybatis的数据源引用 + ++ **mapperLocations**为mybatis的xml配置文件位置,当**mybatis的配置文件与其接口类在同一路径**,且**接口名等于配置文件的namespece参数**时,就可以不需要配置该属性,spring为对其进行自动扫描 diff --git a/leafbot源码解读.md b/leafbot源码解读.md new file mode 100644 index 0000000..a25a0d4 --- /dev/null +++ b/leafbot源码解读.md @@ -0,0 +1,353 @@ +--- +title: leafbot源码解读 +tags: bot,go +categories: bot +date: 2021-12-31 22:05:17 + +--- + +# leafbot简介 + ++ ### leafbot是一个go语言实现的[onebot11](https://github.com/botuniverse/onebot-11)sdk实现之一,是一个免费的开源的go语言库,具有开发简单,持续维护等优点,可对接遵循onebot标准的实现,主要对接[go-cqhttp](https://github.com/Mrs4s/go-cqhttp) ++ ### leafbot的github仓库地址为: ++ ### leafbot文档地址为: + +# driver介绍 + +driver为和onebot端的通信抽象层实现,目前内置了四种driver,分别对应了go-cqhttp的三种通信模式,还有一个直接连接go-cqhttp进行内置启动,driver包含在形目的driver文件夹下面,每一个driver需要实现如下接口。 + +```go +type Driver interface { + // Run + // @Description: 运行该驱动的接口,该接口应该为阻塞式运行 + // + Run() + // GetEvent + // @Description: 返回一个chan,该chan为事件传递的chan + // @return chan + // + GetEvent() chan []byte + // 当一个bot连接时的回调 + OnConnect(func(selfId int64, host string, clientRole string)) + // 当bot断开连接时的回调 + OnDisConnect(func(selfId int64)) + + // GetBot + // @Description: 获取一个实现了APi接口的bot + // @param int64 bot的id + // @return interface{} + // + GetBot(int64) interface{} + // GetBots + // @Description: 获取所有bot + // @return map[int64]interface{} + // + GetBots() map[int64]interface{} + // 给驱动设置一些运行信息,例如运行地址以及端口之类的 + SetConfig(config map[string]interface{}) + // 给驱动添加一个webhook监听,主要用于cqhttp_http_driver + AddWebHook(selfID int64, postHost string, postPort int) + // 给driver设置token,用于onebot端的鉴权 + SetToken(token string) +} +``` + ++ ## cqhttp_http_driver + +该驱动对应go-cqhttp的http连接方式,需要在配置文件中配置listen_address和listen_port,分别为leafbot的监听地址,还需要配置对应的webhook,对应了go-cqhttp端的监听地址。 + +driver通过实现了http包中的handler接口,即实现了ServerHttp方法,在ServerHttp方法中监听来自cqhttp的上报消息,并在在run方法中注册bot对象,每一个bot对象记录了自己上报端的接口地址和自己的selfId,bot通过调用Do方法进行api的调用 + +```go + + +// 实现了ServerHttp方法 +func (d *Driver) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + data, err := io.ReadAll(request.Body) + if err != nil { + return + } + + d.eventChan <- data + writer.WriteHeader(200) +} + + +// 注册Bot对象 +func (d *Driver) Run() { + log.Infoln("Load the cqhttp_http_driver successful") + for _, s := range d.webHook { + b := new(Bot) + b.selfID = s.selfID + b.postHost = s.postHost + b.postPort = s.postPort + b.responses = sync.Map{} + b.disConnectHandle = d.disConnectHandle + b.client = gout.NewWithOpt() + b.token = d.token + d.bots.Store(s.selfID, b) + } + log.Infoln("Load the cqhttp_http_driver successful") + log.Infoln(fmt.Sprintf("the cqhttp_http_driver listening in %v:%v", d.listenHost, d.listenPort)) + if err := http.ListenAndServe(fmt.Sprintf("%v:%v", d.listenHost, d.listenPort), d); err != nil { + log.Errorln("监听webhook失败" + err.Error()) + } +} + + +// 进行Api的调用 +func (b *Bot) Do(i interface{}) { + type userAPi struct { + Action string `json:"action"` + Params interface{} `json:"params"` + Echo string `json:"echo"` + } + data := i.(userAPi) + var resp []byte + err := b.client.POST(fmt.Sprintf("http://%v:%v/%v", b.postHost, b.postPort, data.Action)). + SetHeader(gout.H{"Authorization": "Bearer " + b.token}). + SetJSON(data.Params). + BindBody(&resp).Do() + if err != nil { + log.Errorln("调用api出现错误", err.Error()) + return + } + b.responses.Store(data.Echo, resp) +} +``` + ++ ## cqhttp_positive_driver + + 该驱动对应了go-cqhttp的正向websocket连接方式,即go-cqhttp为服务的,leafbot为客户端,只需要在配置文件中配置**host**和**port**即可,对应了go-cqhttp的正向ws监听地址。 + + 该驱动通过直接在Run方法中主动连接onebot端进行通信,然后再连接成功后从请求头中获取到bot的SelfId。并且创建Bot对象,bot对象中持有ws的conn连接对象,可以通过该对象主动进行Api调用。 + + ```go + func (d *Driver) Run() { + u := url.URL{Scheme: "ws", Host: d.address + ":" + strconv.Itoa(d.port)} + header := http.Header{} + header.Add("Authorization", "Bearer "+d.token) + conn, _, err := websocket.DefaultDialer.Dial(u.String(), header) //nolint:bodyclose + if err != nil { + return + } + log.Infoln("Load the cqhttp_positive_driver successful") + _, data, err := conn.ReadMessage() + if err != nil { + return + } + selfId := gjson.GetBytes(data, "self_id").Int() + role := "" + host := d.address + + b := new(Bot) + b.conn = conn + b.selfId = selfId + b.responses = sync.Map{} + + _, ok := d.bots.Load(selfId) + if ok { + d.bots.LoadOrStore(selfId, b) + } else { + d.bots.Store(selfId, b) + } + + d.connectHandle(selfId, host, role) + b.disConnectHandle = d.disConnectHandle + log.Infoln(fmt.Sprintf("the bot %v is connected", selfId)) + go func() { + defer func() { + i := recover() + if i != nil { + log.Errorln("ws链接读取出现错误") + log.Errorln(i) + d.disConnectHandle(selfId) + } + }() + for { + _, data, err := conn.ReadMessage() + if err != nil { + b.wsClose() + } + + echo := gjson.GetBytes(data, "echo") + if echo.Exists() { + b.responses.Store(echo.String(), data) + } else { + d.eventChan <- data + } + } + }() + } + + ``` + +``` + ++ ## cqhttp_reverse_driver + + 该驱动对应了cqhttp的反向websocket连接方式,需要配置host和port即可,该驱动leafbot作为服务器,onebot端主动连接leafbot,所以可以实现同时连接多个onebot实现。 + + 驱动通过实现http包中的handler接口,再SeverHttp方法中,升级http协议为websocket协议,然后再升级成功后从header中获取selfId,然后创建Bot对象,bot对象分别持有websocket连接对象,可以通过对象赖调用对应的Onebot的api. + +```go +func (d *Driver) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + selfID, err := strconv.ParseInt(request.Header.Get("X-Self-ID"), 10, 64) + role := request.Header.Get("X-Client-Role") + host := request.Header.Get("Host") + if d.token != "" { + get := request.Header.Get("Authorization") + auth := strings.Split(get, " ") + if auth[0] != "Bearer" || auth[1] != d.token { + log.Errorln("the token is not current!") + return + } + } + conn, err := upgrade.Upgrade(writer, request, nil) + if err != nil { + return + } + b := new(Bot) + b.conn = conn + b.selfId = selfID + b.responses = sync.Map{} + _, ok := d.bots.Load(selfID) + if ok { + d.bots.LoadOrStore(selfID, b) + } else { + d.bots.Store(selfID, b) + } + b.disConnectHandle = d.disConnectHandle + log.Infoln(fmt.Sprintf("the bot %v is connected", selfID)) + // 执行链接回调 + go d.connectHandle(selfID, host, role) + go func() { + defer func() { + err := recover() + if err != nil { + b.wsClose() + log.Errorln("ws链接读取出现错误") + log.Errorln(err) + } + }() + for { + _, data, err := conn.ReadMessage() + if err != nil { + b.wsClose() + } + + echo := gjson.GetBytes(data, "echo") + if echo.Exists() { + b.responses.Store(echo.String(), data) + } else { + d.eventChan <- data + } + } + }() +} + +``` + +## cqhttp_default_driver + +该驱动为leafbot最新版本添加的驱动,通过调用go-cqhttp的registerServer对象,直接实现内置go-cqhttp来实现铜线,驱动在run方法中调用了gocq.Main()方法,并且注册了一个默认的服务,注册了一个事件回调,使用该驱动控制台同时会输出go-cqhttp的日志和leafbot日志。并且leafbot一些日志配置会被go-cqhttp所覆盖。 + +```go + + +func (d *Driver) Run() { + servers.RegisterCustom("leafBot", func(bot *coolq.CQBot) { + b := new(Bot) + b.CQBot = bot + b.call = api.NewCaller(bot) + d.bot = b + bot.OnEventPush(func(e *coolq.Event) { + data := e.JSONString() + result := gjson.Parse(data) + if result.Get("message").Exists() { + m := message.ParseMessageFromString(result.Get("message").String()) + data, _ = sjson.Set(data, "message", m) + } + d.EventChan <- []byte(data) + }) + }) + gocq.Main() +} + +``` + +# Plugin介绍 + +leafbot使用命令模式进行运作,先注册插件,然后去回调插件中的方法,其中一个插件包含多个Matcher,一个Matcher为最小的执行者。 + +Plugin结构体中存储了当前插件的插件名、插件帮助以及插件所包含的Matcher,可以通过NewPlugin(name string)方法进行注册一个插件,插件实现了多个注册Matcher的方法。 + +```go + // Plugin所实现的方法 + basePlugin interface { + OnCommand(command string, options ...Option) Matcher + OnMessage(messageType string, options ...Option) Matcher + OnRequest(requestType string, options ...Option) Matcher + OnNotice(noticeType string, options ...Option) Matcher + OnMeta(options ...Option) Matcher + OnRegex(regexMatcher string, options ...Option) Matcher + + OnStart(start string, options ...Option) Matcher + OnEnd(end string, options ...Option) Matcher + OnFullMatch(content string, options ...Option) Matcher + OnFullMatchGroup(content string, options ...Option) Matcher + + OnConnect(options ...Option) Matcher + OnDisConnect(options ...Option) Matcher + } +``` + + 其中Matcher接口为如下内容,Matcher接口为为Matcher添加内容 + +```go + Matcher interface { + MatcherSet + Enabled() bool + GetHandler() Action + GetRules() []Rule + GetWeight() int + IsBlock() bool + GetDisAbleGroup() []int64 + GetType() string + GetPluginType() string + } + + MatcherSet interface { + AddRule(rule Rule) Matcher + SetWeight(weight int) Matcher + SetBlock(block bool) Matcher + SetAllies(allies []string) Matcher + + Handle(action Action) + } +``` + + 注册一个最小插件的示例为: + +```go + +func init() { + plugin := leafbot.NewPlugin("测试") + plugin.OnCommand("测试", leafbot.Option{ + Weight: 0, + Block: false, + Allies: nil, + Rules: []leafbot.Rule{func(ctx *leafbot.Context) bool { + return true + }}, + }).Handle(func(ctx *leafbot.Context) { + ctx.Send(message.Text("123")) + }) +} +``` + + 该插件会匹配命令测试,并且会回应123 + +# 更多 + +## 了解更多leafbot内容,请查看leafbot[文档](https://vtsqr.xyz/leafbot/dist/) diff --git a/linux软件的安装.md b/linux软件的安装.md new file mode 100644 index 0000000..70da9fd --- /dev/null +++ b/linux软件的安装.md @@ -0,0 +1,67 @@ +--- +title: linux软件的安装 +tags: linux +categories: linux +date: 2022-03-25 10:58:34 +--- + +# 查询已经安装的软件 +## 软件的查询 +```shell +rpm -qa | grep 软件名 +``` +## 软件包的版本查询 +```shell +rpm -qi 软件名 +``` +软件包的常用信息 +``` +Name: bzip2 +Version: 1.0.6 +Release : 0.17.20131004.git.e17 +Install DateL Thu 17 Mar 2022 08:39:28 +Size: 83791 +URL: http://www.bzip.org # 软件包来源 + +``` + +## 软件包的安装位置 +``` +rmp -ql 软件名 +``` +```shell +/etc -- 配置文件 +/usr/bin/ -- 启动文件 +/usr/share/man/ -- 用户手册 +/var/lib/mysql -- 数据库文件 +/var/log/ -- 日志目录 +/var/run/ -- 进程号文件 +``` + +# 查询未安装的包的详细信息 +``` +rpm -qpi 软件报名 +rpm -qpl +``` + +# 软件的卸载 +``` +rpm -e 软件名 +rpm -ev 软件名 显示卸载信息并卸载 +rpm -evh 软件名,显示卸载信息和进度 +``` + +# 软件的安装 +``` +rmp -i 软件包名 +rmp -i 软件包名 --nodeps # 忽略依赖关系安装 +``` + +# 光驱的挂载与卸载 +``` +/dev 光驱的目录 +mount /dev/sr0 /mnt -- 挂载光驱 +df -- 查询挂载 +umount /dev/sr0 -- 卸载光盘 + +``` \ No newline at end of file diff --git a/office之excel基础讲解.md b/office之excel基础讲解.md new file mode 100644 index 0000000..c50520c --- /dev/null +++ b/office之excel基础讲解.md @@ -0,0 +1,131 @@ +--- +title: office之excel基础讲解 +tags: office +categories: default +date: 2022-04-16 19:33:20 +--- + +# Office +常用的office有以下两种 ++ ### Wps Office + - 包括了Word和excel和ppt ++ ### Micosoft Office + - Word 文档编辑软件 + - Excel 表格数据处理软件 + - PPt 幻灯片制作软件 +> 因为一般的windows笔记本电脑都预装了Micosoft Office,所以我们一般都使用Micosoft Office,下面我们讲的Office都是Micosoft Office。 + +> 当然,计算机二级即有Micosoft Office,也有Wps Office,所以大家要根据自己的使用情况进行报名。 + +> 计算机二级考试从2021年3月份开始,考试将才用Office 2016版本进行考试,学校机房预装版本为office2010,一般笔记本预装版本为office 2010,虽然各个版本相差不大,但是大家如果以防万一还是最好选择自行安装Office 2016版本 + +> office安装包下载网站推荐: https://msdn.itellyou.cn/ + + +# Excel基础 + +## 1.1. excel文件创建 +![](https://s2.loli.net/2022/04/16/kAv3RSXbNUayKMY.png) + +## 1.2. excel的构成 ++ 一个excel文件为一个工作簿,excel文件的后缀为```.docx```,老版本office生成的后缀名为```.doc``` ++ 一个工作簿包括了多个工作表 + +![](https://s2.loli.net/2022/04/16/DxzYMwl5u3WZokh.png) + ++ 一个工作表包括了无数个的单元格 + +## 1.3 excel的行高和列宽 +> 可直接通过拖动进行设置,也可以右键行头或列头进行设置 +![](https://s2.loli.net/2022/04/16/v1YMyHNx94CVnsS.png) +![](https://s2.loli.net/2022/04/16/gtNUp1ZqAuM9k8P.png) + +![](https://s2.loli.net/2022/04/16/GRcDfdk89PEs4w5.png) +> 友情提醒:行高和列宽都可以通过格式刷进行改变,所以我们只需要设置一行合适的行高,然后用格式刷就可以了 + +> 当出现 ```######```的时候一般是因为列宽太窄了,就会导致出现上述情况,只需要将列宽增大就可以 + +## 1.4 excel的表头制作 +> excel表头一般通过合并单元格生成 +![](https://s2.loli.net/2022/04/16/tlIXN1WdY9w7nMh.png) + +## 1.5 excel的数据类型 +![](https://s2.loli.net/2022/04/16/k9vJ1ZDCipwa45c.png) +> 当输入大数字时,默认会变成科学计数法的形式显示,我们只需要在前面加上```'```就可以显示原本的样式 + +## 1.6 excel边框 ++ 内边框 ++ 外边框 + +![](https://s2.loli.net/2022/04/16/y4i5R3olJC2zF7H.png) + +## 1.7 excel条件格式 +> 可以根据特定的条件而设置单元格的格式 +![](https://s2.loli.net/2022/04/16/kV9G25iMbaQSzCY.png) + +![](https://s2.loli.net/2022/04/16/NR6aldZUcBzViOs.png) + +## 1.8 excel序列填充 +![](https://s2.loli.net/2022/04/16/Qvyc9mb67TldPMA.png) + +## 1.9 excel排序筛选 +![](https://s2.loli.net/2022/04/16/4k6Cg3Bdt1LEymj.png) + +## 1.10 excel图表生成 +![](https://s2.loli.net/2022/04/16/5P42MzAfpxjdDEO.png) + +# Excel进阶知识 + +## 2.1 Excel的公式 +> excel的公式可以说是excel的灵魂,用的好可以减少很多没必要的工作量,同时在计算机二级中明确要求了公式的使用。 +![](https://s2.loli.net/2022/04/16/OAoNViE2CzkZScf.png) + +> 公式的原理类似高中数学所学的函数 ```y = f(x) ```,我们需要将对于的单元格引用传给公式,公式会自动根据规则计算结果并输出到当前单元格。 + +> 单元格的引用,单元格通过两个坐标进行引用,例如```A1 A2 B3```,此种引用为相对引用,当我们将公式所在单元格进行序列填充时,所引用的单元格会进行对应方向改变。例如B1单元格的公式为```=A1+C1```,如果我们将单元格序列填充到B2时,公式会自动变成```A2+C2```,如果我们希望填充后,对应引用不改变,那么我们可以在引用前加上```$```,例如```=$A$1+$B$1``` + +> 单元格可以跨表引用,即在引用前加上表名,例如:```Sheet1!B2```,代表引用Sheet1表格的B2单元格 + +## 2.1.1 公式的四则运算 + ++ 加 ```+``` ++ 减 ```-``` ++ 乘 ```*``` ++ 除 ```/``` + +例如: + +![](https://s2.loli.net/2022/04/16/eSQ9NfGztOVJnH7.png) + +> 当然,如果很多值进行四则运算时,也有对应的公式,例如+可以使用```sum()```公式,-可以用```imsub()```公式,*可以用```product()```代替,/可以用```quotient()```代替 + +![](https://s2.loli.net/2022/04/16/l8E9cdvfYnxpq5C.png) + ++ ```if```公式 + +![](https://s2.loli.net/2022/04/16/Zlfqcaju5JOrGeX.png) + +> 通俗的理解为,需要三个参数,第一个参数为一个表达式,例如大于,小于,等于,第二个为如果表达式成立的返回,第三个为表达式不成立的返回值。 + +![](https://s2.loli.net/2022/04/16/ORKLwJSXDNGivCI.png) + ++ ```vlookup```公式 + +> 用于条件查询 + +![](https://s2.loli.net/2022/04/16/au3WRYETkhivVBI.png) + +![](https://s2.loli.net/2022/04/16/c7QfHXzrweGLuvj.png) + +![](https://s2.loli.net/2022/04/16/krVv6pXoFEHCj9P.png) + +``` +vlookup(要查找的值,查找的范围,需要的值在范围中的第几列,模糊查询还是准确查询) + +上面的方法含义为:在B3到C7中模糊查找姓名为王五的的第二列的值 +``` + +# 关于 +> excel的更多使用技巧就希望同学们能够自行摸索了,毕竟实践课程只有自己摸索才是最有用的,在实践中遇到不懂得可以合理运用搜索引擎 + +## 个人博客 https://shhy.xyz \ No newline at end of file diff --git a/pg指令记录.md b/pg指令记录.md new file mode 100644 index 0000000..4c66601 --- /dev/null +++ b/pg指令记录.md @@ -0,0 +1,36 @@ +--- +title: pg指令记录 +tags: postgres +categories: 笔记 +date: 2023-05-23 15:55:31 +--- + +# 1. pg查询慢查询进程 + +```sql +-- 查询进程 +SELECT C.procpid, C.START, C.lap, C.current_query +FROM + (SELECT pg_stat_get_backend_pid (S.backendid) AS procpid, + pg_stat_get_backend_activity_start (S.backendid) AS START, + now() - pg_stat_get_backend_activity_start (S.backendid) AS lap, + pg_stat_get_backend_activity (S.backendid) AS current_query + FROM + (SELECT pg_stat_get_backend_idset () AS backendid) AS S) AS C +WHERE current_query <> '' + AND lap > '00:00:10' +ORDER BY start DESC; +``` + +# 2. pg杀死进程 + +```sql +-- 杀进程 +SELECT pg_terminate_backend(25278); +``` + +# 3. 重新跑脚本 + +```sql + +``` diff --git a/vi的基础使用.md b/vi的基础使用.md new file mode 100644 index 0000000..cfed646 --- /dev/null +++ b/vi的基础使用.md @@ -0,0 +1,35 @@ +--- +title: vi的基础使用 +tags: linux +categories: linux +date: 2022-03-25 10:37:09 +--- + +# vi的三种模式 ++ 命令模式 ++ 末行模式 ++ 编辑模式 + +> 命令模式 ==》末行模式 快捷键:冒号 + +>命令模式 ==》 编辑模式 快捷键: a,i,o ,a代表在光标前输入,i代表在光标后输入,o代表换行输入 + +>编辑模式 ==》 命令模式 快捷键:esc + +# 从vi中退出: ++ 进入命令模式 ++ 输入**wq** 保存并退出 ++ 输入**q** 不保存退出 ++ 输入q!强制退出 + +# 快捷键 ++ 移动到末尾,命令模式输入 +```:G``` ++ 移动到第一行,行尾模式输入```:1``` ++ 显示行数,行尾模式输入```:set nu``` ++ 取消行数,行尾u模式输入```:set nonu``` ++ 查询,输入```/``` ++ 删除 在命令模式下输入```dd```,删除一整行,在行尾模式输入```:.,%d```,删除所有 ++ 复制,在行尾模式输入```:yy```,复制一整行,命令模式下```p```粘贴 ++ 命令模式```u```撤销上一步操作 ++ 行尾模式下 ```s /old/new/g```进行替换 \ No newline at end of file diff --git a/从零开始搭建一个群管机器人.md b/从零开始搭建一个群管机器人.md new file mode 100644 index 0000000..5da6bc2 --- /dev/null +++ b/从零开始搭建一个群管机器人.md @@ -0,0 +1,182 @@ +--- +title: 从零开始搭建一个群管机器人 +tags: bot +categories: bot,go +date: 2021-12-28 22:05:42 +--- + +# 环境搭建 + +## 安装golang ++ 从golang[中文官网](https://studygolang.com/dl)下载go语言安装包 ++ 下载完成后解压配置环境变量 + +命令行输入**go version** + +![](https://gitee.com/aabbccddeeeff/img/raw/master//img/202112282212881.png) + +## 安装goland ++ 从goland官网下载goland安装包 ++ 双击后正常安装就可 + +# 创建项目 + +## 初始化项目 ++ 打开goland,创建go项目,取名为leafBot-plugin ++ 初始化项目```go mod init github.com/huoxue1/fan``` ++ 安装leafbot依赖 ```go get github.com/huoxue1/leafbot``` ++ 跟项目下创建**main.go**文件 + +## 编写main.go文件,初始化leafBot ++ 在**main.go**中修改package为main ++ 创建main方法,添加如下代码 +```go +// main方法,项目运行起点 +func main() { + // 创建一个cqhttp的driver + driver := cqhttp_default_driver.NewDriver() + // 为leafBot加载该驱动 + leafbot.LoadDriver(driver) + // 初始化leafBot + leafbot.InitBots() + // 运行cqhttp驱动 + driver.Run() +} +``` ++ 一般main.go会自动导入如下依赖 +```go +import ( + // leafbot核心依赖 + "github.com/huoxue1/leafbot" + // leafbot的cqhttp与leafbot直接结合所用的依赖 + "github.com/huoxue1/leafbot/cqhttp_default_driver" +) + +``` ++ main.go最终结果为 +```go +package main + +import ( + "github.com/huoxue1/leafbot" + "github.com/huoxue1/leafbot/cqhttp_default_driver" +) + +func main() { + driver := cqhttp_default_driver.NewDriver() + leafbot.LoadDriver(driver) + leafbot.InitBots() + driver.Run() +} +``` ++ 首次运行该项目,输入bot的qq号即可 +![](https://gitee.com/aabbccddeeeff/img/raw/master//img/202112282225028.png) ++ 再次运行,因为还需要初始化go-cqhttp的配置文件,所有好需要交互 +![](https://gitee.com/aabbccddeeeff/img/raw/master//img/202112282226998.png) ++ 再次运行即可运行成功,生成二维码后使用手机扫码登录即可 +![](https://gitee.com/aabbccddeeeff/img/raw/master//img/202112282227074.png) + +# 插件编写 + +## 创建插件项目 ++ 在项目跟目录添加文件夹plugin,在plugin下继续创建文件夹plugin-manager,并且创建manager.go文件 ++ 在manager.go文件夹中添加如下代码 +```go +package group_manager + +import ( + "fmt" + "strconv" + + "github.com/huoxue1/leafbot" + "github.com/huoxue1/leafbot/message" +) + +func init() { + manager() +} + +func manager() { + // 创建一个leafBot插件,插件名为group-manager + plugin := leafbot.NewPlugin("group-manager") + // 为plugin添加一个matcher,匹配器为Onstart,即匹配消息开始为升为管理的语句 + plugin.OnStart("升为管理", leafbot.Option{ + // 添加matcher的权重,权重越低,越先匹配 + Weight: 1, + // 设置匹配成功后是否匹配其他matcher + Block: true, + Rules: []leafbot.Rule{}, + }).Handle(func(ctx *leafbot.Context) { + // 遍历事件中的message + for _, v := range ctx.Event.Message { + // 判断事件中的消息类型weiat + if v.Type == "at" { + // 取出at的qq号,并强制转为整形 + qq, _ := strconv.Atoi(v.Data["qq"]) + // 调用api,为其设置为管理员 + ctx.Bot.(leafbot.OneBotApi).SetGroupAdmin(ctx.Event.GroupId, qq, true) + // 发送消息,xxx已升为管理员 + ctx.Send(message.Text(fmt.Sprintf("%v已经升为管理员", qq))) + } + } + }) + + // 取消管理 + plugin.OnStart("取消管理",leafbot.Option{ + Weight: 1, + Block: true, + }).Handle(func(ctx *leafbot.Context) { + for _, v := range ctx.Event.Message { + if v.Type == "at" { + qq, _ := strconv.Atoi(v.Data["qq"]) + ctx.Bot.(leafbot.OneBotApi).SetGroupAdmin(ctx.Event.GroupId, qq, false) + ctx.Send(message.Text(fmt.Sprintf("%v已被取消管理员", qq))) + } + } + }) + +} + +``` ++ 在main.go中导入该插件,即在import中添加 +``` +import( + "github.com/huoxue1/leafbot" + "github.com/huoxue1/leafbot/cqhttp_default_driver" + + _ "github.com/huoxue1/fan/plugin/group-mamanger" +) +``` ++ 运行后,在机器人为群主的群里发送,升为管理并且艾特需要升为管理的人即可设置管理 + +# leafBot解释 + +leafBot为一个go语言版本实现了onebot11协议的SDK,推荐对接onebot为go-cqhttp +leafBot除了Onstart匹配器外,还有匹配器 +```go +// 命令匹配 +OnCommand(command string, options ...Option) Matcher +// 消息匹配 +OnMessage(messageType string, options ...Option) Matcher +// 请求事件匹配 +OnRequest(requestType string, options ...Option) Matcher +// 提醒事件匹配 +OnNotice(noticeType string, options ...Option) Matcher +// 元事件匹配 +OnMeta(options ...Option) Matcher +// 正则匹配 +OnRegex(regexMatcher string, options ...Option) Matcher +// 匹配字符串开始 +OnStart(start string, options ...Option) Matcher +// 匹配字符串结尾 +OnEnd(end string, options ...Option) Matcher +// 消息完全匹配 +OnFullMatch(content string, options ...Option) Matcher +// 群组消息完全匹配 +OnFullMatchGroup(content string, options ...Option) Matcher +// bot连接回调 +OnConnect(options ...Option) Matcher +// bot断开回调 +OnDisConnect(options ...Option) Matcher +``` +更多内容参考[LeafBot](https://github.com/huoxue1/leafbot) diff --git a/从零开始搭建一个自动回复qq机器人.md b/从零开始搭建一个自动回复qq机器人.md new file mode 100644 index 0000000..becc6f0 --- /dev/null +++ b/从零开始搭建一个自动回复qq机器人.md @@ -0,0 +1,68 @@ +--- +title: 从零开始搭建一个自动回复qq机器人 +tags: + - bot + - go +categories: bot +date: 2021-08-22 20:23:05 +summary: 只需简单几个步骤,即可搭建一个功能众多的qq机器人。 +--- + + + +## 1. 下载leafBotPlugin + ++ #### 打开[leafBotPlugin](https://github.com/huoxue1/leafBotPlugin/releases) + ++ #### 选择最新的版本,下载leafBotPlugin_windows_amd64.exe + ++ #### 选择一个合适的文件夹放置该文件 + ++ #### 双击打开leafBotPlugin_windows_amd64.exe,在黑窗口中输入自己的机器人qq号 + ++ #### 会在当前目录的config目录下生成两个文件,分别是congfig.json和config.yml + +## 2. 下载go-cqhttp + ++ #### 打开[go-cqhttp](https://github.com/Mrs4s/go-cqhttp/releases) + ++ #### 选择最新版本,下载go-cqhttp_windows_amd64.exe,将其重命名为go-cqhttp.exe + ++ #### 选择一个空文件夹放置文件,将刚才生成的config,yml移动到当前文件夹 + ++ #### 创建一个文本文件,输入以下内容 + ++ ``` + ./go-cqhttp.exe + ``` + ++ #### 将其重命名为start.bat,双击打开文件,待其出现验证码后使用手机qq进行扫码登录 + ++ #### 此时继续打开leafBotPlugin_windows_amd64.exe + +## 3. 验证 + +##### 使用qq向机器人qq发送 + +``` +/echo 123 +``` + +###### 如果机器人回复 + +``` +123 +``` + +###### 证明机器人搭建成功 + +## 4.更多命令 + +[机器人帮助](https://vtsqr.xyz/leafBotPlugin/Features) + +需要更多功能欢迎联系邮箱:3343780376@qq.com + +或直接在github提交issue + + + diff --git a/异常处理/playwright报错Host-system-is-missing-dependencies.md b/异常处理/playwright报错Host-system-is-missing-dependencies.md new file mode 100644 index 0000000..e1e90c2 --- /dev/null +++ b/异常处理/playwright报错Host-system-is-missing-dependencies.md @@ -0,0 +1,51 @@ +--- +title: playwright报错Host system is missing dependencies +tags: exception +categories: 异常处理 +date: 2022-04-12 13:59:34 +--- + +在使用playwright的过程中,在linux中运行总是会出现各种依赖问题,今天尝试解决。 +# playwright + +## 报错 **Host system is missing dependencies** + ++ 系统:centos7 ++ playwright: 使用go-playwright@v1.14 ++ 浏览器: firefox ++ 具体报错 +
+ 展开查看 +

+    could not send message: could not send message to server: Host system is missing dependencies!
+    Missing libraries are:
+        libgtk-3.so.0
+        libgdk-3.so.0
+        libX11.so.6
+        libX11-xcb.so.1
+        libxcb.so.1
+        libXcomposite.so.1
+        libXcursor.so.1
+        libXdamage.so.1
+        libXext.so.6
+        libXfixes.so.3
+        libXi.so.6
+        libXrender.so.1
+        libfontconfig.so.1
+        libpangocairo-1.0.so.0
+        libpango-1.0.so.0
+        libharfbuzz.so.0
+        libatk-1.0.so.0
+        libcairo-gobject.so.2
+        libcairo.so.2
+        libgdk_pixbuf-2.0.so.0
+        libxcb-shm.so.0
+        libpangoft2-1.0.so.0
+        libXt.so.6
+    
+
+ +### 解决方式,先查询playwright的[issue](https://github.com/microsoft/playwright/issues/5893) +```shell +yum -y install libappindicator-gtk3 && yum -y install liberation-fonts && yum install libXt +``` \ No newline at end of file diff --git a/异常处理/playwright报错缺少libatk-1-0-so-0-异常处理-md.md b/异常处理/playwright报错缺少libatk-1-0-so-0-异常处理-md.md new file mode 100644 index 0000000..5b4a936 --- /dev/null +++ b/异常处理/playwright报错缺少libatk-1-0-so-0-异常处理-md.md @@ -0,0 +1,92 @@ +--- +title: 'playwright报错缺少libatk-1.0.so.0:异常处理.md' +date: 2021-08-18 10:29:40 +tags: exception +--- + +### 最近开发qq机器人插件时想通过playwright自动github的页面 + +#### 1. 安装go-playwright + +```go +go get github.com/mxschmitt/playwright-go +``` + +#### 2.安装chromium + +```go +playwright install chromium +``` + +### 3.自动化操作浏览器 + +```go +func GetPWScreen(url string) ([]byte, error) { + + pw, err := playwright.Run() + if err != nil { + log.Errorf("could not start playwright: %v", err) + return nil, err + } + browser, err := pw.Chromium.Launch() + if err != nil { + log.Errorf("could not launch browser: %v", err) + return nil, err + } + + page, err := browser.NewPage() + + defer func() { + page.Close() + pw.Stop() + }() + if err != nil { + log.Errorf("could not create page: %v", err) + return nil, err + } + if _, err = page.Goto(url); err != nil { + log.Fatalf("could not goto: %v", err) + } + data, err := page.Screenshot(playwright.PageScreenshotOptions{ + FullPage: playwright.Bool(true), + }) + return data, err + +} +``` + +#### 4.遇到问题 + +在windows上一切都能运行 + +在linux(ubuntu 20.4)上遇到问题提示 + +```go +could not send message: could not send message to server: Protocol error (Browser.getVersion): Browser closed. +==================== Browser output: ==================== + /root/.cache/ms-playwright/chromium-857950/chrome-linux/chrome --disable-background-networking --enable-features=NetworkService,NetworkServiceInProcess --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-background-pages --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=TranslateUI,BlinkGenPropertyTrees,ImprovedCookieControls,SameSiteByDefaultCookies,LazyFrameLoading --disable-hang-monitor --disable-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --disable-sync --force-color-profile=srgb --metrics-recording-only --no-first-run --enable-automation --password-store=basic --use-mock-keychain --user-data-dir=/tmp/playwright_chromiumdev_profile-fc8kRc --remote-debugging-pipe --headless --hide-scrollbars --mute-audio --blink-settings=primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4 --no-sandbox --no-startup-window + pid=250754 +[pid=250754][err] /root/.cache/ms-playwright/chromium-857950/chrome-linux/chrome: error while loading shared libraries: libatk-1.0.so.0: cannot open shared object file: No such file or directory +=========================== logs =========================== + /root/.cache/ms-playwright/chromium-857950/chrome-linux/chrome --disable-background-networking --enable-features=NetworkService,NetworkServiceInProcess --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-background-pages --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=TranslateUI,BlinkGenPropertyTrees,ImprovedCookieControls,SameSiteByDefaultCookies,LazyFrameLoading --disable-hang-monitor --disable-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --disable-sync --force-color-profile=srgb --metrics-recording-only --no-first-run --enable-automation --password-store=basic --use-mock-keychain --user-data-dir=/tmp/playwright_chromiumdev_profile-fc8kRc --remote-debugging-pipe --headless --hide-scrollbars --mute-audio --blink-settings=primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4 --no-sandbox --no-startup-window + pid=250754 +[pid=250754][err] /root/.cache/ms-playwright/chromium-857950/chrome-linux/chrome: error while loading shared libraries: libatk-1.0.so.0: cannot open shared object file: No such file or directory +============================================================ +Note: use DEBUG=pw:api environment variable to capture Playwright logs. +``` + +找到重要语句 + +``` +error while loading shared libraries: libatk-1.0.so.0: cannot open shared object file: No such file or directory +``` + +通过查询google + +解决办法为: + +``` +sudo apt install libcups2 libnss3-dev librust-atk-sys-dev libatk-bridge2.0-dev librust-gtk-sys-dev +``` + +安装对应依赖即可解决 diff --git a/异常处理/解决android运行-Failed-to-find-Build-Tools-revision-30.0.2的问题.md b/异常处理/解决android运行-Failed-to-find-Build-Tools-revision-30.0.2的问题.md new file mode 100644 index 0000000..a022629 --- /dev/null +++ b/异常处理/解决android运行-Failed-to-find-Build-Tools-revision-30.0.2的问题.md @@ -0,0 +1,37 @@ +--- +title: 解决android运行'Failed to find Build Tools revision 30.0.2'的问题.md +tags: + - android + - exception +categories: exception +date: 2021-10-29 20:31:52 + +--- + +## 在某次运行的时候突然出现了该报错 + +``` +Failed to find Build Tools revision 30.0.2 +``` + +## 解决方法 + + + ++ ## 首先打开sdk manager + + + + ![](https://gitee.com/aabbccddeeeff/img/raw/master//img/202110292058476.png) + ++ ## 点击sdk tools,查看第一个Android sdk build tools是否下载,如果没有下载则选中进行下载,若下载了则记住版本号 +![](https://gitee.com/aabbccddeeeff/img/raw/master//img/202110292101282.png) + ++ ## 右键单击项目根目录,选择Open module setting +![](https://gitee.com/aabbccddeeeff/img/raw/master//img/202110292054075.png) + ++ ## 在如图地方,填上上面的版本号 +![](https://gitee.com/aabbccddeeeff/img/raw/master//img/202110292104361.png) + +## 问题解决 + diff --git a/数据结构/使用go实现链栈.md b/数据结构/使用go实现链栈.md new file mode 100644 index 0000000..6cc3462 --- /dev/null +++ b/数据结构/使用go实现链栈.md @@ -0,0 +1,124 @@ +--- +title: 使用go实现链栈 +tags: go,数据结构,链栈 +categories: 数据结构 +date: 2022-01-09 10:28:29 +--- + +# 栈的定义 + +栈是一种先进后出的数据结构,日常使用较为广泛,可以将其比喻成一个瓶子,先放进去的东西掉在了最下面,所以后放出来,栈一般只提供了两种操作方式,分别为入栈和出栈。栈分为链栈和顺序栈,顺序栈使用数组存储数据,链栈采用单链表村粗数据,我们今天是实现的链栈。 + +## 结构定义 + +```go +type Stack struct { + data interface{} + next *Stack + size int + sync.Mutex +} +``` +data对应存储数据,next是一个实例指针,指向下面的一个元素,size用来存储栈的大小,其实可有可无,sync.Mutex是解决并发操作的问题。 + +## 初始化栈 + +```go +func newStack() *Stack { + s := new(Stack) + s.next = nil + s.size = 0 + return s +} +``` +首先创建一个Stack对象,将其next指针域置为nil,size置为0. + +# 栈的相关操作 + +## 入栈 + +```go +func (s *Stack) push(element interface{}) { + s2 := new(Stack) + s2.next = s.next + s2.data = element + s.Lock() + s.size ++ + s.next = s2 + s.Unlock() +} +``` + +入栈首先创建一个新的节点,然后将节点的指针指向头节点的指向,然后为该节点赋值,然后操纵头节点之前应该上锁,将头节点的size加一,同时头节点的指针指向新创建的节点,最后取消锁。 + +## 出栈 + +```go +func (s *Stack) pop() interface{} { + if s.next == nil { + return nil + } + node := s.next + s.Lock() + s.size -- + s.next = node.next + s.Unlock() + return node.data +} +``` +出栈首先判断头节点的指针是否为nil,若为nil则代表栈内没有元素,返回nil值,然后将栈顶节点取出来,然后将头节点的size减一,同时将头节点的指针域指向栈顶节点的下一节点,最后返回栈顶节点的值。 + + +## 获取栈顶节点的值 + + +```go +func (s *Stack) getTop() interface{} { + if s.next == nil { + return nil + } + return s.next.data +} +``` +和出栈不一样的地方是,该方法只获取栈顶节点的值,不会删除栈顶节点。 + +## 全部出栈转为数组 + +```go +func (s *Stack) toArray() []interface{} { + array:= make([]interface{}, s.size) + length := s.size + for i := 0; i < length; i++ { + array[i] = s.pop() + } + return array +} +``` +其中要将s.size单独取出来,因为在循环过程中出栈,s.size的值会发生改变。 + +# 测试 +```go +func main() { + stack := newStack() + stack.push(1) + stack.push(2) + stack.push(3) + stack.push(1) + stack.push(4) + + fmt.Println(stack.pop()) + fmt.Println(stack.pop()) + fmt.Println("栈的剩余元素个数:",stack.size) + + fmt.Println(stack.toArray()) +} + + +// 4 +// 1 +// 栈的剩余元素个数: 3 +// [3 2 1] + +``` + +## 个人博客地址: \ No newline at end of file diff --git a/数据结构/数据结构-c语言-顺序表.md b/数据结构/数据结构-c语言-顺序表.md new file mode 100644 index 0000000..d438de3 --- /dev/null +++ b/数据结构/数据结构-c语言-顺序表.md @@ -0,0 +1,202 @@ +--- +title: 数据结构-c语言-顺序表 +tags: 数据结构,C语言 +categories: 数据结构,C语言 +date: 2022-08-13 17:50:56 +--- + +# 什么是顺序表 ++ 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。 + ++ 顺序表:可动态增长的数组,要求数据是连续存储的 + ++ 与链表的区别:同样是存储想同类型数据的线性表,但是链表两个节点之间使用指针进行链接,相比来说占用更大的空间,但是对于增加和删除节点时相比比顺序表更快。 + +# 静态顺序表和动态顺序表 + +- 静态顺序表:静态顺序表通过一个提前分配好了的数组进行存储数据,优点是不能很好评估分配的大小 + +- 动态顺序表:动态顺序表是通过动态分配数组的大小进行存储数据,优点是可以动态分配大小,缺点是在扩容过程中可能比较复杂 + +# 动态顺序表 + +## 顺序表的数据结构定义 +```c +typedef struct OrderLists { + int *datas; + int maxsize; + int size; + +}OrderList; +``` + +size代表当前顺序表已经存储的数据量,maxsize表示当前顺序表所能存储的最大数据,当size=maxsize时会进行扩容操作,datas是用来存储数据的动态数组,数组大小动态分配。 + +## 顺序表的初始化 +```c + +// 初始化顺序表 +void initOrderList(OrderList* list,int maxsize) { + list->datas = (int*)malloc(maxsize * sizeof(int)); + list->maxsize = maxsize; + list->size = 0; +} +``` + +在初始化时定义一个预先分配的最大值,通过malloc函数进行分配内存,maxsize相应进行赋值,size分配为0 + +## 顺序表扩容 +```c +/// +/// 顺序表扩容,扩容为原来内容的两倍 +/// +/// +void expansion(OrderList *list) { + // 申请两倍当前maxsize的内存 + int* new_data = malloc(2 * list->maxsize * sizeof(int)); + if (!new_data) + { + printf("申请内存失败\n"); + return; + } + // 转移数据 + for (int i = 0; i < list->size; i++) + { + new_data[i] = list->datas[i]; + } + // 释放之前分配的内存 + free(list->datas); + list->datas = new_data; + list->maxsize = 2 * list->maxsize; + printf("顺序表已扩容,扩容后大小 ==》 %d\n", list->maxsize); +} +``` +首先分配一个与当前maxsize两倍空间的一个数组,然后将之前数组中的值转移到新数组中来,然后释放之前的数组,同时将顺序表的指针指向新的数组 + + +## 顺序表的插入 +```c +/// +/// 顺序表插入数据 +/// +/// +/// +/// +/// +int OrderListInsert(OrderList *list,int data,int position) { + if (list->size == list->maxsize) { + expansion(list); + } + if (position == -1) { + list->datas[list->size] = data; + list->size++; + return 0; + } + for (int i = list->size - 1; i >= position; i--) + { + list->datas[i + 1] = list->datas[i]; + } + list->datas[position] = data; + list->size++; + return 0; +} +``` +首先在插入之前判断当前是否需要扩容,然后判断position为-1时我们直接将数据插入的结尾,退出函数,如果数据需要插入到中间,那么我们将数组从后向前遍历,直到需要插入的位置,然后将数据依次向后移动一个位置,然后插入数据。 + +## 顺序表的删除 +```c +int OrderDelete(OrderList* list,int position) { + if (position > list->size - 1) { + return 0; + } + for (int i = position; i <= list->size-2; i++) + { + list->datas[i] = list->datas[i+1]; + } + list->size--; +} +``` +顺序表的删除和插入同理,当然遍历方式从删除的位置向末尾遍历,直到结尾的前一个元素,然后依次把数据向前移动一位,就覆盖掉了需要删除的内容。 + +## 顺序表的修改 +```c +/// +/// 更新某个位置的数据 +/// +/// +/// +/// +/// +int OrderUpdate(OrderList* list, int data, int position) { + if (position > list->size-1) { + return 0; + } + list->datas[position] = data; + return 1; +} +``` +顺序表的修改可以说没得难度,直接修改数组内内容就可以了 + +## 顺序表的查询 +```c +/// +/// 获取某个位置的数据 +/// +/// +/// +/// +int OderGetData(OrderList* list, int position) { + if (position > list->size - 1) { + return -1; + } + else + { + return list->datas[position]; + } +} +``` +顺序表的查询直接返回对于位置的内容即可 + +## 顺序表的遍历 +``` +/// +/// 遍历顺序表 +/// +/// +void OderForEach(OrderList* list) { + for (int i = 0; i < list->size; i++) + { + printf("%d ", list->datas[i]); + } +} +``` + +## 测试 +```c +int main() { + OrderList list; + initOrderList(&list,10); + for (int i = 0; i < 40; i++) + { + OrderListInsert(&list, i+1, i); + } + OderForEach(&list); + for (int i = 0; i < 20; i++) + { + OrderDelete(&list, 0); + } + printf("\n删除前20个元素\n"); + OderForEach(&list); + return 0; +} +/* +顺序表已扩容,扩容后大小 ==》 20 +顺序表已扩容,扩容后大小 ==》 40 +1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 +删除前20个元素 +21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 +*/ +``` + +# 引申 +顺序表可以引申出顺序队,顺序栈,只需要编写特定的方法即可 \ No newline at end of file diff --git a/数据结构/用go实现二叉树.md b/数据结构/用go实现二叉树.md new file mode 100644 index 0000000..ab3753d --- /dev/null +++ b/数据结构/用go实现二叉树.md @@ -0,0 +1,406 @@ +--- +title: 用go实现二叉树 +tags: 数据结构 +categories: 数据结构, 二叉树 +date: 2022-05-08 20:01:00 +--- + +# 用go实现二叉树 + +> 二叉查找树(Binary Search Tree,简称BST)是一棵二叉树,它的左子节点的值比父节点的值要小, +右节点的值要比父节点的值大。它的高度决定了它的查找效率。在理想的情况下,二叉查找树增删查改的时间复杂度为O(logN) +(其中N为节点数),最坏的情况下为O(N)。当它的高度为logN+1时,我们就说二叉查找树是平衡的。 + +## 二叉树的构成 + +因为二叉树的节点需要可比较,所以定义了一个接口,包含了比较大小的方法和是否相等的方法 + +```go + +// 实现该接口则代表可比较 +type compare interface { + // 如果大于返回true,否则返回false + compare(data interface{}) bool + // 判断是否相等 + equals(data interface{}) bool +} + +// 二叉树节点 +type BST struct { + // 节点值 + data compare + // 左节点 + LeftNode *BST + // 右节点 + RightNode *BST +} + +// 节点值得结构体,实现了compare接口 +type data struct { + key int + value interface{} +} + +func (d *data) equals(d1 interface{}) bool { + if d.key == d1.(*data).key { + return true + } + return false +} + +func (d *data) compare(d1 interface{}) bool { + if d.key > d1.(*data).key { + return true + } + return false +} +``` + +## 二叉树的插入 + +二叉树的插入非常简单,先判断根节点是否为空,为空则直接作为根节点, +若不为空则找到需要插入位置的父节点,从根节点开始比较,小于则向左子节点靠近, +大于则靠近右子节点,找到插入位置的父节点后,再进行比较插入就行。 + +```go +// insert 插入 +func (b *BST) insert(content compare) { + var parent *BST + temp := b + // 先判断根节点是否为空 + if temp.data == nil { + temp.data = content + return + } + // 循环遍历找到父节点 + for true { + // 当节点为null时, + if temp == nil { + break + } + parent = temp + // 如果当前节点值小于插入的值,则跳向左子节点 + if temp.data.compare(content) { + temp = temp.LeftNode + } else { + temp = temp.RightNode + } + } + // 比较值,进行插入赋值 + if parent.data.compare(content) { + parent.LeftNode = &BST{ + data: content, + LeftNode: nil, + RightNode: nil, + } + } else { + parent.RightNode = &BST{ + data: content, + LeftNode: nil, + RightNode: nil, + } + } +} + +func main() { + bst := NewBST() + bst.insert(&data{ + key: 3, + value: "123", + }) + + bst.insert(&data{ + key: 2, + value: "234", + }) + bst.insert(&data{ + key: 1, + value: "345", + }) + bst.insert(&data{ + key: 4, + value: "456", + }) + fmt.Println(bst) +} +``` +通过goland断点调试查看插入结果![](https://s2.loli.net/2022/05/08/byvcQHTRGjgAEJ5.png) + +## 二叉树节点查询 + +二叉树查询只需要依次比较大小进行判断就能找到需要的值 +```go +// 查询 +func (b *BST) query(node compare) compare { + temp := b + for true { + // 判断节点是否为空 + if temp == nil { + return nil + } + // 判断节点的值是否匹配 + if temp.data.equals(node) { + return temp.data + } + // 根据节点值得大小移动当前位置 + if temp.data.compare(node) { + temp = temp.LeftNode + } else { + temp = temp.RightNode + } + } + return nil +} + +func main() { + bst := NewBST() + bst.insert(&data{ + key: 3, + value: "123", + }) + + bst.insert(&data{ + key: 2, + value: "234", + }) + bst.insert(&data{ + key: 1, + value: "345", + }) + bst.insert(&data{ + key: 4, + value: "456", + }) + // &{4 456} + fmt.Println(bst.query(&data{key: 4})) +} +``` + +## 二叉树更新 +更新二叉树节点得值和插入类似,依次遍历找到节点更新即可 + +```go +func (b *BST) update(node compare) error { + temp := b + for true { + if temp == nil { + return errors.New("not found") + } + if temp.data.equals(node) { + temp.data = node + return nil + } + + if temp.data.compare(node) { + temp = temp.LeftNode + } else { + temp = temp.RightNode + } + } + return nil +} +``` + +## 节点遍历 +节点遍历分为三种方式,分别为前序遍历,中序遍历,后续遍历 + ++ ### 前序遍历 + 前序遍历访问顺序为当前节点,左节点,右节点 + 我们通过递归的方式进行遍历 + ![](https://s2.loli.net/2022/05/08/F3Sds8P9KWaRoIp.png) + 该二叉树我们通过前序遍历的顺序为**3 2 4 7 6 9** + +```go + // 前序遍历 +func (b *BST) frontItem(slice []compare) []compare { + if b.data == nil { + return nil + } + slice = append(slice, b.data) + if b.LeftNode != nil { + slice = b.LeftNode.frontItem(slice) + } + if b.RightNode != nil { + slice = b.RightNode.frontItem(slice) + } + return slice +} +``` + ++ ### 中序遍历 + 中序遍历访问顺序为当左子节点,当前节点,右子节点 + 前面的二叉树我们通过中序遍历的顺序为**2 3 4 7 6 9** +```go +// 中序遍历 +func (b *BST) midItem(slice []compare) []compare { + if b.data == nil { + return nil + } + if b.LeftNode != nil { + slice = b.LeftNode.frontItem(slice) + } + slice = append(slice, b.data) + if b.RightNode != nil { + slice = b.RightNode.frontItem(slice) + } + return slice +} +``` + ++ ### 后序遍历 + 后序遍历访问顺序为左节点,右节点,当前节点 + 前面二叉树我们通过后续遍历访问顺序为**2 6 9 7 4 3** + +```go +// 后序遍历 +func (b *BST) rearItem(slice []compare) []compare { + if b.data == nil { + return nil + } + if b.LeftNode != nil { + slice = b.LeftNode.rearItem(slice) + } + if b.RightNode != nil { + slice = b.RightNode.rearItem(slice) + } + slice = append(slice, b.data) + return slice +} +``` + +## 二叉树删除 + +二叉树得删除分为三种情况 + ++ 节点左右子节点都为空 + + 该情况只需要将该节点从父节点的指向删除即可 + + ++ 节点的子节点有一个为空 + + 该情况需要将该节点的唯一子节点根据大小连接向该节点的父节点 + 从该二叉树中删除节点6,只需要将节点7的左子节点指向5即可 + ![](https://s2.loli.net/2022/05/08/YrqLx5f6Gk2KeRH.png) + + ++ 节点的左右子节点都存在 + + 该情况需要进行节点交换,需要先获取到当前节点的右子节点的中序遍历的第一个节点,然后交换两个节点的值,如果该右子节点的前序遍历 + 首节点存在右子节点,则继续获取并交换,知道将该节点交换到叶子节点。 + + ![](https://s2.loli.net/2022/05/08/25dGitWVH7Zh38x.png) + + 若将该二叉树的节点7删除,先需要获取右子节点的中序遍历守节点,因为节点9不存在左子节点,所以该节点就为9,我们就将节点9与节点7 + 进行交换,因为仍不为叶子节点,所以继续获取,将节点11与节点7交换,最终交换结果为: + ![](https://s2.loli.net/2022/05/08/m72bcweH8vX4z5h.png) + 我们直接将该叶子节点删除即可 + +```go +// 删除节点 +func (b *BST) delete(node compare) error { + temp := b + if temp == nil { + return errors.New("root") + } + isRoot := false + var parent *BST + // 获取要删除节点的位置和父节点位置 + for true { + // 判断节点是否为根节点 + if b.data.equals(node) { + isRoot = true + break + } + // 判断节点的值的大小 + if temp.data.compare(node) { + if temp.LeftNode == nil { + return errors.New("not found") + } + if temp.LeftNode.data.equals(node) { + parent = temp + temp = temp.LeftNode + break + } else { + temp = temp.LeftNode + } + } else { + if temp.RightNode == nil { + return errors.New("not found") + } + if temp.RightNode.data.equals(node) { + parent = temp + temp = temp.RightNode + break + } else { + temp = temp.RightNode + } + } + } + + // case1: 删除节点即无左节点也无右节点 + if temp.LeftNode == nil && temp.RightNode == nil { + if parent.data.compare(node) { + parent.LeftNode = nil + } else { + parent.RightNode = nil + } + return nil + + // case2: 删除节点有左节点无右节点 + } else if temp.LeftNode != nil && temp.RightNode == nil { + + if isRoot { + b.data = temp.LeftNode.data + b.LeftNode = temp.LeftNode.LeftNode + b.RightNode = temp.LeftNode.RightNode + return nil + } + + if parent.LeftNode == temp { + parent.LeftNode = temp.LeftNode + } else { + parent.RightNode = temp.LeftNode + } + return nil + + // case3: 删除节点有右节点无左节点 + } else if temp.LeftNode == nil && temp.RightNode != nil { + if isRoot { + b.data = temp.RightNode.data + b.LeftNode = temp.RightNode.LeftNode + b.RightNode = temp.RightNode.RightNode + return nil + } + if parent.LeftNode == temp { + parent.LeftNode = temp.RightNode + } else { + parent.RightNode = temp.RightNode + } + return nil + + // case3: 删除节点有右节点有左节点 + } else { + for temp.RightNode != nil { + firstMidNode := temp.RightNode.firstMidNode() + firstMidNode.data, temp.data = temp.data, firstMidNode.data + temp = firstMidNode + } + return b.delete(node) + } +} + +// 获取中序遍历第一个节点 +func (b *BST) firstMidNode() *BST { + temp := b + for true { + if temp.LeftNode == nil { + return temp + } + temp = temp.LeftNode + } + return nil +} +``` + +## 个人博客 https://shhy.xyz \ No newline at end of file diff --git a/数据结构/用go实现队列.md b/数据结构/用go实现队列.md new file mode 100644 index 0000000..e617cf6 --- /dev/null +++ b/数据结构/用go实现队列.md @@ -0,0 +1,132 @@ +--- +title: 用go实现队列 +tags: 数据结构 +categories: 数据结构 +date: 2022-01-09 16:13:09 +--- + +# 队列的定义 +队列,和栈一样,也是一种对数据的"存"和"取"有严格要求的线性存储结构,与栈结构不同的是,队列的两端都"开口",要求数据只能从一端进,从另一端出。队列有两种存储方式,分别是顺序队和链队,今天实现的是链队。 + +# 结构的定义 +队列需要两个指针定位对头和队尾的位置,所以定义方式如下所示。 +```go +// 代表每一个节点 +type node struct { + data interface{} + next *node +} + +type queue struct { + // 头节点 + head *node + + // 队尾节点 + rear *node + + size int + + sync.Mutex +} +``` + +# 队列的相关操作 + +## 初始化队列 + +```go +func newQueue() *queue { + q := new(queue) + q.head = nil + q.rear = nil + q.size = 0 + return q +} +``` +创建一个心得队列对象,同时将头指针和尾指针全部置为nil + +## 入队 + +```go +// Put 尾插法 +func (q *queue) Put(element interface{}) { + n := new(node) + n.data = element + q.Lock() + defer q.Unlock() + + if q.rear == nil { + q.head = n + q.rear = n + }else { + q.rear.next = n + q.rear = n + } + q.size ++ +} + +// PutHead 头插法,在队列头部插入一个元素 +func (q *queue) PutHead(element interface{}) { + n := new(node) + n.data = element + q.Lock() + defer q.Unlock() + if q.head == nil { + q.head = n + q.rear = n + }else { + n.next = q.head + q.head = n + } + q.size ++ +} +``` +入队分为两种方式,分别是头插法和尾插法,默认采用尾插法。 + +尾插法入队,首先创建一个节点对象,判断队列此时是否为空,若为空则将头指针和尾指针都指向该节点,否则将尾指针的节点的指针域指向新创建的节点,然后将队列的尾指针指向该节点。 + +## 出队 +```go +// Get 获取并删除队列头部的元素 +func (q *queue) Get() interface{} { + if q.head == nil { + return nil + } + n := q.head + q.Lock() + defer q.Unlock() + // 代表队列中仅一个元素 + if n.next == nil { + q.head = nil + q.rear = nil + + }else { + q.head = n.next + } + q.size-- + return n.data +} +``` + +出队默认从对头取出元素,首先判断队列是否存在元素,若不存在则返回nil,若存在,则将头节点赋值,然后判断队列中是否只存在一个元素,若只存在一个元素则将头尾节点都置为nil,否则将头节点置为该节点的下一个节点。 + +## 测试 +```go + +func main() { + q := newQueue() + q.Put(1) + q.Put(2) + fmt.Println(q.Get()) + fmt.Println(q.Get()) + fmt.Println("队列中剩余元素个数",q.size) +} + +// 1 +// 2 +// 队列中剩余元素个数 0 + + +``` + +## 个人博客 \ No newline at end of file diff --git a/模拟登录教务系统.md b/模拟登录教务系统.md new file mode 100644 index 0000000..cdf4ceb --- /dev/null +++ b/模拟登录教务系统.md @@ -0,0 +1,259 @@ +--- +title: 模拟登录教务系统 +date: 2021-08-12 17:27:41 +tags: python +--- + ++++ + + +# 1.分析协议 + +## 1.1. 使用fiddler进行抓包 + +首先尝试登录,登录网页为然后通过fiddler抓包获取传递的参数。 + +![fiddler](https://github.com/3343780376/myBlog/blob/master/resources/static/image/scnucas/1.png?raw=true) + +发现其中有两个参数乱码,通过fiddler的syntaview选项可以找到内容 + +``` +typeName=%D1%A7%C9%FA +txt_psasas=%C7%EB%CA%E4%C8%EB%C3%DC%C2%EB +``` + +通过GBK解码得知,typeName的值为‘学生’, txtpsasas的值为‘请输入密码’,可得知这两个参数并不是我们需要的, +此时就只能通过分析‘dsdsdsdsdxcxdfgfg’和‘fgfggfdgtyuuyyuuckjg’参数了。可猜测这两个参数是加密后的验 证码和密码。然后通过正常解密手段尝试不通,就只能阅读javascript源码了。 + +## 1.2. 使用浏览器自带的网络分析工具 + +通过浏览器的检查工具发现,登录区域是一个iframe页面,链接为, 我们讲该网页下载到本地后进行阅读。 + +```javascript + + + + + +``` + +通过上面这段html发现关键函数‘shtitcus(this)’,然后通过notepad++的搜索功能找到了该函数 + +```javascript +function shtitcus(obj) { + if (obj.id == "txt_sdertfgsadscxcadsads") { + if (obj.value == "请输入验证码") { + obj.value = ""; + obj.style.color = "black"; + } + } + if (obj.id == "txt_psasas") { + if (obj.value == "请输入密码") { + obj.style.display = "none"; + document.getElementById("txt_pewerwedsdfsdff").style.display = ""; + document.getElementById("txt_pewerwedsdfsdff").focus(); + } + } + if (obj.id == "txt_asmcdefsddsd") { + if (obj.value == "请输入帐号") { + obj.value = ""; + obj.style.color = "black"; + } + } +} +``` + +也就是该函数判断当id为txt_psasas的input输入框获得焦点时触发,然后通过设置display的值来隐藏该输入框,并且使id为txt_pewerwedsdfsdff的 +input输入框获得焦点。所以当我们正常通过检查得到的input是错误的输入框。然后我们发现真实输入框还有一个事件触发函数chkpwd函数。尝试搜索到该函数。 + +```javascript +function chkpwd(obj) { + if (obj.value != '') { + var s = md5(document.all.txt_asmcdefsddsd.value + md5(obj.value).substring(0, 30).toUpperCase() + '13671').substring(0, 30).toUpperCase(); + document.all.dsdsdsdsdxcxdfgfg.value = s; + } else { + document.all.dsdsdsdsdxcxdfgfg.value = obj.value; + } + chkLxstr(obj.value); +} +``` + +然后我在这里就找到了上文提到的dsdsdsdsdxcxdfgfg的值了,该值就是通过加密之后的密码。然后在一起还找到了chkyzm函数。 + +```javascript +function chkyzm(obj) { + if (obj.value != '') { + var s = md5(md5(obj.value.toUpperCase()).substring(0, 30).toUpperCase() + '13671').substring(0, 30).toUpperCase(); + document.all.fgfggfdgtyuuyyuuckjg.value = s; + } else { + document.all.fgfggfdgtyuuyyuuckjg.value = obj.value.toUpperCase(); + } +} +``` + +所以说fgfggfdgtyuuyyuuckjg就是验证码加密后的值。至此我们基本已经有了头绪,这时候就指向了md5这个加密函数了。 +通过浏览器的网络分析找到md5文件里面的md5函数。然后一部一部分析发现如果需要重构这个加密函数,那不过又是大废周折。 +正好,最近学了node.js的一点基础。不妨直接使用node.js来调用这个加密函数。然后通过api的形式来调用。 + +# 2.模拟登录 + +## 2.1. 使用node.js编写加密api + +在这里使用了node.js的express框架。代码如下: + +```javascript +//将md5.js下载到同级目录, +//使用npm安装express和bodu-parser npm install express , npm install body-parser + + +var md5 = require("./md5"); +var express = require("express"); +var path = require("path") + +var app = express(); +var bodyParser = require('body-parser'); +app.use(bodyParser.urlencoded({ + extended:true +})); + +function chkpwd(account, pass) { + if (pass !== '') { + return md5(account + md5(pass).substring(0, 30).toUpperCase() + '13671').substring(0, 30).toUpperCase(); + } else { + return ""; + } +} + +function chkyzm(value) { + if (value !== '') { + return md5(md5(value.toUpperCase()).substring(0, 30).toUpperCase() + '13671').substring(0, 30).toUpperCase(); + } else { + return "" + } +} + +app.post("/login",function (req,res) { + var account = req.body.account; + console.log(account) + var password = req.body.password; + console.log(account+password) + var data = chkpwd(account,password); + res.send(data) +}) + +app.post("/yzm",function (req,res) { + var value = req.body.value; + var yzm = chkyzm(value); + res.send(yzm) +}) + +app.listen(10001,"127.0.0.1") +``` + +当我们此时使用post方法访问,然后加上需要加密账号和密码,就可以返回 +加密后的内容了。同理可以对验证码进行加密。 + +## 2.2. 使用python模拟登录 + +首先创建类和构造方法 + +```python +class Education: + + def __init__(self, account: str, password: str): + self.account = account + self.password = password + self.client = httpx.Client() + self.source = "http://jwc.scnucas.com" +``` + +然后在类里面编写两个加密api调用的函数 + +```python + //加密账户和密码 + def __md5Account(self) -> str: + data = { + "account": self.account, + "password": self.password + } + return httpx.post("http://47.110.228.1:10001/login", data=data).text + + //加密验证码 + def __md5_verification(self): + content = input("请输入验证码:") + data = { + "value": content + } + result = httpx.post("http://47.110.228.1:10001/yzm", data=data) + + return result +``` + +然后编写发送请求的工具方法,因为教务系统偶尔会返回503服务器繁忙,所以我们对此进行判断并且重新进行请求, + +```python + def __ask(self, url: str, method: str, data=None) -> httpx.Response: //带上常用请求头 headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'jwc.scnucas.com', 'Referer': 'http://jwc.scnucas.com/_data/login_home.aspx', 'User-Agent': 'Mozilla / 5.0(Windows NT 10.0;Win64;x64) AppleWebKit / 537.36(KHTML, likeGecko) Chrome / 88.0.4324.96Safari / 537.36Edg / 88.0.705.50' } if data is None: data = {} response = self.client.request(method=method, url=url, headers=headers, data=data, allow_redirects=False,) code = response.status_code while code >= 500: response = self.client.request(method=method, url=url, data=data, allow_redirects=False) code = response.status_code response.encoding = "GBK" return response +``` + +最后就是调用该方法进行登录 + +```python + def login(self): + //这是模拟使用get方法获取登录页面 + self.__ask(self.source + "/_data/login_home.aspx", "GET", data=None) + //该接口是获得验证码的接口 + a = self.__ask(self.source + "/sys/ValidateCode.aspx", "GET").content + with open("code.png", "wb+") as f: + f.write(a) + //使用PIL库的Image方法打开验证码 + Image.open("code.png").show() + data = { + # "__VIEWSTATE": view, + "pcInfo": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.50undefined5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.50 " + "SN:NULL", + "txt_mm_expression": "", + "txt_mm_length": "", + "txt_mm_userzh": "", + "typeName": "学生", + "dsdsdsdsdxcxdfgfg": self.__md5Account(), + "fgfggfdgtyuuyyuuckjg": self.__md5_verification().text, + + "Sel_Type": "STU", + "txt_asmcdefsddsd": "19104978", + "txt_pewerwedsdfsdff": "", + "txt_psasas": "请输入密码", + "txt_sdertfgsadscxcadsads": "" + } + + result = self.__ask(self.source + "/_data/login_home.aspx", "POST", data=data).text +``` + +通过发现当登录成功的时候,就会出现‘window.top.document.location.replace("../MAINFRM.aspx")’语句进行页面跳转。 +所以我们只需要判断返回的结果有没有该语句就可以确定是否登录成功。修改上面代码为: + +```python +result = self.__ask(self.source + "/_data/login_home.aspx", "POST", data=data).text.find( + 'window.top.document.location.replace("../MAINFRM.aspx")') + if result != -1: + print(f"登录成功\n{self.account}") + else: + print("登录失败") + exit(3) +``` + +然后尝试调用该类 + +```python +if __name__ == '__main__': + account = input("请输入你的教务系统账号:") + password = input("请输入你的教务系统密码:") + e = Education(account=account,password=password) + e.login() +``` diff --git a/毕业论文/毕业论文-2.md b/毕业论文/毕业论文-2.md new file mode 100644 index 0000000..079abc2 --- /dev/null +++ b/毕业论文/毕业论文-2.md @@ -0,0 +1,535 @@ +--- +title: 毕业设计(2) +tags: + - java + - ssm + - react + - ant design + - typescript +categories: 毕业设计 +date: 2022-12-07 12:36:32 +summary: 数据库配置何整合ssm框架 + +--- + +## 四、数据库创建 + +```sql +use workingsystem; +-- #行政班级表 +create table adminClass +( + id int primary key not null auto_increment comment '行政班级id', + name varchar(255) not null comment '班级名称', + teacher_id int not null default 0 comment '所属教师id' +); + +-- #教学班级表 +create table teacherClass +( + id int primary key not null auto_increment comment '行政班级id', + name varchar(255) not null comment '班级名称', + teacher_id int not null default 0 comment '所属教师id' +); + +-- #学生表 +create table student +( + id int primary key not null auto_increment comment '学生id', + name varchar(255) not null default '' comment '学生姓名', + card_id long not null comment '学生卡id', + phone varchar(15) not null default 0 comment '手机号', + password varchar(255) not null comment '账号密码', + admin_class_id int default 0 not null comment '所属行政班级id', + teacher_class_id int default 0 not null comment '所属教学班级id' +); + +-- #教师表 +create table teacher +( + id int primary key not null auto_increment comment '教师id', + card_id long not null comment '教师工号', + name varchar(255) not null default '' comment '教师姓名', + phone varchar(15) not null default 0 comment '手机号', + password varchar(255) not null comment '账号密码' +); + + +-- #家长表 +create table patriarch +( + id int primary key not null auto_increment comment '家长id', + name varchar(255) not null default '' comment '家长姓名', + phone varchar(15) not null comment '手机号', + password varchar(255) not null comment '账号密码', + student_id int not null comment '对应绑定的学生id' +); + +create table work_record +( + id int primary key not null auto_increment comment '作业记录id', + work_id int not null default 0 comment '作业id', + student_id int not null default 0 comment '提交的学生id', + submit_time datetime not null comment '提交时间', + end_time datetime comment '截至时间', + appendix varchar(255) comment '附件地址', + teacher_id int default 0 not null comment '创建的教师的id' +); + +create table work +( + id int primary key not null auto_increment comment '作业id', + name varchar(255) not null comment '作业名称', + description longtext comment '作业描述', + create_time datetime comment '创建时间', + end_time datetime comment '截至时间', + appendix varchar(255) comment '附件地址', + teacher_id int comment '创建教师的id', + admin_class_id int default 0 comment '行政班级id', + teacher_class_id int default 0 comment '教学班级的id' +); +``` + +## 五、SSM框架整合 + +### 1. 项目创建 + +项目才有idea进行开发,首先在idea中创建maven项目 + +![](https://s2.loli.net/2022/12/07/vBe9562q4XfJZQY.png) + +### 2. 依赖添加 + +pom.xml + +```xml + + 4.0.0 + org.gjs + WorkingSystem + war + v1.0.0 + WorkingSystem Maven Webapp + https://maven.apache.org + + + 11 + 11 + 11 + 5.3.20 + 8.0.28 + 2.0.3 + 2.19.0 + 2.14.0 + + + + junit + junit + 4.13.2 + test + + + + + + org.springframework + spring-context + ${spring-version} + + + + org.springframework + spring-core + ${spring-version} + + + + org.springframework + spring-web + ${spring-version} + + + + org.springframework + spring-beans + ${spring-version} + + + + org.springframework + spring-webmvc + ${spring-version} + + + + org.springframework.session + spring-session-core + 2.6.2 + + + + + org.springframework + spring-aop + ${spring-version} + + + + + + org.springframework + spring-jdbc + ${spring-version} + + + + + org.springframework + spring-test + ${spring-version} + + + + + + mysql + mysql-connector-java + ${mysql-version} + + + + + org.mybatis + mybatis + 3.5.9 + + + + + org.mybatis + mybatis-spring + 2.0.7 + + + + + + com.alibaba + druid + 1.2.9 + + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + + + + + + org.slf4j + slf4j-log4j12 + ${slf4j.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + org.springframework.data + spring-data-commons + 2.7.1 + + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + + + org.aspectj + aspectjweaver + 1.9.9.1 + + + + + com.auth0 + java-jwt + 4.2.1 + + + org.jetbrains + annotations-java5 + RELEASE + compile + + + + + + + + WorkingSystem + + + maven-compiler-plugin + 3.10.1 + + 11 + 11 + + + + org.apache.maven.plugins + maven-compiler-plugin + + 7 + 7 + + + + + +``` + +### 3. 配置web.xml文件 + +web.xml + +```xml + + + + Archetype Created Web Application + + + + log4jConfigLocation + classpath:log4j.properties + + + + + + + org.springframework.web.context.ContextLoaderListener + + + contextConfigLocation + classpath:spring/spring.xml + + + + + CharacterEncodingFilter + org.springframework.web.filter.CharacterEncodingFilter + + encoding + utf-8 + + + + CharacterEncodingFilter + /* + + + + mvc + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + classpath:spring/spring-mvc.xml + + + 1 + + + + mvc + / + + + + +``` + +### 4. 配置日志输出 + +log4j.properties + +```properties +log4j.rootLogger = debug, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{MM-dd HH:mm:ss} [%X{traceId}] %-5p [%t] %c{2}:%L - %m%n +``` + +### 5. spring配置文件 + +spring.xml + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### 6. spring-mvc配置 + +spring-mvc.xml + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### 7.mybatis整合 + +dao.xml + +```xml + + + + + + + + + + + + + + + + + + + + + + +``` diff --git a/毕业论文/毕业论文-3.md b/毕业论文/毕业论文-3.md new file mode 100644 index 0000000..393847e --- /dev/null +++ b/毕业论文/毕业论文-3.md @@ -0,0 +1,307 @@ +--- +title: 毕业设计(3) +tags: + - java + - ssm + - react + - ant design + - typescript +categories: 毕业设计 +date: 2022-12-10 12:38:32 +summary: 基本工具类创建 + +--- + +## 六、创建Resp返回类 + +实体类分别包含了四个字段,code、data、errot、message + +Resp.class + +```java +package org.gjs.utils; + + +public class Resp { + int code; + T data; + String error; + String message; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Resp() { + } + + public Resp(int code, T data, Exception error, String message) { + this.code = code; + this.data = data; + if (error == null){ + this.error = ""; + }else { + this.error = error.getMessage(); + } + + this.message = message; + } + + public static Resp Ok(){ + return new Resp(200,null,null,""); + } + public static Resp Ok(K data){ + return new Resp(200,data,null,""); + } + public static Resp Ok(K data,String message){ + return new Resp(200,data,null,message); + } + + + public static Resp Err(int code, Exception e){ + return new Resp(code,false,e,""); + } + + public static Resp Err(int code, Exception e,K data){ + return new Resp(code,data,e,""); + } + + public static Resp Err(int code, Exception e,K data,String message){ + return new Resp(code,data,e,message); + } + + public static Resp Err(int code,Exception e,String message){ + return new Resp(code,false,e,message); + } +} +``` + +## 七、全局错误处理 + +通过**RestControllerAdvice**注解和**RestControllerAdvice**注解进行全局异常处理 + +ExceptionController.java + +```java +package org.gjs.exception; + + +import org.apache.log4j.Logger; +import org.gjs.utils.Resp; +import org.jetbrains.annotations.NotNull; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.servlet.http.HttpServletResponse; + +@RestControllerAdvice +@Component +public class ExceptionController { + + + private final Logger logger = Logger.getLogger(this.getClass()); + + + @ExceptionHandler({RuntimeException.class}) + public Object runtime(HttpServletResponse response,RuntimeException e){ + response.setStatus(503); + e.printStackTrace(); + return Resp.Err(503,e,"服务端异常错误!"); + } + + @ExceptionHandler({NullPointerException.class}) + @ResponseBody + @ResponseStatus(code = HttpStatus.SERVICE_UNAVAILABLE) + public Object nullExc(HttpServletResponse response,Exception e){ + e.printStackTrace(); + return Resp.Err(503,e); + } + + @ExceptionHandler({Exception.class}) + @ResponseBody + @ResponseStatus(code = HttpStatus.SERVICE_UNAVAILABLE) + public Object my(HttpServletResponse response, @NotNull Exception e){ + e.printStackTrace(); + return Resp.Err(503,e); + } +} +``` + +## 八、Jwt工具类编写 + +首先在maven中导入jwt得依赖 + +```xml + + + com.auth0 + java-jwt + 4.2.1 + +``` + +工具类编写 + +Jwt.java + +```java +package org.gjs.utils; + + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.Calendar; +import java.util.Date; + +public class Jwt { + + private static final String SECRET = "XX#$%()(#*!()!KL<>?N<:{LWPW"; + + private static final String EXP = "exp"; + + private static final String PAYLOAD = "payload"; + + private static final String ISSUE = "gjs"; + + private static final Integer MAX_AGE = 60 * 60 * 24 * 30; + + //加密,传入一个对象和有效期 + public static String sign(T object) { + try { + + ObjectMapper mapper = new ObjectMapper(); + String jsonString = mapper.writeValueAsString(object); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date()); + calendar.add(Calendar.MONTH,1); + return JWT.create() + .withIssuer(ISSUE) + .withClaim(PAYLOAD,jsonString) + .withExpiresAt(calendar.getTime()) + .sign(Algorithm.HMAC256(SECRET)); + } catch(Exception e) { + return null; + } + } + + //解密,传入一个加密后的token字符串和解密后的类型 + public static T verify(String jwt, Class classT) { + try { + JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).withIssuer(ISSUE).build(); + DecodedJWT v = verifier.verify(jwt); + Date expiresAt = v.getExpiresAt(); + // 检查是否过期 + if (new Date().after(expiresAt) ){ + return null; + } + String s = v.getClaim(PAYLOAD).asString(); + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(s, classT); + } catch (JsonProcessingException e) { + return null; + } + + } +} +``` + +## 九、日志中间件实现 + +使用aop进行拦截日志记录 + +LogAop.java + +```java +package org.gjs.aop; + +import org.apache.log4j.Logger; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +@Aspect +@Component +public class LogAop { + + public LogAop() { + } + + private final Logger logger = Logger.getLogger(this.getClass()); + + @Before("execution(* org.gjs.controller.*.*(..))") + public void before(JoinPoint point){ + + Signature signature = point.getSignature(); + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + logger.info(request.getSession().getId()+" ----------------------------------------------------"); + logger.info(request.getSession().getId() +" - name: "+signature.getName()); + logger.info(request.getSession().getId()+" - ip: "+request.getRemoteAddr()); + logger.info(request.getSession().getId()+" - method: "+request.getMethod()); + logger.info(request.getSession().getId()+" - query: "+request.getQueryString()); + Object[] args = point.getArgs(); + for (Object a : args + ) { + if (a != null){ + logger.info(request.getSession().getId()+" - args: "+a.toString()); + } + + } + } + @AfterReturning(value = "execution(* org.gjs.controller.*.*(..))",returning = "methodResult") + public void afterReturning(JoinPoint joinPoint, Object methodResult){ + HttpServletResponse res = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + logger.info(request.getSession().getId()+ " - status: "+res.getStatus()); + logger.info(request.getSession().getId()+ " - return: "+methodResult); + logger.info(request.getSession().getId()+" ----------------------------------------------------"); + } +} +``` + +# diff --git a/毕业论文/毕业论文-4.md b/毕业论文/毕业论文-4.md new file mode 100644 index 0000000..19aae08 --- /dev/null +++ b/毕业论文/毕业论文-4.md @@ -0,0 +1,504 @@ +--- +title: 毕业设计(4) +tags: + - java + - ssm + - react + - ant design + - typescript +categories: 毕业设计 +date: 2022-12-14 12:40:32 +summary: 登录实现 +--- + +## 十、管理登录逻辑后端实现 + +    管理员账号的账号密码从配置文件中读写 + +```java +@RestController +@PropertySource("classpath:config/config.properties") +@RequestMapping("/admin") +public class AdminController { + + @Value("${admin.username}") + String username; + + @Value("${admin.password}") + String password; + + @PostMapping("/login") + public ResponseEntity>> login(@RequestBody Map params){ + String user = params.get("phone"); + String pass = params.get("password"); + HashMap map = new HashMap<>(); + if (user.equals(username) && pass.equals(password)){ + map.put("phone",username); + map.put("password",password); + map.put("type","admin"); + String token = Jwt.sign(map); + map.remove("password"); + map.put("token",token); + return ResponseEntity.ok(Resp.Ok(map)); + } + return ResponseEntity.status(HttpStatus.PAYMENT_REQUIRED).body(Resp.Err(403,null,map)); + } +} +``` + +从 **config.propertie** 文件中中读取管理员的账号密码,然后配置登录的controller类,首先判断账号密码是否正确,正确就调用 **Jwt** 生成登录token,返回token到前端。 + +鉴权拦截器 + +在spring mvc的配置文件中配置拦截器,配置除了登陆地址以外全部拦截 + +```xml + + + + + + + + + + +``` + +拦截器类 + +```java +@PropertySource("classpath:config/config.properties") +public class AuthInterceptor implements HandlerInterceptor { + + @Value("${admin.username}") + String username; + + @Value("${admin.password}") + String password; + + + @Resource + StudentService studentService; + + @Resource + TeacherService teacherService; + + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String authorization = request.getHeader("Authorization"); + if (authorization == null || authorization.equals("")){ + response.sendError(401,"the auth fail!"); + return false; + }else { + HashMap map = Jwt.verify(authorization.split(" ")[1], HashMap.class); + if (map==null){ + throw new MyException("未登录!"); + } + request.getSession().setAttribute("login_type",map.get("type")); + Object type = map.get("type"); + if ("admin".equals(type)) { + if (map.get("phone").equals(username) && map.get("password").equals(password)) { + return true; + } else { + throw new MyException("未登录!"); + } + } else if ("student".equals(type)) { + Student student = new Student(); + student.setPhone((String) map.get("phone")); + student.setId((Integer) map.get("id")); + Page students = studentService.queryByPage(student, PageRequest.of(0, 100)); + return students.getContent().size() >= 1; + } else if ("teacher".equals(type)) { + Teacher teacher = new Teacher(); + teacher.setPhone((String) map.get("phone")); + teacher.setId((Integer) map.get("id")); + Page teachers = teacherService.queryByPage(teacher, PageRequest.of(0, 100)); + return teachers.getContent().size() >= 1; + } + throw new Exception("权限检测错误!"); + + } + + } + + @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 { + + } +} +``` + +调用jwt解析前端携带的token,检测账号密码是否正确,如果正确则返回 **true**,否则抛出异常错误。 + +## 十一、前端基本配置 + +项目前端采用 **react+ant design**编写,语言使用 **typescript** ,开发工具使用 **webstorm** 。 + +首先使用webstorm创建react项目,使用typescript模板,然后添加依赖 + +### 添加依赖 + +```shell +yarn add antd +yarn add axios +yarn add react-router-dom + + +yarn add http-proxy-middleware -D +``` + +因为在项目开发过程中,需要解决跨域的问题,所以我们使用开发依赖 **http-proxy-middleware**,将前端的接口转发到本地开发地址。 + +### 解决本地调试跨域 + +**setupProxy.js** + +```javascript +const { createProxyMiddleware } = require('http-proxy-middleware'); + module.exports = function(app) { + app.use("/api",createProxyMiddleware({ + target: "http://127.0.0.1:8080", + changeOrigin: true, + pathRewrite:{ + "/api":"/" + } + })) + } +``` + +配置项目前端路由,在index.tsx中添加 **HashRouter**节点 + +### 配置根路由 + +**index.tsx** + +```typescript +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App'; +import reportWebVitals from './reportWebVitals'; +import {HashRouter} from "react-router-dom"; + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); +root.render( + + + + + +); +reportWebVitals(); +``` + +在App.tsx中添加基本页面,因为使用类组件开发,类组件中无法使用hooks等回调,所以将App组件使用WithRouter进行包装一下。 + +### 配置主页面 + +**App.tsx** + +```typescript +import React, {Component} from 'react'; +import './App.css'; +import { + useLocation, + useNavigate, + useParams, + Routes, + Route +} from "react-router-dom"; +import LoginPage from "./pages/LoginPage"; +import {PageProps} from "./models/props"; +import AdminPage from "./pages/admin/AdminPage"; +import TeacherPage from "./pages/teacher/TeacherPage"; +import StudentPage from "./pages/student/StudentPage"; +import AddTeacher from "./pages/admin/AddTeacher"; + + +function withRouter(Component: any) { + function ComponentWithRouterProp(props: any) { + let location = useLocation(); + let navigate = useNavigate(); + let params = useParams(); + return ( + + ); + } + + return ComponentWithRouterProp; +} + +class App extends Component { + render() { + return <> + + }/> + }> + + + }/> + }/> + }/> + + + } +} +export default withRouter(App); +``` + +## 十一、前端登录页面实现 + +首先封装axios,封装axios的接口为工具类 + +后端返回的json将其封装为 **IResponseData** + +### 封装axios + +**request.ts** + +```typescript +import axios, {AxiosInstance, AxiosRequestConfig, AxiosResponse} from "axios"; +import {message} from "antd"; + + +type TAxiosOption = { + baseURL: string; + timeout: number; +} + + +class Http { + service: AxiosInstance; + constructor(config:TAxiosOption) { + this.service = axios.create(config); + this.service.defaults.withCredentials = true + this.service.interceptors.request.use( + (value)=>{ + if (value.headers !== null){ + // @ts-ignore + value.headers.Authorization = "Bearer "+sessionStorage.getItem("work_token") + } + return value + },()=>{ + console.log("请求异常") + }) + this.service.interceptors.response.use((value:AxiosResponse>)=>{ + console.log(value) + return value + },(error)=>{ + console.log(error.response.data.message) + if (error.response.data.message !== ""){ + message.error(error.response.data.message).then(() => {}) + } + if (error.response.status === 401){ + window.location.hash = "/login" + } + return error + }) + } + + get(url: string, params?: object, _object = {}): Promise>> { + return this.service.get(url, { params, ..._object }) + } + post(url: string, data?: object, _object:AxiosRequestConfig = {}): Promise>> { + return this.service.post(url, data, _object) + } + put(url: string, params?: object, _object = {}): Promise>> { + return this.service.put(url, params, _object) + } + delete(url: string, params?: any, _object = {}): Promise>> { + return this.service.delete(url, { params, ..._object }) + } +} + +export default Http + +export interface IResponseData { + success: boolean; + message?:string; + data:T; + code: number; + error?:string +} +``` + +封装调用后端api的接口为一个类 + +### 封装接口 + +**api.ts** + +```typescript +import Http, {IResponseData} from "./request"; +import { + AdminClass, + GetAdminClassResp, + GetStudentResp, + GetTeacherClassResp, + GetTeacherResp, + LoginResp, + Student, + Teacher +} from "../models/resp"; + +let http = new Http({ + baseURL: "/", + timeout: 30000 +}); + +let base = process.env.REACT_APP_BASE_URL + +class Api { + + login = async function(type:string, username: string, password: string) { + let data = await http.post(`${base}/${type}/login`,{ + "phone": username, + "password": password + }); + + return data.data + } +} + +export default new Api() +``` + +登录页面组件采用Layout进行管理,在header组件中添加页面标题,在content组件中添加Form表单。 + +### 登录主页面 + +```typescript +import {Component} from "react"; +import {PageProps} from "../models/props"; +import {Button, Checkbox, Form, Input, Layout, Select} from "antd"; + +import { Col, Row } from 'antd'; + +import loginImg from "../img/loginnew-img.jpg"; + +import '../utils/api' +import api from "../utils/api"; +const { Header, Footer, Content } = Layout; + + +const options = [ + { + "label": "学生", + "value": "student" + }, + { + "label": "老师", + "value": "teacher" + }, + { + "label": "家长", + "value": "patriarch" + }, + { + "label": "管理员", + "value": "admin" + } +] + +class LoginPage extends Component{ + + + onSubmit = (value:any) => { + api.login(value.type,value.phone,value.password).then(resp => { + if (resp.code === 200){ + sessionStorage.setItem("work_token",resp.data.token) + this.props.router.navigate(`/${value.type}`) + } + }) + } + + render() { + return (<> + +
+ + +

分 层 教 学 作 业 收 集 系 统

+ + + +
+
+ + + + 图片 + + +

登 录

+
+
+ + + + + + + + + + 记住密码 + + + + + +
+ + + + + + +