This commit is contained in:
wzp 2024-04-12 21:20:27 +08:00
commit 8bf450c2a3
237 changed files with 56315 additions and 0 deletions

20
.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
.idea
target
out
*.class
*.iml
web/node_modules
nginx/nginx.exe
nginx/temp
nginx/logs
nginx/docs
nginx/contrib
nginx/auto
nginx/man
nginx/src
nginx/CHANGES
nginx/CHANGES.ru
nginx/configure
nginx/LICENSE
nginx/README
filesave

203
LICENSE Normal file
View File

@ -0,0 +1,203 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2022 xinsin
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

111
README.md Normal file
View File

@ -0,0 +1,111 @@
<div align="center"><img alt="Logo" height="128" src="web/src/assets/logo.png" width="128"/></div>
<h2 align="center">🌟项目名称: WitsTalk</h2>
<h5 align="center">一个能在网页语音的项目.</h5>
<h5 align="center">🚧 WitsTalk还在开发状态下,请勿当作主力使用.</h5>
<div align="center">
<img alt="GitHub" src="https://img.shields.io/github/license/xin-sin/WitsTalk?style=for-the-badge">
<img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/xin-sin/WitsTalk/Experimental-UI?style=for-the-badge">
</div>
<div align="center">
<img src="https://img.shields.io/badge/Node-%3E%3Dv14.18.1-green">
<img src="https://img.shields.io/badge/Vue-v3.0-blue">
<img src="https://img.shields.io/badge/yarn-v1.22.19-yellow">
<img src="https://img.shields.io/badge/Java-%3E%3Dv1.8-orange">
<img src="https://img.shields.io/badge/SPRING%20BOOT-v2.7-green">
<img src="https://img.shields.io/badge/Nety-v4.1.86.Final-lightgrey">
</div>
## ✨ WitsTalk是什么项目
- `WitsTalk`是我们项目团队在2021/12/11制作的开源项目,项目的构思是在群语音的时候发现了一些缺点,才开发此项目.
- 实现在语音聊天中群员可以调节任何群员的`输出音量`以及自己的`输入音量`.
- 管理员拥有最高权限,可以开关群员的`麦克风`,也可以调节群员的`输入音量`. (...暂定是这些功能)
- 项目的初衷是给Minecraft玩家一个更舒服的语音环境,更好的交流环境.
## 💡️ WitsTalk该如何使用
- ~~由于该项目还在开发中,暂不提供使用方式,只提供开发方法~~
- 一开完毕将提供完整的`使用文档``release`
## ✏️ 如何向WitsTalk提交代码?
- 1.Fork`WitsTalk`
- 2.维护代码~
- 3.请遵守以下提交格式:
- `🚧 Fix`,` Feat`,`🔨 Refactor`,`📝 Docs`,`✨ Style`,`🍱 Perf`,`🔧 Test`,`⚡️ Chore`,`🐛 Bug`
- 4.提交到`主仓库`的修改的`相应分支`.
## ✅ 如何发送Issues?
- 请遵守以下提交格式:
- `🐛 Bug`,`✨ Style`,`🎨 Proposai`.
## 👥 本项目开发人员
- `[UI设计、前端]Mo_Yi` `[后端、前端]xinxin` `[后端、前端]wzp`
- [Dongyifengs 的 GitHub](https://github.com/Dongyifengs)
- [XinSin-top 的 GitHub](https://github.com/xin-sin)
- [Wzp-2008 的 GitHub](https://github.com/Wzp-2008)
## ⚖️ 开源协议
- 本项目是面向大众的,所以我们会进行开源,请遵循相关开源协议 [Apache License 2.0](https://github.com/XinSin-top/witsTalk/blob/main/LICENSE) 的规则.
- 众人拾柴火焰高,开源需要依靠大家的努力,请自觉遵守开源协议,弘扬开源精神,共建开源社区!
## 🍀 鸣谢
<div align="center"><img alt="Logo" height="256" src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png?_gl=1*avq98w*_ga*NjQ5OTM0MzUxLjE2NDY1NTIyMzQ.*_ga_V0XZL7QHEB*MTY0Njk2NjY2Mi4zLjAuMTY0Njk2NjY2Mi4w" width="256"/></div>
## 🧑‍💻如何开发?
- 1.使用`git clone https://github.com/xin-sin/witsTalk.git` 下载我们的项目
- 2.我们建议您使用`idea`来进行开发,那样将会为您省去很多配置环境时间
- 3.在数据库中创建`user`
``` mysql
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户表id',
`username` varchar(25) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户表用户名',
`password` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户表密码',
`auth` enum('admin','user') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'user' COMMENT '用户表用户权限',
`online` tinyint(1) NOT NULL DEFAULT 0 COMMENT '用户表用户是否在线',
`last_login` datetime NULL DEFAULT NULL COMMENT '用户表用户最后上线时间',
`base64` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '用户表用户头像',
PRIMARY KEY (`id`) USING BTREE,
INDEX `username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
```
- 4.在数据库中创建`message`
``` mysql
CREATE TABLE `message` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '消息表id',
`content` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '消息表消息内容',
`sender` varchar(25) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '消息表发送者',
`recall` tinyint(1) UNSIGNED ZEROFILL NOT NULL DEFAULT 0 COMMENT '消息表是否撤回',
`sendtime` datetime NOT NULL COMMENT '消息表消息发送时间',
`type` enum('text','img') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'text' COMMENT '消息表消息类型',
PRIMARY KEY (`id`) USING BTREE,
INDEX `sender`(`sender`) USING BTREE,
CONSTRAINT `message_ibfk_1` FOREIGN KEY (`sender`) REFERENCES `witstalk`.`user` (`username`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
```
- 5.在数据库中创建`file`
``` mysql
CREATE TABLE `file` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '文件表id',
`size` double NOT NULL COMMENT '文件表文件大小',
`name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '文件表文件名',
`md5` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '文件表文件md5',
`uploadTime` datetime NOT NULL COMMENT '文件表文件上传时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
```
- 7.调整每一个模块的数据库`url`
- 8.使用`maven`来下载后端项目依赖
- 9.检查`mysql`数据库版本,并更改`pom.xml``JDBC`依赖版本
- 10.使用`npm install`下载前端项目依赖
- 11.(可选).配置`nginx`反向代理,和端口号
- 12.启动前端项目`npm run dev`,启动nginx,启动后端项目:(还用我教吗?-_-):

291
http.md Normal file
View File

@ -0,0 +1,291 @@
# 该文件将列出所有接口以及使用
## user模块
### 用户登录
- url:`/user/api/login`
- method:`post`
- requestParameter:
| 参数名称 | 是否必须 | 注释 |
|----------|------|-----|
| username | √ | 用户名 |
| password | √ | 密码 |
- example:
~~~json
{
"username":"admin",
"password":"21232f297a57a5a743894a0e4a801fc3",
"//":"password是前端MD5加密后"
}
~~~
- responseParameter:
| 参数名称 | 注释 |
|----------|------|
| canLogin | 是否成功 |
| status | 状态码 |
- example:
~~~json
{
"canLogin":true,
"status":200,
"//": "登陆成功后会将token写在请求头中以后每次请求需要携带token进行验证"
}
~~~
### 添加用户
- url:`/user/api/adduser`
- method:`post`
- requestParameter:
| 参数名称 | 是否必须 | 注释 |
|----------|------|----------|
| username | √ | 用户名 |
| password | √ | 密码 |
| auth | √ | 权限 |
| base64 | √ | 头像base64 |
- example:
~~~json
{
"username":"user",
"password":"21232f297a57a5a743894a0e4a801fc3",
"auth": "user",
"base64": "去头的base64",
"//":"password是前端MD5加密后",
"/*": "base64是去除{data:image/png;base64,}"
}
~~~
- responseParameter:
| 参数名称 | 注释 |
|--------|-----|
| status | 状态码 |
- example:
~~~json
{
"status":200
}
~~~
### 修改密码
- url:`/user/api/changepassword`
- method:`post`
- requestParameter:
| 参数名称 | 是否必须 | 注释 |
|----------|------|-----|
| username | √ | 用户名 |
| password | √ | 密码 |
- example:
~~~json
{
"username":"user",
"password":"21232f297a57a5a743894a0e4a801fc3",
"//":"password是前端MD5加密后"
}
~~~
- responseParameter:
| 参数名称 | 注释 |
|--------|-----|
| status | 状态码 |
- example:
~~~json
{
"status":200
}
~~~
### 修改头像
- url:`/user/api/setHeadPortrait`
- method:`post`
- requestParameter:
| 参数名称 | 是否必须 | 注释 |
|----------|------|--------|
| username | √ | 用户名 |
| base64 | √ | base64 |
- example:
~~~json
{
"username":"user",
"base64": "去头的base64"
}
~~~
- responseParameter:
| 参数名称 | 注释 |
|--------|-----|
| status | 状态码 |
- example:
~~~json
{
"status":200
}
~~~
### 获取头像
- url:`/user/api/getUserHeadPortrait`
- method:`get`
- requestParameter:
| 参数名称 | 是否必须 | 注释 |
|----------|------|-----|
| username | √ | 用户名 |
- example:
~~~json
{
"username":"user"
}
~~~
- responseParameter:
| 参数名称 | 注释 |
|--------|--------|
| data | base64 |
| status | 状态码 |
- example:
~~~json
{
"data": "去头的base64",
"status":200
}
~~~
### 获取在线人数
- url:`/user/api/getOnlineUser`
- method:`get`
- responseParameter:
| 参数名称 | 注释 |
|--------|------|
| data | 在线用户 |
| status | 状态码 |
- example:
~~~json
{
"data": "countUser",
"status":200
}
~~~
## fileTransfer模块
### 文件上传
- url:`/file/api/fileUpload`
- method:`post`
- requestParameter:
| 参数名称 | 是否必须 | 注释 |
|------|------|-----|
| file | √ | 文件 |
- example:
~~~json
{
"file":"file"
}
~~~
- responseParameter:
| 参数名称 | 注释 |
|--------|-------|
| data | 文件md5 |
| status | 状态码 |
- example:
~~~json
{
"data": "md5",
"status":200
}
~~~
### 获取文件名字
- url:`/file/api/getName`
- method:`get`
- requestParameter:
| 参数名称 | 是否必须 | 注释 |
|------|------|--------|
| md5 | √ | 文件MD5值 |
- responseParameter:
| 参数名称 | 注释 |
|--------|------|
| data | 文件名字 |
| status | 状态码 |
- example:
~~~json
{
"data": "文件名字",
"status":200
}
~~~
### 下载文件
- url:`/file/api/downloadFile`
- method:`get`
- requestParameter:
| 参数名称 | 是否必须 | 注释 |
|----------|------|--------|
| md5 | √ | 文件MD5值 |
| filename | √ | 文件名字 |
| token | √ | token |
- responseParameter:
| 参数名称 | 注释 |
|----------------|-----|
| ResponseEntity | 文件流 |
### 获取全部文件名字
- url:`/file/api/getAllFileNames`
- method:`post`
- responseParameter:
| 参数名称 | 注释 |
|--------|--------|
| data | 全部文件名字 |
| status | 状态码 |
- example:
~~~json
{
"data": "全部文件名字",
"status":200
}
~~~

78
lib-chat/pom.xml Normal file
View File

@ -0,0 +1,78 @@
<?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">
<parent>
<artifactId>witsTalk</artifactId>
<groupId>top.xinsin</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lib-chat</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>top.xinsin</groupId>
<artifactId>lib-utils</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!--netty框架依赖-->
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.86.Final</version>
</dependency>
<!--spring-boot mybatis依赖-->
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--druid连接池依赖-->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
<!--mysqlJDBC依赖-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--spring-boot web模块-->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<!--<directory>..\target</directory>-->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<!--suppress MavenModelInspection -->
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,44 @@
package cn.wzpmc;
import cn.wzpmc.services.ChatCommandHandler;
import io.netty.channel.*;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
import top.xinsin.utils.NettyUtils;
/**
* @author wzp
* Created On 2022/5/14
* @version 1.0
*/
@ChannelHandler.Sharable
@Slf4j
@Component
public class ChatFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
private static NettyUtils<ChatCommandHandler> commandHandler = null;
public static void setCommandHandler(ConfigurableApplicationContext configurableApplicationContext) {
if(ChatFrameHandler.commandHandler == null){
ChatFrameHandler.commandHandler = new NettyUtils<>(ChatCommandHandler.class, configurableApplicationContext);
}
}
@Override
public void handlerAdded(ChannelHandlerContext channelHandlerContext) {
commandHandler.handlerJoin(channelHandlerContext);
}
@Override
public void handlerRemoved(ChannelHandlerContext channelHandlerContext) {
commandHandler.handlerClose(channelHandlerContext);
}
@Override
public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable cause) {
commandHandler.handlerError(channelHandlerContext,cause);
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) {
commandHandler.handler(channelHandlerContext, textWebSocketFrame);
}
}

View File

@ -0,0 +1,43 @@
package cn.wzpmc;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.springframework.stereotype.Component;
/**
* @author wzp
* Created On 2022/5/14
* @version 1.0
*/
@Component
public class ChatHandler extends ChannelInitializer<SocketChannel> {
private final ChatFrameHandler chatFrameHandler;
public ChatHandler(ChatFrameHandler chatFrameHandler){
this.chatFrameHandler = chatFrameHandler;
}
@Override
protected void initChannel(SocketChannel socket){
//获取pipeline
ChannelPipeline pipeline = socket.pipeline();
//添加解析器
//日志解析器
pipeline.addLast(new LoggingHandler(LogLevel.INFO));
//Http解析器
pipeline.addLast(new HttpServerCodec());
//块解析器
pipeline.addLast(new ChunkedWriteHandler());
//聚合解析器
pipeline.addLast(new HttpObjectAggregator(16384));
//设置uri为/connect
pipeline.addLast(new WebSocketServerProtocolHandler("/chat"));
//自定义解析器
pipeline.addLast(chatFrameHandler);
}
}

View File

@ -0,0 +1,32 @@
package cn.wzpmc;
import lombok.SneakyThrows;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @author wzp
* Created On 2022/5/14
* @version 1.0
*/
@MapperScan("cn.wzpmc.dao")
@SpringBootApplication
public class ChatStart {
private static Netty netty;
@Autowired
public void setNetty(Netty netty){
ChatStart.netty = netty;
}
@SneakyThrows
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(ChatStart.class);
ConfigurableApplicationContext run = springApplication.run(args);
ChatFrameHandler.setCommandHandler(run);
springApplication.addListeners(netty);
netty.start();
}
}

View File

@ -0,0 +1,15 @@
package cn.wzpmc;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.datasource.pooled.PooledDataSourceFactory;
/**
* @author wzp
* Created On 2022/5/14
* @version 1.0
*/
public class DataSourceFactory extends PooledDataSourceFactory {
public DataSourceFactory(){
this.dataSource = new DruidDataSource();
}
}

View File

@ -0,0 +1,77 @@
package cn.wzpmc;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
import java.util.HashMap;
/**
* @author wzp
* @version 1.0
* Created On 2022/5/14
*/
@Slf4j
@Component
public class Netty implements ApplicationListener<ContextClosedEvent> {
@Value("${server.port}")
private int port;
private final EventLoopGroup boss = new NioEventLoopGroup();
private final EventLoopGroup work = new NioEventLoopGroup();
private Channel channel;
private final ChatHandler chatHandler;
@Autowired
public Netty(ChatHandler chatHandler){
this.chatHandler = chatHandler;
}
/**
* Start netty service
*/
public void start(){
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss,work)
//设置日志处理器
.handler(new LoggingHandler(LogLevel.DEBUG))
//设置自主处理器
.childHandler(chatHandler)
//设置通道
.channel(NioServerSocketChannel.class);
try {
ChannelFuture future = bootstrap.bind(new InetSocketAddress(port)).sync();
if (future.isSuccess()){
log.info("Chat Server started on 0.0.0.0:{}",port);
}
this.channel = future.channel();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void destroy() {
log.info("Shutdown Netty Server...");
if(channel != null) {
channel.close();
}
work.shutdownGracefully();
boss.shutdownGracefully();
log.info("Shutdown Netty Server Success!");
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
this.destroy();
}
}

View File

@ -0,0 +1,52 @@
package cn.wzpmc.dao;
import cn.wzpmc.pojo.Message;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
/**
* @author wzp
*/
@Mapper
@Repository
public interface ChatDao {
/**
* send a message to database
* @param message the message you want to send
*/
void sendMessage(Message message);
/**
* get message with id > id_min and id < id_max
*
* @param count the count of message
* @param idMin the max value of id
* @return some message objects
*/
ArrayList<Message> getMessage(@Param("id_min") int idMin, @Param("count") int count);
/**
* get all messages count
* @return count of message
*/
Integer getCount();
/**
* get a user's head portrait with username
* @param username username
* @return a base64 of user's head portrait
*/
String getUserHeadPortrait(@Param("username") String username);
/**
* recall a message
* @param id the id of the message you want to recall
*/
void recall(@Param("id") Integer id);
List<Message> getNewMessage(@Param("id_min") Integer minId,@Param("id_max") Integer maxId,@Param("count") Integer count);
}

View File

@ -0,0 +1,14 @@
package cn.wzpmc.pojo;
import lombok.Data;
/**
* @author wzp
* Created On 2022/5/14
* @version 1.0
*/
@Data
public class HeadPortrait {
private String username;
private String base64;
}

View File

@ -0,0 +1,64 @@
package cn.wzpmc.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import top.xinsin.enums.MessageTypes;
import java.text.SimpleDateFormat;
import java.util.Calendar;
/**
* @author wzp
* Created On 2022/5/14
* @version 1.0
*/
@Data
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class Message {
private int id;
private String content;
private String sender;
private int recall;
private String sendtime;
private String base64;
private MessageTypes type;
public Message(int id){
this.id = id;
}
public Message(int id, String content, String sender, int recall, String sendtime, String type, String base64){
this.content = content;
this.sender = sender;
this.id = id;
this.recall = recall;
this.type = MessageTypes.valueOf(type);
this.sendtime = sendtime;
this.base64 = base64;
}
public Message(String content, String sender,String type){
this.content = content;
this.sender = sender;
this.type = MessageTypes.valueOf(type);
Calendar calendar= Calendar.getInstance();
SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd :hh:mm:ss");
this.sendtime = dateFormat.format(calendar.getTime());
}
public Message(String content, String sender,String type,String sendtime){
this.content = content;
this.sender = sender;
this.type = MessageTypes.valueOf(type);
this.sendtime = sendtime;
}
public Message(int id,String content,String sender,int recall,String type,String sendtime){
this.content = content;
this.sender = sender;
this.id = id;
this.recall = recall;
this.type = MessageTypes.valueOf(type);
this.sendtime = sendtime;
}
}

View File

@ -0,0 +1,121 @@
package cn.wzpmc.services;
import cn.wzpmc.dao.ChatDao;
import cn.wzpmc.pojo.Message;
import com.alibaba.fastjson2.JSONObject;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import top.xinsin.interfaces.NettyCommandHandler;
import top.xinsin.utils.JwtTokenUtils;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
@Slf4j
public class ChatCommandHandler implements NettyCommandHandler {
private final ChatDao chatDao;
private final Map<ChannelId, Channel> users = new ConcurrentHashMap<>();
private final Map<ChannelId, Boolean> loginTable = new ConcurrentHashMap<>();
@Autowired
public ChatCommandHandler(ChatDao chatDao){
this.chatDao = chatDao;
}
public JSONObject handlerLogin(ChannelHandlerContext channelHandlerContext, String token){
Boolean right = JwtTokenUtils.isRight(token);
Channel channel = channelHandlerContext.channel();
ChannelId id = channel.id();
loginTable.put(id, right);
JSONObject result = new JSONObject();
result.fluentPut("success", right);
return result;
}
public List<Message> handlerGet(ChannelHandlerContext channelHandlerContext,Integer minId,Integer count){
if (!loginTable.getOrDefault(channelHandlerContext.channel().id(), false)) {
return null;
}
return chatDao.getMessage(minId, count);
}
public List<Message> handlerGetNew(ChannelHandlerContext channelHandlerContext,Integer minId,Integer maxId,Integer count){
if (!loginTable.getOrDefault(channelHandlerContext.channel().id(), false)) {
return null;
}
return chatDao.getNewMessage(minId, maxId, count);
}
public int handlerCount(ChannelHandlerContext channelHandlerContext){
if (!loginTable.getOrDefault(channelHandlerContext.channel().id(), false)) {
return -1;
}
return chatDao.getCount();
}
public JSONObject handlerSend(ChannelHandlerContext channelHandlerContext, String content,String sender,Integer type){
if (!loginTable.getOrDefault(channelHandlerContext.channel().id(), false)) {
return null;
}
String type_ = "img";
if (type == 0){
type_ = "text";
}
Message message = new Message(content, sender, type_);
chatDao.sendMessage(message);
String base64 = chatDao.getUserHeadPortrait(sender);
message.setBase64(base64);
ChannelId id = channelHandlerContext.channel().id();
JSONObject toAll = new JSONObject().fluentPut("op", "add").fluentPut("data", new JSONObject().fluentPut("message", message));
for (Map.Entry<ChannelId, Channel> channelIdChannelEntry : users.entrySet()) {
if (!channelIdChannelEntry.getKey().equals(id)) {
channelIdChannelEntry.getValue().writeAndFlush(new TextWebSocketFrame(toAll.toJSONString()));
}
}
return new JSONObject().fluentPut("message", message);
}
public void handlerRemove(ChannelHandlerContext channelHandlerContext, Integer id){
if (!loginTable.getOrDefault(channelHandlerContext.channel().id(), false)) {
return;
}
JSONObject toAll = new JSONObject().fluentPut("op", "recall").fluentPut("data", new JSONObject().fluentPut("id", id));
for (Map.Entry<ChannelId, Channel> channelIdChannelEntry : users.entrySet()) {
if (!channelIdChannelEntry.getKey().equals(channelHandlerContext.channel().id())) {
channelIdChannelEntry.getValue().writeAndFlush(new TextWebSocketFrame(toAll.toJSONString()));
}
}
chatDao.recall(id);
}
public void handlerPing(ChannelHandlerContext channelHandlerContext){
Channel channel = channelHandlerContext.channel();
JSONObject jsonObject = new JSONObject();
jsonObject.fluentPut("op", "pong").fluentPut("data",new JSONObject().fluentPut("time", System.currentTimeMillis()));
channel.writeAndFlush(new TextWebSocketFrame(jsonObject.toJSONString()));
}
@Override
public void handlerClose(ChannelHandlerContext channelHandlerContext) {
Channel channel = channelHandlerContext.channel();
ChannelId id = channel.id();
users.remove(id);
log.info("User {} disconnected in channel {}",channel.remoteAddress(), id.asShortText());
}
@Override
public void handlerJoin(ChannelHandlerContext channelHandlerContext) {
Channel channel = channelHandlerContext.channel();
ChannelId id = channel.id();
users.put(id, channel);
log.info("User {} connected in channel {}",channel.remoteAddress(), id.asShortText());
}
@Override
public void handlerError(ChannelHandlerContext channelHandlerContext, Throwable cause) {
Channel channel = channelHandlerContext.channel();
ChannelId id = channel.id();
users.remove(id);
log.info("User {} disconnected with error in channel {}",channel.remoteAddress(), id.asShortText());
cause.printStackTrace();
}
}

View File

@ -0,0 +1,29 @@
server:
port: 8005
spring:
datasource:
username: witstalk
password: witsTalk
url: jdbc:mysql://wzpmc.cn:3306/witstalk?serverTimezone=UTC-8
driver-class-name: com.mysql.cj.jdbc.Driver
# druid配置
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters去掉后监控界面sql无法统计'wall'用于防火墙
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
mapper-locations: classpath:cn/wzpmc/dao/*Dao.xml
type-aliases-package: cn.wzpmc.dao

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.wzpmc.dao.ChatDao">
<insert id="sendMessage" parameterType="cn.wzpmc.pojo.Message" useGeneratedKeys="true" keyProperty="id">
insert into message
set content=#{content},
sender=#{sender},
type=#{type},
sendtime=#{sendtime};
</insert>
<update id="recall" parameterType="cn.wzpmc.pojo.Message">
update message
set recall=1
where id = #{id};
</update>
<select id="getMessage" resultType="cn.wzpmc.pojo.Message">
select m.*,u.base64 from message m inner join user u on u.username = m.sender where m.id >= #{id_min} and m.recall = 0 order by m.id desc limit #{count};
</select>
<select id="getUserHeadPortrait" resultType="java.lang.String">
select `base64` from user where BINARY `username`=#{username};
</select>
<select id="getCount" resultType="java.lang.Integer">
select COUNT(*) from message;
</select>
<select id="getNewMessage" resultType="cn.wzpmc.pojo.Message">
select m.*,u.base64 from message m inner join user u on u.username = m.sender where m.id >= #{id_min} and m.id &lt;= #{id_max} and m.recall = 0 order by m.id desc limit #{count};
</select>
</mapper>

55
lib-fileTransfer/pom.xml Normal file
View File

@ -0,0 +1,55 @@
<?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">
<parent>
<artifactId>witsTalk</artifactId>
<groupId>top.xinsin</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lib-fileTransfer</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>top.xinsin</groupId>
<artifactId>lib-utils</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!--spring-boot mybatis依赖-->
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--druid连接池依赖-->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
<!--mysqlJDBC依赖-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<!--<directory>..\target</directory>-->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<!--suppress MavenModelInspection -->
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,18 @@
package top.xinsin;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author xinxin
* Created On 2021/12/14 14:00
* @version 1.0
*/
@MapperScan("top.xinsin.dao")
@SpringBootApplication
public class FileTransferStart {
public static void main(String[] args) {
SpringApplication.run(FileTransferStart.class,args);
}
}

View File

@ -0,0 +1,35 @@
package top.xinsin.config;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author wzp
* Created On 2021/12/11 21:59
* @version 1.0
* mvn配置
*/
@SpringBootConfiguration
public class GlobalWebMvcConfig implements WebMvcConfigurer {
/**
* 解决跨域问题
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
//添加映射路径
registry.addMapping("/**")
//是否发送Cookie
.allowCredentials(true)
//设置放行哪些原始域 SpringBoot2.4.4下低版本使用.allowedOrigins("*")
.allowedOriginPatterns("*")
//放行哪些请求方式
.allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS", "HEAD")
//.allowedMethods("*") //或者放行全部
//放行哪些原始请求头部信息
.allowedHeaders("*")
//暴露哪些原始请求头部信息
.exposedHeaders("*");
}
}

View File

@ -0,0 +1,30 @@
package top.xinsin.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.xinsin.interceptor.AuthenticationInterceptor;
/**
* @author xinxin
* Created On 2022/1/21 22:14
* @version 1.0
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
private AuthenticationInterceptor authenticationInterceptor;
@Autowired
public InterceptorConfig(AuthenticationInterceptor authenticationInterceptor){
this.authenticationInterceptor = authenticationInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor)
//拦截
.addPathPatterns("/**")
.excludePathPatterns("/file/api/downloadFile")
.excludePathPatterns("/api/fileUpload");
}
}

View File

@ -0,0 +1,27 @@
package top.xinsin.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import top.xinsin.service.FileDownloadService;
import top.xinsin.service.FileService;
import top.xinsin.utils.RData;
/**
* @author xinsin
* Created On 2022/12/22 11:20
* @version 1.0
*/
@RestController
public class FileController {
private final FileService fileService;
@Autowired
public FileController(FileService fileService){
this.fileService = fileService;
}
@GetMapping("/file/fileDelete")
public RData<String> fileDelete(@RequestParam("id")Integer id,@RequestParam("md5")String md5){
return fileService.fileDelete(id,md5);
}
}

View File

@ -0,0 +1,43 @@
package top.xinsin.controller;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import top.xinsin.pojo.FileObject;
import top.xinsin.service.FileDownloadService;
import top.xinsin.utils.RData;
import java.io.IOException;
/**
* @author xinxin
* Created On 2022/1/2 15:09
* @version 1.0
*/
@RestController
public class FileDownloadController {
private final FileDownloadService fileDownloadService;
@Autowired
public FileDownloadController(FileDownloadService fileDownloadService){
this.fileDownloadService = fileDownloadService;
}
@GetMapping("/file/api/getName")
public RData<String> getFileName(@RequestParam("md5") String md5){
return fileDownloadService.getFileName(new FileObject(md5));
}
@GetMapping("/file/api/downloadFile")
public ResponseEntity<InputStreamResource> getFile(@RequestParam("md5") String md5, @RequestParam("filename") String filename, @RequestParam("token") String token) throws IOException {
return fileDownloadService.getFile(md5, filename, token);
}
@PostMapping("/file/api/getAllFileNames")
public RData<JSONObject> getAllFileNames(@RequestParam("min_id") int minId, @RequestParam("pageSize") Integer pageSize) {
return fileDownloadService.getAllFileNames(minId,pageSize);
}
}

View File

@ -0,0 +1,39 @@
package top.xinsin.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import top.xinsin.service.FileUpLoadService;
import top.xinsin.utils.RData;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
/**
* @author xinxin
* Created On 2021/12/14 14:07
* @version 1.0
*/
@RestController
public class FileUpLoadController {
private final FileUpLoadService fileUpLoadService;
@Autowired
public FileUpLoadController(FileUpLoadService fileUpLoadService) {
this.fileUpLoadService = fileUpLoadService;
}
@PostMapping("/file/api/fileUpload")
public RData<String> fileUpload(@RequestBody MultipartFile file, HttpServletRequest request) throws NoSuchAlgorithmException, IOException {
return fileUpLoadService.fileUpload(file, request);
}
@GetMapping("/file/api/debug")
public String debug() {
return "123";
}
}

View File

@ -0,0 +1,31 @@
package top.xinsin.dao;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import top.xinsin.pojo.FileObject;
import java.util.ArrayList;
/**
* @author xinxin
* Created On 2021/12/23 19:07
* @version 1.0
*/
@Repository
public interface FileDownloadMapper {
/**
* 获取原始文件名称
* @param fileObject 文件对象
* @return 文件名称
*/
String getFileName(FileObject fileObject);
/**
* 获取所有文件的名称
*
* @return 文件名称列表
*/
ArrayList<FileObject> getAllFileNames(@Param("min_id")Integer minId,@Param("pageSize")Integer pageSize);
Integer selectFileCount();
}

View File

@ -0,0 +1,15 @@
package top.xinsin.dao;
import org.apache.ibatis.annotations.Mapper;
import top.xinsin.pojo.FileObject;
/**
* @author xinsin
* Created On 2022/12/22 11:23
* @version 1.0
*/
@Mapper
public interface FileMapper {
Integer deleteByIdAndMd5(Integer id, String md5);
FileObject selectByMD5(String md5);
}

View File

@ -0,0 +1,19 @@
package top.xinsin.dao;
import org.springframework.stereotype.Repository;
import top.xinsin.pojo.FileObject;
/**
* @author xinxin
* Created On 2022/1/2 15:11
* @version 1.0
*/
@Repository
public interface FileUploadMapper {
/**
* 上传文件
* @param fileObject 文件对象
*/
void addFile(FileObject fileObject);
}

View File

@ -0,0 +1,19 @@
package top.xinsin.dao;
import org.springframework.stereotype.Repository;
import top.xinsin.pojo.AuthVerificationTokenJwt;
/**
* @author xinxin
* Created On 2022/1/22 9:22
* @version 1.0
*/
@Repository
public interface UserVerifyMapper {
/**
* 用户验证映射
* @param authVerificationTokenJwt 用户的JWTToken
* @return 用户是否在线
*/
Integer userVerify(AuthVerificationTokenJwt authVerificationTokenJwt);
}

View File

@ -0,0 +1,69 @@
package top.xinsin.interceptor;
import com.alibaba.fastjson2.JSONObject;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import sun.misc.BASE64Decoder;
import top.xinsin.pojo.AuthVerificationTokenJwt;
import top.xinsin.service.UserVerifyService;
import top.xinsin.utils.JwtTokenUtils;
import top.xinsin.utils.ResponseData;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* @author xinxin
* Created On 2022/1/21 22:16
* @version 1.0
*/
@Component
@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private UserVerifyService userVerifyService;
public static final String OPTIONS = "OPTIONS";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (OPTIONS.equals(request.getMethod())) {
return true;
}
// 从http请求头中取出token
final String token = request.getHeader("Access-Token");
//解决跨域问题
response.setHeader("Access-Control-Allow-Origin","*");
response.setHeader("Access-Control-Allow-Headers","*");
ResponseData responseData;
String data = null;
System.out.println(token);
//获取token
DecodedJWT tokenInfo = JwtTokenUtils.getTokenInfo(token);
try {
//解析token
data = new String(new BASE64Decoder().decodeBuffer(tokenInfo.getPayload()), StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
//查找json字符串中的结尾 并取下标将其分割
assert data != null;
data = data.substring(0,data.indexOf("}",39) + 1);
//获取实体类对象
AuthVerificationTokenJwt authVerificationTokenJwt = JSONObject.parseObject(data, AuthVerificationTokenJwt.class);
if (!userVerifyService.userVerify(authVerificationTokenJwt)) {
log.warn("AuthenticationInterceptor --> username --> " + authVerificationTokenJwt.getUsername() + ": TokenVerifyFail:(");
responseData = new ResponseData("token验证失败,该用户可能还没有登陆", HttpStatus.UNAUTHORIZED);
}else{
log.info("AuthenticationInterceptor --> username --> " + authVerificationTokenJwt.getUsername() + ": TokenVerifySuccess:)");
return true;
}
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(responseData.toString());
return false;
}
}

View File

@ -0,0 +1,17 @@
package top.xinsin.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author xinxin
* Created On 2022/1/21 22:09
* @version 1.0
*/
@AllArgsConstructor
@Data
public class AuthVerificationTokenJwt {
private String id;
private String exp;
private String username;
}

View File

@ -0,0 +1,39 @@
package top.xinsin.pojo;
import lombok.Data;
/**
* @author wzp
* Created On 2021/12/24 14:48
* @version 1.0
*/
@Data
public class FileObject {
private int id;
private long size;
private String name;
private String md5;
private String uploadTime;
private String uploaderName;
private int uploaderId;
public FileObject(String name, long size, String md5, int uploadId) {
this.name = name;
this.size = size;
this.md5 = md5;
this.uploaderId = uploadId;
}
public FileObject(String md5) {
this.md5 = md5;
}
public FileObject(int id, long size, String name, String md5, String uploadTime, String uploaderName) {
this.id = id;
this.size = size;
this.name = name;
this.md5 = md5;
this.uploadTime = uploadTime;
this.uploaderName = uploaderName;
}
}

View File

@ -0,0 +1,90 @@
package top.xinsin.service;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import top.xinsin.dao.FileDownloadMapper;
import top.xinsin.pojo.FileObject;
import top.xinsin.utils.JwtTokenUtils;
import top.xinsin.utils.RData;
import java.io.File;
import java.io.IOException;
/**
* @author xinxin
* Created On 2022/1/2 15:14
* @version 1.0
*/
@Service
@Slf4j
public class FileDownloadService {
private final FileDownloadMapper fileDownloadMapper;
/**
* 文件保存目录
*/
@Value("${saveFolder}")
private String fileSaveFolder;
@Autowired
public FileDownloadService(FileDownloadMapper fileDownloadMapper) {
this.fileDownloadMapper = fileDownloadMapper;
}
/**
* 获取原始文件名
* @param fileObject 文件对象
* @return 原始文件名
*/
public RData<String> getFileName(FileObject fileObject){
log.info("getFileName,args:{}",fileObject);
String fileName = fileDownloadMapper.getFileName(fileObject);
return RData.success(fileName);
}
/**
* 获取所有文件名称
*
* @return 文件名称
*/
public RData<JSONObject> getAllFileNames(Integer minId, Integer pageSize) {
log.info("getAllFileNames --> begin");
JSONObject jsonObject = new JSONObject();
jsonObject.fluentPut("files", fileDownloadMapper.getAllFileNames(minId,pageSize));
jsonObject.fluentPut("total", fileDownloadMapper.selectFileCount());
return RData.success(jsonObject);
}
/**
* 下载文件
* @param md5 文件md5值
* @param filename 下载时的文件名
* @param token JWTToken
* @return 文件
* @throws IOException 当文件获取失败时抛出
*/
public ResponseEntity<InputStreamResource> getFile(String md5, String filename,String token) throws IOException {
//验证token合法性
JwtTokenUtils.verify(token);
log.info("getFile,md5={},filename={}",md5,filename);
FileSystemResource file = new FileSystemResource(fileSaveFolder + File.separator + md5);
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Content-Disposition", String.format("attachment; filename=\"%s\"", filename));
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return ResponseEntity.ok().headers(headers)
.contentLength(file.contentLength())
.contentType(MediaType.parseMediaType("application/octet-stream"))
.body(new InputStreamResource(file.getInputStream()));
}
}

View File

@ -0,0 +1,46 @@
package top.xinsin.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import top.xinsin.dao.FileMapper;
import top.xinsin.dao.FileUploadMapper;
import top.xinsin.enums.HttpCodes;
import top.xinsin.utils.RData;
import java.io.File;
/**
* @author xinsin
* Created On 2022/12/22 11:22
* @version 1.0
*/
@Service
@Slf4j
public class FileService {
private final FileMapper fileMapper;
/**
* 文件保存的目录
*/
@Value("${saveFolder}")
private String fileSaveFolder;
@Autowired
public FileService(FileMapper fileMapper) {
this.fileMapper = fileMapper;
}
public RData<String> fileDelete(Integer id, String md5) {
Integer count = fileMapper.deleteByIdAndMd5(id,md5);
if (count != 0){
log.info("数据库数据删除成功-#:{},md5:{}",id,md5);
log.info("即将删除本地文件");
File file = new File(fileSaveFolder, md5);
if (file.delete()) {
log.info("本地文件删除成功-md5:{}",md5);
return RData.success("删除成功");
}
}
return RData.failed(HttpCodes.HTTP_CODES500,"删除文件失败");
}
}

View File

@ -0,0 +1,109 @@
package top.xinsin.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import top.xinsin.dao.FileMapper;
import top.xinsin.dao.FileUploadMapper;
import top.xinsin.enums.HttpCodes;
import top.xinsin.pojo.FileObject;
import top.xinsin.utils.FileUtils;
import top.xinsin.utils.JwtTokenUtils;
import top.xinsin.utils.RData;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
/**
* @author xinxin
* Created On 2021/12/18 13:40
* @version 1.0
*/
@Service
@Slf4j
public class FileUpLoadService {
private final FileUploadMapper fileUploadMapper;
private final FileMapper fileMapper;
/**
* 文件保存的目录
*/
@Value("${saveFolder}")
private String fileSaveFolder;
@Autowired
public FileUpLoadService(FileUploadMapper fileUploadMapper,FileMapper fileMapper) {
this.fileUploadMapper = fileUploadMapper;
this.fileMapper = fileMapper;
}
/**
* 上传文件服务
*
* @param file 文件
* @param request 请求
* @return 是否成功
*/
public RData<String> fileUpload(MultipartFile file, HttpServletRequest request) throws NoSuchAlgorithmException, IOException {
if (file.isEmpty()) {
return RData.failed(HttpCodes.HTTP_CODES401, "文件是空的");
}
log.info("starting saving");
//获取原始文件名
String originalFilename = file.getOriginalFilename();
//获取文件的InputStream
InputStream input = file.getInputStream();
//拼接文件保存路径
String savePath = fileSaveFolder + File.separator + originalFilename;
//获取文件对象
File file1 = new File(savePath);
//获取磁盘中的文件的OutputStream
FileOutputStream out = new FileOutputStream(file1);
//使用IOUtils来复制
IOUtils.copy(input,out);
//计算磁盘中文件的md5
String md5 = FileUtils.calcMd5(file1);
// 去重
FileObject fileObject1 = fileMapper.selectByMD5(md5);
if (fileObject1 != null){
return RData.failed(HttpCodes.HTTP_CODES500,"请不要上传相同的文件");
}
log.info("fileSaved,SavedName:{},md5:{}",savePath,md5);
//关闭输入流
input.close();
//关闭输出流
out.close();
//清除缓存
System.gc();
log.info("closeIOStream");
log.info("fileRename to {}",md5);
//获取使用md5拼接的文件名
String newSavePath = fileSaveFolder + File.separator + md5;
File newFile = new File(newSavePath);
if(newFile.exists()){
log.info("fileExists!");
}else{
//将磁盘中的文件重命名
boolean b = file1.renameTo(newFile);
log.info("fileRenamed,Succeed:{}",b);
log.info("Sending to database");
int id = Integer.parseInt(JwtTokenUtils.getTokenInfo(request.getHeader("Access-Token")).getClaim("id").asString());
//新建FileObject
FileObject fileObject = new FileObject(originalFilename, file.getSize(), md5, id);
//上传数据库
fileUploadMapper.addFile(fileObject);
log.info("Sanded");
log.info("fileUpload done");
}
return RData.success(md5);
}
}

View File

@ -0,0 +1,24 @@
package top.xinsin.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import top.xinsin.dao.UserVerifyMapper;
import top.xinsin.pojo.AuthVerificationTokenJwt;
/**
* @author xinxin
* Created On 2022/1/22 9:21
* @version 1.0
*/
@Service
public class UserVerifyService {
private final UserVerifyMapper userVerifyMapper;
@Autowired
public UserVerifyService(UserVerifyMapper userVerifyMapper) {
this.userVerifyMapper = userVerifyMapper;
}
public boolean userVerify(AuthVerificationTokenJwt authVerificationTokenJwt){
return userVerifyMapper.userVerify(authVerificationTokenJwt) == 1;
}
}

View File

@ -0,0 +1,36 @@
server:
port: 8004
spring:
datasource:
username: witstalk
password: witsTalk
url: jdbc:mysql://wzpmc.cn:3306/witstalk?serverTimezone=UTC-8
driver-class-name: com.mysql.cj.jdbc.Driver
# druid配置
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters去掉后监控界面sql无法统计'wall'用于防火墙
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mvc:
static-path-pattern: /static/**
mybatis:
mapper-locations: classpath:top/xinsin/dao/*Mapping.xml
type-aliases-package: top.xinsin.dao
servlet:
multipart:
max-file-size: 20GB
max-request-size: 20GB
saveFolder: ./filesave

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="top.xinsin.dao.FileDownloadMapper">
<select id="getFileName" parameterType="top.xinsin.pojo.FileObject" resultType="java.lang.String">
select name
from file
where md5 = #{md5};
</select>
<select id="getAllFileNames" resultType="top.xinsin.pojo.FileObject">
select f.id, f.size, f.`name`, f.md5, f.uploadTime, u.username as uploaderName
from file f
INNER JOIN `user` u on u.id = f.uploaderId
LIMIT #{min_id} ,#{pageSize}
</select>
<select id="selectFileCount" resultType="java.lang.Integer">
select count(*)
from file;
</select>
</mapper>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.xinsin.dao.FileMapper">
<delete id="deleteByIdAndMd5">
delete
from file
where id = #{id} and md5 = #{md5};
</delete>
<select id="selectByMD5" resultType="top.xinsin.pojo.FileObject">
select *
from file where md5 = #{md5};
</select>
</mapper>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="top.xinsin.dao.FileUploadMapper">
<insert id="addFile" parameterType="top.xinsin.pojo.FileObject">
insert into file
set size=#{size},
name=#{name},
md5=#{md5},
uploadTime=now(),
uploaderId=#{uploaderId};
</insert>
</mapper>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="top.xinsin.dao.UserVerifyMapper">
<select id="userVerify" resultType="java.lang.Integer" parameterType="top.xinsin.pojo.AuthVerificationTokenJwt">
select `online` from `user` where BINARY username = #{username} and id = #{id}
</select>
</mapper>

60
lib-user/pom.xml Normal file
View File

@ -0,0 +1,60 @@
<?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">
<parent>
<artifactId>witsTalk</artifactId>
<groupId>top.xinsin</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lib-user</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>top.xinsin</groupId>
<artifactId>lib-utils</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
<!--spring-boot mybatis依赖-->
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--druid连接池依赖-->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
<!--mysqlJDBC依赖-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<!--<directory>..\target</directory>-->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<!--suppress MavenModelInspection -->
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,17 @@
package top.xinsin;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
/**
* @author xinxin
* Created On 2021/12/11 17:16
* @version 1.0
*/
@MapperScan("top.xinsin.dao")
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class })
public class UserStart {
public static void main(String[] args){SpringApplication.run(UserStart.class,args);}
}

View File

@ -0,0 +1,35 @@
package top.xinsin.config;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author wzp
* Created On 2021/12/11 21:59
* @version 1.0
* mvn配置
*/
@SpringBootConfiguration
public class GlobalWebMvcConfig implements WebMvcConfigurer {
/**
* 解决跨域问题
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
//添加映射路径
registry.addMapping("/**")
//是否发送Cookie
.allowCredentials(true)
//设置放行哪些原始域 SpringBoot2.4.4下低版本使用.allowedOrigins("*")
.allowedOriginPatterns("*")
//放行哪些请求方式
.allowedMethods("GET", "POST", "DELETE", "PUT", "HEAD")
//.allowedMethods("*") //或者放行全部
//放行哪些原始请求头部信息
.allowedHeaders("*")
//暴露哪些原始请求头部信息
.exposedHeaders("*");
}
}

View File

@ -0,0 +1,31 @@
package top.xinsin.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.xinsin.interceptor.AuthenticationInterceptor;
/**
* @author xinxin
* Created On 2021/12/12 21:05
* @version 1.0
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthenticationInterceptor()).
//拦截
addPathPatterns("/**").
//放行登录接口
excludePathPatterns("/user/api/login").
//放行验证码接口
excludePathPatterns("/user/api/vc").
//放行注册接口
excludePathPatterns("/user/api/adduser").
// 放行token验证登陆接口
excludePathPatterns("/user/api/autoLogin").
//放行调试接口
excludePathPatterns("/user/api/debug");
}
}

View File

@ -0,0 +1,37 @@
package top.xinsin.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import top.xinsin.pojo.User;
import top.xinsin.services.UserService;
import top.xinsin.utils.RData;
import java.util.Date;
import java.util.HashMap;
/**
* @author wzp
* Created On 2021/12/12 10:49
* @version 1.0
*/
@RestController
@Slf4j
public class CheckController {
private final UserService userService;
@Autowired
public CheckController(UserService userService){
this.userService = userService;
}
public static HashMap<User, Date> checklist = new HashMap<>();
@PostMapping("/user/api/check")
public RData<Date> check(@RequestBody User user){
log.info("Check args:user=" + user);
checklist.put(user,new Date());
userService.setOnline(user);
return RData.success(checklist.get(user));
}
}

View File

@ -0,0 +1,36 @@
package top.xinsin.controller;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import top.xinsin.pojo.Route;
import top.xinsin.pojo.Vo.RouteVO;
import top.xinsin.services.RouteService;
import top.xinsin.utils.RData;
import java.util.List;
/**
* @author xinsin
* Created On 2022/12/24 16:05
* @version 1.0
*/
@RestController
public class RouteController {
private final RouteService routeService;
@Autowired
public RouteController(RouteService routeService) {
this.routeService = routeService;
}
@GetMapping("/user/sys/getRoutes")
public RData<JSONObject> getRoutes(@RequestParam("page")Integer page, @RequestParam("pageNum")Integer pageNum){return routeService.getRoutes(page,pageNum);}
@PostMapping("/user/sys/addRoute")
public RData<String> addRoute(@RequestBody Route route){return routeService.addRoute(route);}
@GetMapping("/user/sys/updateStatus")
public RData<String> updateStatus(@RequestParam("id")Integer id,@RequestParam("status")Integer status){return routeService.updateStatus(id,status);}
@PostMapping("/user/sys/updateRoute")
public RData<String> updateRoute(@RequestBody Route route){return routeService.updateRoute(route);}
@GetMapping("/user/api/getRouter")
public RData<List<RouteVO>> getRouter(@RequestParam("auth")String auth){return routeService.getRouter(auth);}
}

View File

@ -0,0 +1,146 @@
package top.xinsin.controller;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.SneakyThrows;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import top.xinsin.pojo.User;
import top.xinsin.services.UserService;
import top.xinsin.utils.IpUtils;
import top.xinsin.utils.RData;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* @author xinxin
* Created On 2021/12/11 18:06
* @version 1.0
*/
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService){
this.userService = userService;
}
/**
* 登录接口
* @param user 用户
* @param response 请求体
* @return 是否可以登陆
*/
@PostMapping("/user/api/login")
public RData<JSONObject> login(@RequestBody User user, HttpServletResponse response) {
return userService.canLogin(user,response);
}
/**
* 添加用户接口
* @param user 需要添加的用户
* @return 受影响行数
*/
@PostMapping("/user/api/adduser")
public RData<JSONObject> addUser(@RequestBody User user) {return userService.addUser(user);}
/**
* @param user 需要修改密码的用户此用户的password字段为需要为修改后的密码
* @return 受影响行数
*/
@PostMapping("/user/api/changepassword")
public RData<JSONObject> changePassword(@RequestBody User user, HttpServletRequest request) {
return userService.changePassword(user, request);
}
/**
* 设置用户头像接口
*
* @param user 需要修改头像的用户此用户的head
* @return 受影响行数
*/
@PostMapping("/user/api/setHeadPortrait")
public RData<JSONObject> setHeadPortrait(@RequestBody User user, HttpServletRequest request) {
return userService.setHeadPortrait(user, request);
}
/**
* 获取用户头像接口
* @param username 用户名
* @return 用户头像的base64
*/
@GetMapping("/user/api/getUserHeadPortrait/{username}")
public String getUserHeadPortrait(@PathVariable String username){
return userService.getUserHeadPortrait(username).toString();
}
/**
* 获取所有在线用户接口
*
* @return 在线用户
*/
@GetMapping("/user/api/getOnlineUser")
public RData<List<User>> getOnlineUser() {
return userService.getOnlineUser();
}
/**
* 修改用户名
*
* @param user 新用户
* @param request 请求
* @return 是否成功
*/
@PostMapping("/user/api/changeUsername")
public RData<Boolean> changeUsername(@RequestBody User user, HttpServletRequest request, HttpServletResponse response) {
return userService.changeUsername(user, request, response);
}
@GetMapping("/user/api/setUserExclusiveColor")
public RData<Boolean> setUserExclusiveColor(@RequestHeader("color") String color,@RequestHeader("username")String username){return userService.setColorById(color,username);}
@GetMapping("/user/api/autoLogin")
public RData<JSONObject> autoLogin(HttpServletRequest request){return userService.autoLogin(request);}
@Value("${map-key}")
private String key;
@SneakyThrows
@GetMapping("/user/api/getWeather")
public RData<JSONObject> getWeather(HttpServletRequest httpServletRequest){
String ipAddr = IpUtils.getIpAddr(httpServletRequest);
// 检测为本地ip则进行默认地点查询
if ("127.0.0.1".equals(ipAddr)){
HttpClient httpClient = new HttpClient();
httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(15000);
GetMethod getMethod = new GetMethod("https://restapi.amap.com/v3/ip?key=" + key);
httpClient.executeMethod(getMethod);
String responseBodyAsString = getMethod.getResponseBodyAsString();
JSONObject jsonObject = JSON.parseObject(responseBodyAsString);
getMethod.releaseConnection();
GetMethod getWeather = new GetMethod("https://restapi.amap.com/v3/weather/weatherInfo?key=" + key + "&city="+jsonObject.getString("adcode")+"&awextensions=base");
httpClient.executeMethod(getWeather);
String getWeatherResult = getWeather.getResponseBodyAsString();
getWeather.releaseConnection();
return RData.success(JSON.parseObject(getWeatherResult));
}else{
HttpClient httpClient = new HttpClient();
httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(15000);
GetMethod getMethod = new GetMethod("https://restapi.amap.com/v3/ip?key=" + key + "&ip=" + ipAddr);
httpClient.executeMethod(getMethod);
String result = getMethod.getResponseBodyAsString();
getMethod.releaseConnection();
String adcode = JSON.parseObject(result).getString("adcode");
if (adcode != null){
GetMethod getWeather = new GetMethod("https://restapi.amap.com/v3/weather/weatherInfo?key=" + key + "&city=" + adcode + "&extensions=base");
httpClient.executeMethod(getWeather);
String getWeatherResult = getWeather.getResponseBodyAsString();
getWeather.releaseConnection();
return RData.success(JSON.parseObject(getWeatherResult));
}else{
return RData.success(new JSONObject().fluentPut("status","未查询到相关天气信息"));
}
}
}
}

View File

@ -0,0 +1,27 @@
package top.xinsin.dao;
import org.apache.ibatis.annotations.Mapper;
import top.xinsin.pojo.Route;
import top.xinsin.pojo.Vo.RouteVO;
import java.util.List;
/**
* @author xinsin
* Created On 2022/12/24 16:05
* @version 1.0
*/
@Mapper
public interface RouteMapper {
List<Route> selectRoute(Integer page, Integer pageNum);
Integer selectCount();
Integer insert(Route route);
Integer updateStatusById(Integer id, Integer status);
Integer updateRouteById(Route route);
List<RouteVO> SelectRouteByAuth(String auth);
}

View File

@ -0,0 +1,78 @@
package top.xinsin.dao;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import top.xinsin.pojo.User;
import java.util.ArrayList;
/**
* @author wzp
* Created On 2022/5/14
* @version 1.0
*/
@Repository
public interface UserMapper {
/**
* Check can user login
* @param user user need to log in
* @return if can ,return this user else return null
*/
User canLogin(User user);
/**
* add a new user to db
* @param user user need to add
* @return 影响行数
*/
int addUser(User user);
/**
* changeUser's password
* @param user user with new password
* @return 影响行数
*/
int changePassword(User user);
/**
* set a user's status to online
* @param user user need to online
* @return 影响行数
*/
int setOnline(User user);
/**
* set a user's status to offline
* @param user user need to offline
* @return 影响行数
*/
int setOffline(User user);
/**
* set a user's headingImg
*
* @param user user with new base64
*/
void setHeadPortrait(User user);
/**
* get users online equals 1
* @return all online equals 1 user
*/
ArrayList<User> getOnlineUser();
/**
* get a user's headingImg
*
* @param username the name of this user
* @return base64
*/
String getUserHeadPortrait(@Param("username") String username);
void changeUsername(String username, String old_username);
void changeUsernameUpdateMessage(String username, String old_username);
void setColorById(String exclusiveColor,String username);
User selectALLBYIdAndUsername(String username,Integer id);
}

View File

@ -0,0 +1,56 @@
package top.xinsin.interceptor;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.HandlerInterceptor;
import top.xinsin.utils.JwtTokenUtils;
import top.xinsin.utils.ResponseData;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author xinxin
* Created On 2021/12/12 19:17
* @version 1.0
*/
@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {
public static final String OPTIONS = "OPTIONS";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (OPTIONS.equals(request.getMethod())) {
return true;
}
// 从http请求头中取出token
final String token = request.getHeader("Access-Token");
//解决跨域问题
response.setHeader("Access-Control-Allow-Origin","*");
//解决跨域问题
response.setHeader("Access-Control-Allow-Headers","*");
ResponseData responseData;
try {
JwtTokenUtils.verify(token);
return true;
} catch (SignatureVerificationException e) {
log.info("用户验证了无效签名");
responseData = new ResponseData("无效签名", HttpStatus.UNAUTHORIZED);
}catch (TokenExpiredException e){
log.info("用户验证的签名已过期");
responseData = new ResponseData("签名已过期", HttpStatus.UNAUTHORIZED);
}catch (AlgorithmMismatchException e){
log.info("用户验证的token算法不一致");
responseData = new ResponseData("token算法不一致", HttpStatus.UNAUTHORIZED);
}catch (Exception e){
log.info("token无效或者是空的");
responseData = new ResponseData("token无效", HttpStatus.UNAUTHORIZED);
}
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(responseData.toString());
return false;
}
}

View File

@ -0,0 +1,18 @@
package top.xinsin.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import top.xinsin.enums.Auth;
/**
* @author xinxin
* Created On 2022/1/16 21:46
* @version 1.0
*/
@Data
@AllArgsConstructor
public class Online {
private String username;
private Auth auth;
private String headPortrait;
}

View File

@ -0,0 +1,25 @@
package top.xinsin.pojo;
import lombok.Data;
/**
* @author xinsin
* Created On 2022/12/24 16:03
* @version 1.0
*/
@Data
public class Route {
private Integer id;
private String path;
private String name;
private String parentId;
private String component;
private String title;
private String auth;
private Integer status;
private String remark;
private String createId;
private String createTime;
private String updateId;
private String updateTime;
}

View File

@ -0,0 +1,39 @@
package top.xinsin.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import top.xinsin.enums.Auth;
/**
* @author wzp
* Created On 2021/12/11 20:09
* @version 1.0
*/
@Data
@Accessors(chain = true)
@NoArgsConstructor
public class User {
private int id;
private String username;
private String password;
private Auth auth = Auth.user;
private int online;
private String lastLogin;
private String exclusiveColor;
private String base64;
public User(String username){
this.username = username;
}
public User(String username,String password){
this.username = username;
this.password = password;
}
public User(String username,String password,String auth){
this.username = username;
this.password = password;
this.auth = Auth.valueOf(auth);
}
}

View File

@ -0,0 +1,18 @@
package top.xinsin.pojo.Vo;
import lombok.Data;
/**
* @author xinsin
* Created On 2022/12/24 16:26
* @version 1.0
*/
@Data
public class RouteVO {
private Integer id;
private String path;
private String name;
private String parentId;
private String component;
private String title;
}

View File

@ -0,0 +1,52 @@
package top.xinsin.services;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import top.xinsin.dao.RouteMapper;
import top.xinsin.pojo.Route;
import top.xinsin.pojo.Vo.RouteVO;
import top.xinsin.utils.RData;
import java.util.List;
/**
* @author xinsin
* Created On 2022/12/24 16:05
* @version 1.0
*/
@Service
@Slf4j
public class RouteService {
private final RouteMapper routeMapper;
@Autowired
public RouteService(RouteMapper routeMapper) {
this.routeMapper = routeMapper;
}
public RData<JSONObject> getRoutes(Integer page, Integer pageNum) {
List<Route> routes = routeMapper.selectRoute(page,pageNum);
Integer count = routeMapper.selectCount();
return null;
}
public RData<String> addRoute(Route route) {
Integer num = routeMapper.insert(route);
return null;
}
public RData<String> updateStatus(Integer id, Integer status) {
Integer num = routeMapper.updateStatusById(id,status);
return null;
}
public RData<String> updateRoute(Route route) {
Integer num = routeMapper.updateRouteById(route);
return null;
}
public RData<List<RouteVO>> getRouter(String auth) {
List<RouteVO> routeVOS = routeMapper.SelectRouteByAuth(auth);
return null;
}
}

View File

@ -0,0 +1,154 @@
package top.xinsin.services;
import com.alibaba.fastjson2.JSONObject;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import top.xinsin.dao.UserMapper;
import top.xinsin.enums.HttpCodes;
import top.xinsin.pojo.User;
import top.xinsin.utils.JwtTokenUtils;
import top.xinsin.utils.RData;
import top.xinsin.utils.SqlUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author wzp
* Created On 2021/12/11 21:21
* @version 1.0
*/
@Service
@Slf4j
public class UserService {
private final UserMapper userMapper;
@Autowired
public UserService(UserMapper userMapper){
this.userMapper = userMapper;
}
public RData<JSONObject> canLogin(User user, HttpServletResponse response){
log.info("canLogin args:user=" + user);
JSONObject jsonObject = new JSONObject();
String password = user.getPassword();
String sha512Hex = DigestUtils.sha512Hex(password);
user.setPassword(sha512Hex);
User user1 = userMapper.canLogin(user);
if (user1 != null){
Map<String,String> payload = new HashMap<>(10);
payload.put("id",String.valueOf(user1.getId()));
payload.put("username",user1.getUsername());
String token = JwtTokenUtils.getToken(payload);
response.setHeader("token", token);
response.setHeader("Access-Control-Expose-Headers", "token");
jsonObject.fluentPut("canLogin", true)
.fluentPut("base64", user1.getBase64())
.fluentPut("auth",user1.getAuth().toString())
.fluentPut("exclusiveColor",user1.getExclusiveColor())
.fluentPut("username",user1.getUsername())
.fluentPut("id",user1.getId());
} else {
jsonObject.fluentPut("canLogin", false);
return RData.failed(HttpCodes.HTTP_CODES501, jsonObject);
}
return RData.success(jsonObject);
}
public RData<JSONObject> addUser(User user) {
log.info("addUser args:user=" + user);
user.setPassword(DigestUtils.sha512Hex(user.getPassword()));
JSONObject jsonObject = SqlUtils.insertOperate(userMapper.addUser(user));
return RData.success(jsonObject);
}
public RData<JSONObject> changePassword(User user, HttpServletRequest request) {
String token = request.getHeader("Access-Token");
JSONObject jsonObject = new JSONObject();
jsonObject.fluentPut("is_success", false);
if (JwtTokenUtils.isUser(token, user.getUsername())) {
String s = DigestUtils.sha512Hex(user.getPassword());
user.setPassword(s);
jsonObject = SqlUtils.updateOperate(userMapper.changePassword(user));
jsonObject.fluentPut("is_success", true);
return RData.success(jsonObject);
}
return RData.failed(HttpCodes.INVALID_TOKEN, jsonObject);
}
public void setOnline(User user) {
SqlUtils.updateOperate(userMapper.setOnline(user));
user.setOnline(1);
}
public void setOffline(User user) {
SqlUtils.updateOperate(userMapper.setOffline(user));
user.setOnline(0);
}
public RData<JSONObject> setHeadPortrait(User user, HttpServletRequest request) {
// 因为传入两个string所以password即为base64
String token = request.getHeader("Access-Token");
JSONObject jsonObject = new JSONObject();
if (JwtTokenUtils.isUser(token, user.getUsername())) {
userMapper.setHeadPortrait(user);
jsonObject.fluentPut("is_success", true);
return RData.success(jsonObject);
}else{
jsonObject.fluentPut("is_success", false);
return RData.failed(HttpCodes.INVALID_TOKEN, jsonObject);
}
}
public RData<String> getUserHeadPortrait(String name) {
String b64 = userMapper.getUserHeadPortrait(name);
return RData.success(b64);
}
public RData<List<User>> getOnlineUser() {
return RData.success(userMapper.getOnlineUser());
}
public RData<Boolean> changeUsername(User user, HttpServletRequest request, HttpServletResponse httpServletResponse) {
String token = request.getHeader("Access-Token");
DecodedJWT tokenInfo = JwtTokenUtils.getTokenInfo(token);
String username = tokenInfo.getClaim("username").asString();
String id = tokenInfo.getClaim("id").asString();
String newUsername = user.getUsername();
userMapper.changeUsername(newUsername, username);
userMapper.changeUsernameUpdateMessage(newUsername, username);
Map<String, String> payload = new HashMap<>();
payload.put("id", id);
payload.put("username", newUsername);
httpServletResponse.addHeader("token", JwtTokenUtils.getToken(payload));
return RData.success(true);
}
public RData<Boolean> setColorById(String exclusiveColor,String username){
userMapper.setColorById(exclusiveColor,username);
return RData.success(true);
}
public RData<JSONObject> autoLogin(HttpServletRequest request) {
String token = request.getHeader("Access-Token");
DecodedJWT tokenInfo = JwtTokenUtils.getTokenInfo(token);
String username = tokenInfo.getClaim("username").asString();
String id = tokenInfo.getClaim("id").asString();
User user = userMapper.selectALLBYIdAndUsername(username, Integer.parseInt(id));
JSONObject jsonObject = new JSONObject();
if (user != null){
jsonObject.fluentPut("status", true)
.fluentPut("base64", user.getBase64())
.fluentPut("auth",user.getAuth().toString())
.fluentPut("exclusiveColor",user.getExclusiveColor())
.fluentPut("username",user.getUsername())
.fluentPut("id",user.getId());
}else{
jsonObject.fluentPut("status",false);
}
return RData.success(jsonObject);
}
}

View File

@ -0,0 +1,43 @@
package top.xinsin.tasks;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import top.xinsin.pojo.User;
import top.xinsin.services.UserService;
import java.util.Date;
import static top.xinsin.controller.CheckController.checklist;
/**
* @author wzp
* Created On 2021/12/12 11:03
* @version 1.0
*/
@Configuration
@EnableScheduling
@Slf4j
public class CheckTask {
private final UserService userService;
@Autowired
public CheckTask(UserService userService) {
this.userService = userService;
}
@Scheduled(fixedRate=1000)
public void checkTasks(){
Date now = new Date();
for (User user : checklist.keySet()) {
Date time = checklist.get(user);
if(time.getTime() + 1000*60*60*2 <= now.getTime()){
userService.setOffline(user);
checklist.remove(user);
log.info("CheckList remove user=" + user);
}
log.info("CheckTask user=" + user);
}
}
}

View File

@ -0,0 +1,34 @@
server:
port: 8003
spring:
datasource:
username: witstalk
password: witsTalk
url: jdbc:mysql://wzpmc.cn:3306/witstalk?serverTimezone=UTC-8
driver-class-name: com.mysql.cj.jdbc.Driver
# druid配置
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters去掉后监控界面sql无法统计'wall'用于防火墙
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mvc:
static-path-pattern: /static/**
mybatis:
mapper-locations: classpath:top/xinsin/dao/*Mapping.xml
type-aliases-package: top.xinsin.dao
configuration:
map-underscore-to-camel-case: true
map-key: 3d665cbd98ac252863b898625450e2a3

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.xinsin.dao.RouteMapper">
<insert id="insert">
insert into route (path, name, parentId, component, title, auth, remark, createId, createTime)
values (#{path},#{name},#{parentId},#{component},#{title},#{auth},#{remark},#{createId},now());
</insert>
<update id="updateStatusById">
update route
set status = #{status}
where id = #{id};
</update>
<update id="updateRouteById">
update route
set path = #{path},name=#{name},parentId=#{parentId},component=#{component},title=#{title},auth=#{auth},remark=#{remark},
updateId=#{updateId},updateTime=now()
where id=#{id}
</update>
<select id="selectRoute" resultType="top.xinsin.pojo.Route">
select * from route r limit #{page},#{pageNum};
</select>
<select id="selectCount" resultType="java.lang.Integer">
select count(*) from route;
</select>
<select id="SelectRouteByAuth" resultType="top.xinsin.pojo.Vo.RouteVO">
select r.id,r.name,r.parentId,r.path,r.component,r.title from route r where auth = #{auth};
</select>
</mapper>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.xinsin.dao.UserMapper">
<insert id="addUser" parameterType="top.xinsin.pojo.User">
insert into user set username = #{username}, password = #{password}, auth = #{auth},base64= #{base64};
</insert>
<update id="changePassword" parameterType="top.xinsin.pojo.User">
update user set password = #{password} where BINARY username = #{username};
</update>
<update id="setOnline" parameterType="top.xinsin.pojo.User">
update user set online = 1 where BINARY username = #{username} and password=#{password};
</update>
<update id="setOffline" parameterType="top.xinsin.pojo.User">
update user
set online = 0
where BINARY username = #{username}
and password = #{password};
</update>
<select id="canLogin" parameterType="top.xinsin.pojo.User" resultType="top.xinsin.pojo.User">
select *
from user
where BINARY username = #{username}
and password = #{password}
and ban = 0;
</select>
<update id="setHeadPortrait" parameterType="top.xinsin.pojo.User">
update user
set base64 = #{password}
where BINARY username = #{username};
</update>
<update id="changeUsername" parameterType="top.xinsin.pojo.User">
update user
set username = #{username}
where BINARY username = #{old_username};
</update>
<update id="changeUsernameUpdateMessage">
update message
set sender = #{username}
where BINARY sender = #{old_username};
</update>
<select id="getUserHeadPortrait" resultType="java.lang.String">
select `base64`
from user
where BINARY `username` = #{username};
</select>
<select id="getOnlineUser" resultType="top.xinsin.pojo.User">
SELECT last_login
from user
WHERE `online` = 1;
</select>
<select id="selectALLBYIdAndUsername" resultType="top.xinsin.pojo.User">
select id, username, auth, base64, exclusiveColor
from user where username = #{username} and id = #{id} and ban = 0;
</select>
<update id="setColorById">
update user
set exclusiveColor = #{exclusiveColor}
where BINARY username = #{username};
</update>
</mapper>

View File

@ -0,0 +1,18 @@
package top.xinsin;
import org.junit.jupiter.api.Test;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author xinsin
* Created On 2022/12/5 13:39
* @version 1.0
*/
public class TestIp {
@Test
public void testIp01() throws UnknownHostException {
System.out.println(InetAddress.getLocalHost().getHostAddress());
}
}

View File

@ -0,0 +1,15 @@
package top.xinsin;
import org.apache.commons.codec.cli.Digest;
import org.apache.commons.codec.digest.DigestUtils;
import org.junit.jupiter.api.Test;
public class TestVerify {
@Test
public void test(){
String username = "DongYifeng";
String username1 = "DongYiFeng";
System.out.println(DigestUtils.sha512Hex(DigestUtils.md5Hex(username)).equals(DigestUtils.sha512Hex(DigestUtils.md5Hex(username1))));
System.out.println(DigestUtils.sha512Hex(DigestUtils.md5Hex("13630594060cloua")));
}
}

31
lib-utils/pom.xml Normal file
View File

@ -0,0 +1,31 @@
<?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">
<parent>
<artifactId>witsTalk</artifactId>
<groupId>top.xinsin</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lib-utils</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--spring-boot web模块-->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,17 @@
package top.xinsin.enums;
/**
* @author wzp
* Created On 2021/12/11 20:33
* @version 1.0
*/
public enum Auth {
/**
* 管理员
*/
admin,
/**
* 普通用户
*/
user
}

View File

@ -0,0 +1,57 @@
package top.xinsin.enums;
/**
* @author xinsin
* Created On 2022/5/14
* @version 1.0
*/
public enum HttpCodes {
/**
* 200
* 成功执行
*/
HTTP_CODES200(200,"接口一不小心执行成功啦!"),
/**
* 500
* 服务器错误
*/
HTTP_CODES500(500,"哎呀,错误了请节哀!"),
/**
* 401
* 数据错误
*/
HTTP_CODES401(401,"你这请求是坏的,你不会写吗?"),
/**
* 501
* 账号密码错误
*/
HTTP_CODES501(501,"帐号或密码错误!"),
/**
* 250
* token验证错误
*/
INVALID_TOKEN(250,"令牌验证错误"),
/**
* 251
* 无权限访问
*/
ACCESS_DENIED( 251,"你没有权限访问");
private final int code;
private final String message;
HttpCodes(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}

View File

@ -0,0 +1,16 @@
package top.xinsin.enums;
/**
* @author wzp
* Created On 2021/12/11 20:33
* @version 1.0
*/
public enum MessageTypes {
/**
* 文本消息
*/
text,
/**
* 图片消息
*/
img
}

View File

@ -0,0 +1,11 @@
package top.xinsin.interfaces;
import io.netty.channel.ChannelHandlerContext;
public interface NettyCommandHandler {
void handlerClose(ChannelHandlerContext channelHandlerContext);
void handlerJoin(ChannelHandlerContext channelHandlerContext);
default void handlerError(ChannelHandlerContext channelHandlerContext, Throwable cause){
cause.printStackTrace();
}
}

View File

@ -0,0 +1,42 @@
package top.xinsin.utils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* @author wzp
* Created On 2022/1/2 11:32
* @version 1.0
*/
public class FileUtils {
private static final char[] HEX_CODE = "0123456789ABCDEF".toCharArray();
/**
* 计算文件 MD5
* @return 返回文件的md5字符串如果计算过程中任务的状态变为取消或暂停返回null 如果有其他异常返回空字符串
*/
public static String calcMd5(File file) throws IOException, NoSuchAlgorithmException {
try (InputStream stream = Files.newInputStream(file.toPath(), StandardOpenOption.READ)) {
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] buf = new byte[8192];
int len;
while ((len = stream.read(buf)) > 0) {
digest.update(buf, 0, len);
}
return toHexString(digest.digest());
}
}
public static String toHexString(byte[] data) {
StringBuilder r = new StringBuilder(data.length * 2);
for (byte b : data) {
r.append(HEX_CODE[(b >> 4) & 0xF]);
r.append(HEX_CODE[(b & 0xF)]);
}
return r.toString();
}
}

View File

@ -0,0 +1,49 @@
package top.xinsin.utils;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author xinsin
* Created On 2022/12/5 13:37
* @version 1.0
*/
public class IpUtils {
public static String getIpAddr(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = request.getHeader("X-Forwarded-For");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1")) {
// 根据网卡取本机配置的IP
try {
ipAddress = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
// 通过多个代理的情况第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null) {
if (ipAddress.contains(",")) {
return ipAddress.split(",")[0];
} else {
return ipAddress;
}
} else {
return "";
}
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
}

View File

@ -0,0 +1,80 @@
package top.xinsin.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
/**
* @author xinxin
* Created On 2021/12/12 20:42
* @version 1.0
*/
public class JwtTokenUtils {
private static final String KEY = "G%k7H4lK;D1@L87Kio8j^ns56lJ68";
/**
* 创建token
* @param map 字段
* @return token
*/
public static String getToken(Map<String,String> map){
Calendar instance = Calendar.getInstance();
//默认两个小时过期
instance.add(Calendar.HOUR,2);
//创建jwt builder
JWTCreator.Builder builder = JWT.create();
//payload
map.forEach(builder::withClaim);
//指定令牌过期时间
return builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC512(KEY));
}
/**
* 验证token是否合法
* @param token token
*/
public static void verify(String token){
JWT.require(Algorithm.HMAC512(KEY)).build().verify(token);
}
/**
* 验证token是否合法
* @param token token
* @return 是否为合法token
*/
public static Boolean isRight(String token){
try{
verify(token);
return true;
}catch (Exception e){
return false;
}
}
/**
* 从token中提取信息
*
* @param token token
* @return token中包含的信息
*/
public static DecodedJWT getTokenInfo(String token) {
return JWT.require(Algorithm.HMAC512(KEY)).build().verify(token);
}
/**
* 判断token与用户名是否对应
*
* @param token token
* @param username 用户名
* @return 是否对应
*/
public static boolean isUser(String token, String username) {
String tokenUsername = getTokenInfo(token).getClaim("username").asString();
return tokenUsername.equals(username);
}
}

View File

@ -0,0 +1,108 @@
package top.xinsin.utils;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
import top.xinsin.interfaces.NettyCommandHandler;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
@Component
@NoArgsConstructor
public class NettyUtils<T extends NettyCommandHandler> {
public static void handler(Class<? extends NettyCommandHandler> handler, NettyCommandHandler commandHandler,ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) {
String text = textWebSocketFrame.text();
JSONObject jsonObject = JSON.parseObject(text);
String command = jsonObject.getString("op");
char c = command.charAt(0);
char c1 = Character.toUpperCase(c);
String s = command.replaceFirst(String.valueOf(c), String.valueOf(c1));
jsonObject.remove("op");
List<Class<?>> types = new ArrayList<>();
List<Object> args = new ArrayList<>();
types.add(ChannelHandlerContext.class);
args.add(channelHandlerContext);
AtomicBoolean hasNull = new AtomicBoolean(false);
for (Map.Entry<String, Object> stringObjectEntry : jsonObject.entrySet()) {
Object value = stringObjectEntry.getValue();
if (value == null) {
hasNull.set(true);
} else {
types.add(value.getClass());
args.add(value);
}
}
if (hasNull.get()) {
return;
}
Method declaredMethod;
try {
declaredMethod = handler.getDeclaredMethod("handler" + s, types.toArray(new Class[0]));
} catch (NoSuchMethodException e) {
e.printStackTrace();
return;
}
try {
Object result = declaredMethod.invoke(commandHandler, args.toArray());
if (result != null) {
channelHandlerContext.writeAndFlush(new TextWebSocketFrame(new JSONObject().fluentPut("op", command).fluentPut("data", result).toJSONString()));
}
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
@SneakyThrows
public static void handlerJoin(Class<? extends NettyCommandHandler> handler, NettyCommandHandler commandHandler,ChannelHandlerContext channelHandlerContext) {
Method handlerJoin = handler.getDeclaredMethod("handlerJoin", ChannelHandlerContext.class);
handlerJoin.invoke(commandHandler, channelHandlerContext);
}
@SneakyThrows
public static void handlerClose(Class<? extends NettyCommandHandler> handler, NettyCommandHandler commandHandler, ChannelHandlerContext channelHandlerContext) {
Method handlerJoin = handler.getDeclaredMethod("handlerClose", ChannelHandlerContext.class);
handlerJoin.invoke(commandHandler, channelHandlerContext);
}
@SneakyThrows
public static void handlerError(Class<? extends NettyCommandHandler> handler, NettyCommandHandler commandHandler, ChannelHandlerContext channelHandlerContext, Throwable cause) {
Method handlerJoin = handler.getDeclaredMethod("handlerError", ChannelHandlerContext.class, Throwable.class);
handlerJoin.invoke(commandHandler, channelHandlerContext, cause);
}
private Class<T> handler;
private T obj;
@SneakyThrows
public NettyUtils(Class<T> handler){
this.handler = handler;
Constructor<T> constructor = handler.getConstructor();
this.obj = constructor.newInstance();
}
public NettyUtils(Class<T> handler, ConfigurableApplicationContext configurableApplicationContext){
this.handler = handler;
obj = configurableApplicationContext.getBean(handler);
}
public void handler(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) {
NettyUtils.handler(this.handler, obj, channelHandlerContext, textWebSocketFrame);
}
public void handlerJoin(ChannelHandlerContext channelHandlerContext) {
NettyUtils.handlerJoin(this.handler, obj,channelHandlerContext);
}
public void handlerClose(ChannelHandlerContext channelHandlerContext) {
NettyUtils.handlerClose(this.handler, obj, channelHandlerContext);
}
public void handlerError(ChannelHandlerContext channelHandlerContext, Throwable cause) {
NettyUtils.handlerError(this.handler, obj, channelHandlerContext, cause);
}
}

View File

@ -0,0 +1,36 @@
package top.xinsin.utils;
import lombok.Data;
import top.xinsin.enums.HttpCodes;
/**
* @author xinsin
* @version 1.0
* Created On 2022/5/14
* @param <T> 返回值类型
*/
@Data
public class RData<T> {
private int status;
private String message;
private T data;
private long timestamp;
public RData() {
this.timestamp = System.currentTimeMillis();
}
public static<T> RData<T> success(T data){
RData<T> rData = new RData<>();
rData.setStatus(HttpCodes.HTTP_CODES200.getCode());
rData.setMessage(HttpCodes.HTTP_CODES200.getMessage());
rData.setData(data);
return rData;
}
public static<T> RData<T> failed(HttpCodes httpCodes, T data){
RData<T> rData = new RData<>();
rData.setStatus(httpCodes.getCode());
rData.setMessage(httpCodes.getMessage());
rData.setData(data);
return rData;
}
}

View File

@ -0,0 +1,137 @@
package top.xinsin.utils;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import org.springframework.http.HttpStatus;
import java.util.List;
import java.util.Map;
/**
* @author wzp
* Created On 2021/12/16 20:39
* @version 1.0
*/
@Deprecated
@Data
public class ResponseData {
private JSONObject data;
private JSONArray array;
private String content;
private Integer num;
private HttpStatus status;
public ResponseData(JSONObject data){
this.data = data;
this.status = HttpStatus.OK;
}
public ResponseData(JSONArray data){
this.array = data;
this.status = HttpStatus.OK;
}
public ResponseData(Map<?,?> data){
this.data = JSON.parseObject(JSON.toJSONString(data));
this.status = HttpStatus.OK;
}
public ResponseData(List<?> data){
this.array = JSON.parseArray(JSON.toJSONString(data));
this.status = HttpStatus.OK;
}
public ResponseData(String data){
this.content = data;
this.status = HttpStatus.OK;
}
public ResponseData(Integer data){
this.num = data;
this.status = HttpStatus.OK;
}
public ResponseData(int data){
this.num = data;
this.status = HttpStatus.OK;
}
public ResponseData(Integer data,HttpStatus status) {
this.num = data;
this.status = status;
}
public ResponseData(int data,HttpStatus status){
this.num = data;
this.status = status;
}
public ResponseData(JSONObject data,HttpStatus status){
this.data = data;
this.status = status;
}
public ResponseData(JSONArray data,HttpStatus status){
this.array = data;
this.status = status;
}
public ResponseData(Map<?,?> data,HttpStatus status){
this.data = JSON.parseObject(JSON.toJSONString(data));
this.status = status;
}
public ResponseData(List<?> data,HttpStatus status){
this.array = JSON.parseArray(JSON.toJSONString(data));
this.status = status;
}
public ResponseData(String data,HttpStatus status){
this.content = data;
this.status = status;
}
public ResponseData(HttpStatus status){
this.data = null;
this.array = null;
this.content = null;
this.num = null;
this.status = status;
}
public ResponseData(Object data,HttpStatus status){
this.content = data.toString();
this.status = status;
}
public ResponseData(Object data){
this.content = data.toString();
this.status = HttpStatus.OK;
}
public ResponseData(){
this.data = null;
this.array = null;
this.content = null;
this.status = HttpStatus.OK;
}
@Override
public String toString() {
JSONObject jsonObject = new JSONObject();
jsonObject.fluentPut("status",this.status.value())
.fluentPut("msg",this.status.getReasonPhrase());
if(this.data != null) {
jsonObject.fluentPut("data", data);
}else if(this.array != null){
jsonObject.fluentPut("data",array);
}else if(this.content != null){
jsonObject.fluentPut("data",content);
}else if(this.num != null){
jsonObject.fluentPut("data",num);
}
return jsonObject.toJSONString();
}
}

View File

@ -0,0 +1,29 @@
package top.xinsin.utils;
import com.alibaba.fastjson2.JSONObject;
/**
* @author xinsin
* @version 1.0.0
* Created On 2022/5/14
*/
public class SqlUtils {
public static JSONObject insertOperate(int i){
JSONObject jsonObject = new JSONObject();
if (i >= 1){
jsonObject.fluentPut("affected_rows",i);
}else{
jsonObject.fluentPut("affected rows","error");
}
return jsonObject;
}
public static JSONObject updateOperate(int i){
JSONObject jsonObject = new JSONObject();
if (i >= 0){
jsonObject.fluentPut("affected_rows",i);
}else{
jsonObject.fluentPut("affected rows","error");
}
return jsonObject;
}
}

51
lib-voice/pom.xml Normal file
View File

@ -0,0 +1,51 @@
<?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">
<parent>
<artifactId>witsTalk</artifactId>
<groupId>top.xinsin</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lib-voice</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>top.xinsin</groupId>
<artifactId>lib-utils</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!--netty框架依赖-->
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.86.Final</version>
</dependency>
<!--spring-boot web模块-->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<!--<directory>..\target</directory>-->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<!--suppress MavenModelInspection -->
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,35 @@
package cn.wzpmc;
import cn.wzpmc.services.VoiceCommandHandler;
import io.netty.channel.*;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.springframework.stereotype.Component;
import top.xinsin.utils.NettyUtils;
/**
* @author wzp
* Created On 2022/5/14
* @version 1.0
*/
@Component
@ChannelHandler.Sharable
public class VoiceFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
private final NettyUtils<VoiceCommandHandler> commandHandler = new NettyUtils<>(VoiceCommandHandler.class);
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) {
commandHandler.handler(channelHandlerContext, textWebSocketFrame);
}
@Override
public void handlerAdded(ChannelHandlerContext channelHandlerContext){
commandHandler.handlerJoin(channelHandlerContext);
}
@Override
public void handlerRemoved(ChannelHandlerContext channelHandlerContext) {
commandHandler.handlerClose(channelHandlerContext);
}
@Override
public void exceptionCaught(ChannelHandlerContext channelHandlerContext,Throwable throwable) {
commandHandler.handlerError(channelHandlerContext, throwable);
}
}

View File

@ -0,0 +1,37 @@
package cn.wzpmc;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author wzp
* Created On 2022/5/14
* @version 1.0
*/
@Component
public class VoiceHandler extends ChannelInitializer<SocketChannel> {
private final VoiceFrameHandler voiceFrameHandler;
@Autowired
public VoiceHandler(VoiceFrameHandler voiceFrameHandler){
this.voiceFrameHandler = voiceFrameHandler;
}
@Override
protected void initChannel(SocketChannel socketChannel){
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new LoggingHandler(LogLevel.DEBUG))
.addLast(new HttpServerCodec())
.addLast(new ChunkedWriteHandler())
.addLast(new HttpObjectAggregator(16384))
.addLast(new WebSocketServerProtocolHandler("/voice"))
.addLast(voiceFrameHandler);
}
}

View File

@ -0,0 +1,62 @@
package cn.wzpmc;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
@Component
@Slf4j
public class VoiceNetty implements ApplicationListener<ContextClosedEvent> {
private final EventLoopGroup boss = new NioEventLoopGroup();
private final EventLoopGroup worker = new NioEventLoopGroup();
@Value("${server.port}")
private int port;
private Channel channel;
private final VoiceHandler voiceHandler;
@Autowired
public VoiceNetty(VoiceHandler voiceNetty){
this.voiceHandler = voiceNetty;
}
public void start(){
try{
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap
.group(boss,worker)
.handler(new LoggingHandler(LogLevel.DEBUG))
.childHandler(voiceHandler)
.channel(NioServerSocketChannel.class);
ChannelFuture channelFuture = bootstrap.bind(new InetSocketAddress(port)).sync();
if (channelFuture.isSuccess()){
log.info("Voice Server started on 0.0.0.0:{}",port);
}
this.channel = channelFuture.channel();
this.channel.closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
log.info("Shutdown Netty Server...");
if(channel != null) {
channel.close();
}
boss.shutdownGracefully();
worker.shutdownGracefully();
log.info("Shutdown Netty Server Success!");
}
}

View File

@ -0,0 +1,26 @@
package cn.wzpmc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author wzp
* Created On 2022/5/14
* @version 1.0
*/
@SpringBootApplication
public class VoiceStart {
private static VoiceNetty voiceNetty;
@Autowired
public VoiceStart(VoiceNetty voiceNetty){
VoiceStart.voiceNetty = voiceNetty;
}
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(VoiceStart.class);
springApplication.run(args);
springApplication.addListeners(voiceNetty);
voiceNetty.start();
}
}

View File

@ -0,0 +1,142 @@
package cn.wzpmc.services;
import com.alibaba.fastjson2.JSONObject;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import top.xinsin.interfaces.NettyCommandHandler;
import top.xinsin.utils.JwtTokenUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
@Slf4j
public class VoiceCommandHandler implements NettyCommandHandler {
/**
* 用于保存用户的ChannelId和username
* key ChannelId
* value username
*/
private final Map<ChannelId, String> usernames = new ConcurrentHashMap<>();
/**
* 用于保存用户的mediaId和username
* key username
* value mediaId
*/
private final Map<String, String> userMedias = new ConcurrentHashMap<>();
/**
* 用于保存ChannelId和Channel
* key ChannelId
* value Channel
*/
public final Map<ChannelId, Channel> users = new ConcurrentHashMap<>();
public JSONObject handlerLogin(ChannelHandlerContext channelHandlerContext, String token, String media){
Boolean right = JwtTokenUtils.isRight(token);
JSONObject jsonObject = new JSONObject();
jsonObject.fluentPut("success", right);
if (right) {
DecodedJWT tokenInfo = JwtTokenUtils.getTokenInfo(token);
Claim username = tokenInfo.getClaim("username");
String s = username.asString();
ChannelId id = channelHandlerContext.channel().id();
usernames.put(id, s);
userMedias.put(s, media);
JSONObject toAll = new JSONObject();
toAll.fluentPut("op","uadd").fluentPut("username", s).fluentPut("mediaId", media);
for (Map.Entry<ChannelId, Channel> channelIdChannelEntry : users.entrySet()) {
ChannelId key = channelIdChannelEntry.getKey();
Channel value = channelIdChannelEntry.getValue();
if (!id.equals(key)){
value.writeAndFlush(new TextWebSocketFrame(toAll.toJSONString()));
}
}
jsonObject.fluentPut("username", s);
HashMap<String, String> resultHashMap = new HashMap<>();
for (Map.Entry<String, String> stringStringEntry : userMedias.entrySet()) {
String key = stringStringEntry.getKey();
if (!s.equals(key)) {
resultHashMap.put(key, stringStringEntry.getValue());
}
}
jsonObject.fluentPut("data", resultHashMap);
}
return jsonObject;
}
public void handlerOffer(ChannelHandlerContext channelHandlerContext,JSONObject data,String username){
Channel channel = channelHandlerContext.channel();
ChannelId id = channel.id();
log.info("{} -> {} <=offer=> {}", usernames.get(id), username, data);
String from = usernames.get(id);
JSONObject send = new JSONObject().fluentPut("op", "offer").fluentPut("from", from).fluentPut("data", data);
sendTo(username, send);
}
public void handlerCandidate(ChannelHandlerContext channelHandlerContext,JSONObject data,String username){
Channel channel = channelHandlerContext.channel();
ChannelId id = channel.id();
log.info("{} -> {} <=candidate=> {}", usernames.get(id), username, data);
String from = usernames.get(id);
JSONObject send = new JSONObject().fluentPut("op", "candidate").fluentPut("from", from).fluentPut("data", data);
sendTo(username, send);
}
public void handlerAnswer(ChannelHandlerContext channelHandlerContext,JSONObject data,String username){
Channel channel = channelHandlerContext.channel();
ChannelId id = channel.id();
log.info("{} -> {} <=answer=> {}", usernames.get(id), username, data);
String from = usernames.get(id);
JSONObject send = new JSONObject().fluentPut("op", "answer").fluentPut("from", from).fluentPut("data", data);
sendTo(username, send);
}
public void handlerPing(ChannelHandlerContext channelHandlerContext){
Channel channel = channelHandlerContext.channel();
JSONObject jsonObject = new JSONObject();
jsonObject.fluentPut("op", "pong").fluentPut("data",new JSONObject().fluentPut("time", System.currentTimeMillis()));
channel.writeAndFlush(new TextWebSocketFrame(jsonObject.toJSONString()));
}
private void sendTo(String username,JSONObject send){
for (Map.Entry<ChannelId, String> entry : usernames.entrySet()) {
if (entry.getValue().equals(username)) {
Channel toChannel = users.get(entry.getKey());
toChannel.writeAndFlush(new TextWebSocketFrame(send.toJSONString()));
break;
}
}
}
@Override
public void handlerClose(ChannelHandlerContext channelHandlerContext) {
Channel channel = channelHandlerContext.channel();
ChannelId id = channel.id();
String username = usernames.get(id);
usernames.remove(id);
JSONObject leaveObject = new JSONObject();
leaveObject.fluentPut("op", "leave").fluentPut("mediaId", userMedias.get(username)).fluentPut("username", username);
for (Map.Entry<ChannelId, Channel> channelIdChannelEntry : users.entrySet()) {
channelIdChannelEntry.getValue().writeAndFlush(new TextWebSocketFrame(leaveObject.toJSONString()));
}
userMedias.remove(username);
users.remove(id);
log.info("User {} disconnected with channel {}", channel.remoteAddress(), id.asShortText());
}
@Override
public void handlerJoin(ChannelHandlerContext channelHandlerContext) {
Channel channel = channelHandlerContext.channel();
ChannelId id = channel.id();
users.put(id, channel);
log.info("User {} connected with channel {}", channel.remoteAddress(), id.asShortText());
}
public void handlerError(ChannelHandlerContext channelHandlerContext, Throwable cause){
Channel channel = channelHandlerContext.channel();
ChannelId id = channel.id();
channelHandlerContext.close();
users.remove(id);
log.info("User {} had an error with channel {}", channel.remoteAddress(), id.asShortText());
cause.printStackTrace();
}
}

View File

@ -0,0 +1,2 @@
server:
port: 8006

View File

@ -0,0 +1,26 @@
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;

View File

@ -0,0 +1,25 @@
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;

109
nginx-1.20.2/conf/koi-utf Normal file
View File

@ -0,0 +1,109 @@
# This map is not a full koi8-r <> utf8 map: it does not contain
# box-drawing and some other characters. Besides this map contains
# several koi8-u and Byelorussian letters which are not in koi8-r.
# If you need a full and standard map, use contrib/unicode2nginx/koi-utf
# map instead.
charset_map koi8-r utf-8 {
80 E282AC ; # euro
95 E280A2 ; # bullet
9A C2A0 ; # &nbsp;
9E C2B7 ; # &middot;
A3 D191 ; # small yo
A4 D194 ; # small Ukrainian ye
A6 D196 ; # small Ukrainian i
A7 D197 ; # small Ukrainian yi
AD D291 ; # small Ukrainian soft g
AE D19E ; # small Byelorussian short u
B0 C2B0 ; # &deg;
B3 D081 ; # capital YO
B4 D084 ; # capital Ukrainian YE
B6 D086 ; # capital Ukrainian I
B7 D087 ; # capital Ukrainian YI
B9 E28496 ; # numero sign
BD D290 ; # capital Ukrainian soft G
BE D18E ; # capital Byelorussian short U
BF C2A9 ; # (C)
C0 D18E ; # small yu
C1 D0B0 ; # small a
C2 D0B1 ; # small b
C3 D186 ; # small ts
C4 D0B4 ; # small d
C5 D0B5 ; # small ye
C6 D184 ; # small f
C7 D0B3 ; # small g
C8 D185 ; # small kh
C9 D0B8 ; # small i
CA D0B9 ; # small j
CB D0BA ; # small k
CC D0BB ; # small l
CD D0BC ; # small m
CE D0BD ; # small n
CF D0BE ; # small o
D0 D0BF ; # small p
D1 D18F ; # small ya
D2 D180 ; # small r
D3 D181 ; # small s
D4 D182 ; # small t
D5 D183 ; # small u
D6 D0B6 ; # small zh
D7 D0B2 ; # small v
D8 D18C ; # small soft sign
D9 D18B ; # small y
DA D0B7 ; # small z
DB D188 ; # small sh
DC D18D ; # small e
DD D189 ; # small shch
DE D187 ; # small ch
DF D18A ; # small hard sign
E0 D0AE ; # capital YU
E1 D090 ; # capital A
E2 D091 ; # capital B
E3 D0A6 ; # capital TS
E4 D094 ; # capital D
E5 D095 ; # capital YE
E6 D0A4 ; # capital F
E7 D093 ; # capital G
E8 D0A5 ; # capital KH
E9 D098 ; # capital I
EA D099 ; # capital J
EB D09A ; # capital K
EC D09B ; # capital L
ED D09C ; # capital M
EE D09D ; # capital N
EF D09E ; # capital O
F0 D09F ; # capital P
F1 D0AF ; # capital YA
F2 D0A0 ; # capital R
F3 D0A1 ; # capital S
F4 D0A2 ; # capital T
F5 D0A3 ; # capital U
F6 D096 ; # capital ZH
F7 D092 ; # capital V
F8 D0AC ; # capital soft sign
F9 D0AB ; # capital Y
FA D097 ; # capital Z
FB D0A8 ; # capital SH
FC D0AD ; # capital E
FD D0A9 ; # capital SHCH
FE D0A7 ; # capital CH
FF D0AA ; # capital hard sign
}

103
nginx-1.20.2/conf/koi-win Normal file
View File

@ -0,0 +1,103 @@
charset_map koi8-r windows-1251 {
80 88 ; # euro
95 95 ; # bullet
9A A0 ; # &nbsp;
9E B7 ; # &middot;
A3 B8 ; # small yo
A4 BA ; # small Ukrainian ye
A6 B3 ; # small Ukrainian i
A7 BF ; # small Ukrainian yi
AD B4 ; # small Ukrainian soft g
AE A2 ; # small Byelorussian short u
B0 B0 ; # &deg;
B3 A8 ; # capital YO
B4 AA ; # capital Ukrainian YE
B6 B2 ; # capital Ukrainian I
B7 AF ; # capital Ukrainian YI
B9 B9 ; # numero sign
BD A5 ; # capital Ukrainian soft G
BE A1 ; # capital Byelorussian short U
BF A9 ; # (C)
C0 FE ; # small yu
C1 E0 ; # small a
C2 E1 ; # small b
C3 F6 ; # small ts
C4 E4 ; # small d
C5 E5 ; # small ye
C6 F4 ; # small f
C7 E3 ; # small g
C8 F5 ; # small kh
C9 E8 ; # small i
CA E9 ; # small j
CB EA ; # small k
CC EB ; # small l
CD EC ; # small m
CE ED ; # small n
CF EE ; # small o
D0 EF ; # small p
D1 FF ; # small ya
D2 F0 ; # small r
D3 F1 ; # small s
D4 F2 ; # small t
D5 F3 ; # small u
D6 E6 ; # small zh
D7 E2 ; # small v
D8 FC ; # small soft sign
D9 FB ; # small y
DA E7 ; # small z
DB F8 ; # small sh
DC FD ; # small e
DD F9 ; # small shch
DE F7 ; # small ch
DF FA ; # small hard sign
E0 DE ; # capital YU
E1 C0 ; # capital A
E2 C1 ; # capital B
E3 D6 ; # capital TS
E4 C4 ; # capital D
E5 C5 ; # capital YE
E6 D4 ; # capital F
E7 C3 ; # capital G
E8 D5 ; # capital KH
E9 C8 ; # capital I
EA C9 ; # capital J
EB CA ; # capital K
EC CB ; # capital L
ED CC ; # capital M
EE CD ; # capital N
EF CE ; # capital O
F0 CF ; # capital P
F1 DF ; # capital YA
F2 D0 ; # capital R
F3 D1 ; # capital S
F4 D2 ; # capital T
F5 D3 ; # capital U
F6 C6 ; # capital ZH
F7 C2 ; # capital V
F8 DC ; # capital soft sign
F9 DB ; # capital Y
FA C7 ; # capital Z
FB D8 ; # capital SH
FC DD ; # capital E
FD D9 ; # capital SHCH
FE D7 ; # capital CH
FF DA ; # capital hard sign
}

View File

@ -0,0 +1,97 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation
pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document
docx;
application/vnd.wap.wmlc wmlc;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}

View File

@ -0,0 +1,83 @@
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#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 logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 8080;
server_name localhost;
charset utf-8;
# access_log logs/host.access.log main;
# 代理前端vue
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8081;
}
# 代理user服务器
location /user {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8003;
}
# 代理文件服务器
location /file {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8004;
}
# 代理chat服务器
location /chat {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8005;
proxy_connect_timeout 300s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
# 代理voice服务器
location /voice {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8006;
proxy_connect_timeout 300s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}

View File

@ -0,0 +1,17 @@
scgi_param REQUEST_METHOD $request_method;
scgi_param REQUEST_URI $request_uri;
scgi_param QUERY_STRING $query_string;
scgi_param CONTENT_TYPE $content_type;
scgi_param DOCUMENT_URI $document_uri;
scgi_param DOCUMENT_ROOT $document_root;
scgi_param SCGI 1;
scgi_param SERVER_PROTOCOL $server_protocol;
scgi_param REQUEST_SCHEME $scheme;
scgi_param HTTPS $https if_not_empty;
scgi_param REMOTE_ADDR $remote_addr;
scgi_param REMOTE_PORT $remote_port;
scgi_param SERVER_PORT $server_port;
scgi_param SERVER_NAME $server_name;

View File

@ -0,0 +1,17 @@
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;

126
nginx-1.20.2/conf/win-utf Normal file
View File

@ -0,0 +1,126 @@
# This map is not a full windows-1251 <> utf8 map: it does not
# contain Serbian and Macedonian letters. If you need a full map,
# use contrib/unicode2nginx/win-utf map instead.
charset_map windows-1251 utf-8 {
82 E2809A ; # single low-9 quotation mark
84 E2809E ; # double low-9 quotation mark
85 E280A6 ; # ellipsis
86 E280A0 ; # dagger
87 E280A1 ; # double dagger
88 E282AC ; # euro
89 E280B0 ; # per mille
91 E28098 ; # left single quotation mark
92 E28099 ; # right single quotation mark
93 E2809C ; # left double quotation mark
94 E2809D ; # right double quotation mark
95 E280A2 ; # bullet
96 E28093 ; # en dash
97 E28094 ; # em dash
99 E284A2 ; # trade mark sign
A0 C2A0 ; # &nbsp;
A1 D18E ; # capital Byelorussian short U
A2 D19E ; # small Byelorussian short u
A4 C2A4 ; # currency sign
A5 D290 ; # capital Ukrainian soft G
A6 C2A6 ; # borken bar
A7 C2A7 ; # section sign
A8 D081 ; # capital YO
A9 C2A9 ; # (C)
AA D084 ; # capital Ukrainian YE
AB C2AB ; # left-pointing double angle quotation mark
AC C2AC ; # not sign
AD C2AD ; # soft hypen
AE C2AE ; # (R)
AF D087 ; # capital Ukrainian YI
B0 C2B0 ; # &deg;
B1 C2B1 ; # plus-minus sign
B2 D086 ; # capital Ukrainian I
B3 D196 ; # small Ukrainian i
B4 D291 ; # small Ukrainian soft g
B5 C2B5 ; # micro sign
B6 C2B6 ; # pilcrow sign
B7 C2B7 ; # &middot;
B8 D191 ; # small yo
B9 E28496 ; # numero sign
BA D194 ; # small Ukrainian ye
BB C2BB ; # right-pointing double angle quotation mark
BF D197 ; # small Ukrainian yi
C0 D090 ; # capital A
C1 D091 ; # capital B
C2 D092 ; # capital V
C3 D093 ; # capital G
C4 D094 ; # capital D
C5 D095 ; # capital YE
C6 D096 ; # capital ZH
C7 D097 ; # capital Z
C8 D098 ; # capital I
C9 D099 ; # capital J
CA D09A ; # capital K
CB D09B ; # capital L
CC D09C ; # capital M
CD D09D ; # capital N
CE D09E ; # capital O
CF D09F ; # capital P
D0 D0A0 ; # capital R
D1 D0A1 ; # capital S
D2 D0A2 ; # capital T
D3 D0A3 ; # capital U
D4 D0A4 ; # capital F
D5 D0A5 ; # capital KH
D6 D0A6 ; # capital TS
D7 D0A7 ; # capital CH
D8 D0A8 ; # capital SH
D9 D0A9 ; # capital SHCH
DA D0AA ; # capital hard sign
DB D0AB ; # capital Y
DC D0AC ; # capital soft sign
DD D0AD ; # capital E
DE D0AE ; # capital YU
DF D0AF ; # capital YA
E0 D0B0 ; # small a
E1 D0B1 ; # small b
E2 D0B2 ; # small v
E3 D0B3 ; # small g
E4 D0B4 ; # small d
E5 D0B5 ; # small ye
E6 D0B6 ; # small zh
E7 D0B7 ; # small z
E8 D0B8 ; # small i
E9 D0B9 ; # small j
EA D0BA ; # small k
EB D0BB ; # small l
EC D0BC ; # small m
ED D0BD ; # small n
EE D0BE ; # small o
EF D0BF ; # small p
F0 D180 ; # small r
F1 D181 ; # small s
F2 D182 ; # small t
F3 D183 ; # small u
F4 D184 ; # small f
F5 D185 ; # small kh
F6 D186 ; # small ts
F7 D187 ; # small ch
F8 D188 ; # small sh
F9 D189 ; # small shch
FA D18A ; # small hard sign
FB D18B ; # small y
FC D18C ; # small soft sign
FD D18D ; # small e
FE D18E ; # small yu
FF D18F ; # small ya
}

View File

@ -0,0 +1,21 @@
geo2nginx.pl by Andrei Nigmatulin
The perl script to convert CSV geoip database ( free download
at http://www.maxmind.com/app/geoip_country ) to format, suitable
for use by the ngx_http_geo_module.
unicode2nginx by Maxim Dounin
The perl script to convert unicode mappings ( available
at http://www.unicode.org/Public/MAPPINGS/ ) to the nginx
configuration file format.
Two generated full maps for windows-1251 and koi8-r.
vim by Evan Miller
Syntax highlighting of nginx configuration for vim, to be
placed into ~/.vim/.

View File

@ -0,0 +1,58 @@
#!/usr/bin/perl -w
# (c) Andrei Nigmatulin, 2005
#
# this script provided "as is", without any warranties. use it at your own risk.
#
# special thanx to Andrew Sitnikov for perl port
#
# this script converts CSV geoip database (free download at http://www.maxmind.com/app/geoip_country)
# to format, suitable for use with nginx_http_geo module (http://sysoev.ru/nginx)
#
# for example, line with ip range
#
# "62.16.68.0","62.16.127.255","1041253376","1041268735","RU","Russian Federation"
#
# will be converted to four subnetworks:
#
# 62.16.68.0/22 RU;
# 62.16.72.0/21 RU;
# 62.16.80.0/20 RU;
# 62.16.96.0/19 RU;
use warnings;
use strict;
while( <STDIN> ){
if (/"[^"]+","[^"]+","([^"]+)","([^"]+)","([^"]+)"/){
print_subnets($1, $2, $3);
}
}
sub print_subnets {
my ($a1, $a2, $c) = @_;
my $l;
while ($a1 <= $a2) {
for ($l = 0; ($a1 & (1 << $l)) == 0 && ($a1 + ((1 << ($l + 1)) - 1)) <= $a2; $l++){};
print long2ip($a1) . "/" . (32 - $l) . " " . $c . ";\n";
$a1 += (1 << $l);
}
}
sub long2ip {
my $ip = shift;
my $str = 0;
$str = ($ip & 255);
$ip >>= 8;
$str = ($ip & 255).".$str";
$ip >>= 8;
$str = ($ip & 255).".$str";
$ip >>= 8;
$str = ($ip & 255).".$str";
}

View File

@ -0,0 +1,131 @@
charset_map koi8-r utf-8 {
80 E29480 ; # BOX DRAWINGS LIGHT HORIZONTAL
81 E29482 ; # BOX DRAWINGS LIGHT VERTICAL
82 E2948C ; # BOX DRAWINGS LIGHT DOWN AND RIGHT
83 E29490 ; # BOX DRAWINGS LIGHT DOWN AND LEFT
84 E29494 ; # BOX DRAWINGS LIGHT UP AND RIGHT
85 E29498 ; # BOX DRAWINGS LIGHT UP AND LEFT
86 E2949C ; # BOX DRAWINGS LIGHT VERTICAL AND RIGHT
87 E294A4 ; # BOX DRAWINGS LIGHT VERTICAL AND LEFT
88 E294AC ; # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
89 E294B4 ; # BOX DRAWINGS LIGHT UP AND HORIZONTAL
8A E294BC ; # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
8B E29680 ; # UPPER HALF BLOCK
8C E29684 ; # LOWER HALF BLOCK
8D E29688 ; # FULL BLOCK
8E E2968C ; # LEFT HALF BLOCK
8F E29690 ; # RIGHT HALF BLOCK
90 E29691 ; # LIGHT SHADE
91 E29692 ; # MEDIUM SHADE
92 E29693 ; # DARK SHADE
93 E28CA0 ; # TOP HALF INTEGRAL
94 E296A0 ; # BLACK SQUARE
95 E28899 ; # BULLET OPERATOR
96 E2889A ; # SQUARE ROOT
97 E28988 ; # ALMOST EQUAL TO
98 E289A4 ; # LESS-THAN OR EQUAL TO
99 E289A5 ; # GREATER-THAN OR EQUAL TO
9A C2A0 ; # NO-BREAK SPACE
9B E28CA1 ; # BOTTOM HALF INTEGRAL
9C C2B0 ; # DEGREE SIGN
9D C2B2 ; # SUPERSCRIPT TWO
9E C2B7 ; # MIDDLE DOT
9F C3B7 ; # DIVISION SIGN
A0 E29590 ; # BOX DRAWINGS DOUBLE HORIZONTAL
A1 E29591 ; # BOX DRAWINGS DOUBLE VERTICAL
A2 E29592 ; # BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
A3 D191 ; # CYRILLIC SMALL LETTER IO
A4 E29593 ; # BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
A5 E29594 ; # BOX DRAWINGS DOUBLE DOWN AND RIGHT
A6 E29595 ; # BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
A7 E29596 ; # BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
A8 E29597 ; # BOX DRAWINGS DOUBLE DOWN AND LEFT
A9 E29598 ; # BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
AA E29599 ; # BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
AB E2959A ; # BOX DRAWINGS DOUBLE UP AND RIGHT
AC E2959B ; # BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
AD E2959C ; # BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
AE E2959D ; # BOX DRAWINGS DOUBLE UP AND LEFT
AF E2959E ; # BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
B0 E2959F ; # BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
B1 E295A0 ; # BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
B2 E295A1 ; # BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
B3 D081 ; # CYRILLIC CAPITAL LETTER IO
B4 E295A2 ; # BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
B5 E295A3 ; # BOX DRAWINGS DOUBLE VERTICAL AND LEFT
B6 E295A4 ; # BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
B7 E295A5 ; # BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
B8 E295A6 ; # BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
B9 E295A7 ; # BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
BA E295A8 ; # BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
BB E295A9 ; # BOX DRAWINGS DOUBLE UP AND HORIZONTAL
BC E295AA ; # BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
BD E295AB ; # BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
BE E295AC ; # BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
BF C2A9 ; # COPYRIGHT SIGN
C0 D18E ; # CYRILLIC SMALL LETTER YU
C1 D0B0 ; # CYRILLIC SMALL LETTER A
C2 D0B1 ; # CYRILLIC SMALL LETTER BE
C3 D186 ; # CYRILLIC SMALL LETTER TSE
C4 D0B4 ; # CYRILLIC SMALL LETTER DE
C5 D0B5 ; # CYRILLIC SMALL LETTER IE
C6 D184 ; # CYRILLIC SMALL LETTER EF
C7 D0B3 ; # CYRILLIC SMALL LETTER GHE
C8 D185 ; # CYRILLIC SMALL LETTER HA
C9 D0B8 ; # CYRILLIC SMALL LETTER I
CA D0B9 ; # CYRILLIC SMALL LETTER SHORT I
CB D0BA ; # CYRILLIC SMALL LETTER KA
CC D0BB ; # CYRILLIC SMALL LETTER EL
CD D0BC ; # CYRILLIC SMALL LETTER EM
CE D0BD ; # CYRILLIC SMALL LETTER EN
CF D0BE ; # CYRILLIC SMALL LETTER O
D0 D0BF ; # CYRILLIC SMALL LETTER PE
D1 D18F ; # CYRILLIC SMALL LETTER YA
D2 D180 ; # CYRILLIC SMALL LETTER ER
D3 D181 ; # CYRILLIC SMALL LETTER ES
D4 D182 ; # CYRILLIC SMALL LETTER TE
D5 D183 ; # CYRILLIC SMALL LETTER U
D6 D0B6 ; # CYRILLIC SMALL LETTER ZHE
D7 D0B2 ; # CYRILLIC SMALL LETTER VE
D8 D18C ; # CYRILLIC SMALL LETTER SOFT SIGN
D9 D18B ; # CYRILLIC SMALL LETTER YERU
DA D0B7 ; # CYRILLIC SMALL LETTER ZE
DB D188 ; # CYRILLIC SMALL LETTER SHA
DC D18D ; # CYRILLIC SMALL LETTER E
DD D189 ; # CYRILLIC SMALL LETTER SHCHA
DE D187 ; # CYRILLIC SMALL LETTER CHE
DF D18A ; # CYRILLIC SMALL LETTER HARD SIGN
E0 D0AE ; # CYRILLIC CAPITAL LETTER YU
E1 D090 ; # CYRILLIC CAPITAL LETTER A
E2 D091 ; # CYRILLIC CAPITAL LETTER BE
E3 D0A6 ; # CYRILLIC CAPITAL LETTER TSE
E4 D094 ; # CYRILLIC CAPITAL LETTER DE
E5 D095 ; # CYRILLIC CAPITAL LETTER IE
E6 D0A4 ; # CYRILLIC CAPITAL LETTER EF
E7 D093 ; # CYRILLIC CAPITAL LETTER GHE
E8 D0A5 ; # CYRILLIC CAPITAL LETTER HA
E9 D098 ; # CYRILLIC CAPITAL LETTER I
EA D099 ; # CYRILLIC CAPITAL LETTER SHORT I
EB D09A ; # CYRILLIC CAPITAL LETTER KA
EC D09B ; # CYRILLIC CAPITAL LETTER EL
ED D09C ; # CYRILLIC CAPITAL LETTER EM
EE D09D ; # CYRILLIC CAPITAL LETTER EN
EF D09E ; # CYRILLIC CAPITAL LETTER O
F0 D09F ; # CYRILLIC CAPITAL LETTER PE
F1 D0AF ; # CYRILLIC CAPITAL LETTER YA
F2 D0A0 ; # CYRILLIC CAPITAL LETTER ER
F3 D0A1 ; # CYRILLIC CAPITAL LETTER ES
F4 D0A2 ; # CYRILLIC CAPITAL LETTER TE
F5 D0A3 ; # CYRILLIC CAPITAL LETTER U
F6 D096 ; # CYRILLIC CAPITAL LETTER ZHE
F7 D092 ; # CYRILLIC CAPITAL LETTER VE
F8 D0AC ; # CYRILLIC CAPITAL LETTER SOFT SIGN
F9 D0AB ; # CYRILLIC CAPITAL LETTER YERU
FA D097 ; # CYRILLIC CAPITAL LETTER ZE
FB D0A8 ; # CYRILLIC CAPITAL LETTER SHA
FC D0AD ; # CYRILLIC CAPITAL LETTER E
FD D0A9 ; # CYRILLIC CAPITAL LETTER SHCHA
FE D0A7 ; # CYRILLIC CAPITAL LETTER CHE
FF D0AA ; # CYRILLIC CAPITAL LETTER HARD SIGN
}

View File

@ -0,0 +1,48 @@
#!/usr/bin/perl -w
# Convert unicode mappings to nginx configuration file format.
# You may find useful mappings in various places, including
# unicode.org official site:
#
# http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1251.TXT
# http://www.unicode.org/Public/MAPPINGS/VENDORS/MISC/KOI8-R.TXT
# Needs perl 5.6 or later.
# Written by Maxim Dounin, mdounin@mdounin.ru
###############################################################################
require 5.006;
while (<>) {
# Skip comments and empty lines
next if /^#/;
next if /^\s*$/;
chomp;
# Convert mappings
if (/^\s*0x(..)\s*0x(....)\s*(#.*)/) {
# Mapping <from-code> <unicode-code> "#" <unicode-name>
my $cs_code = $1;
my $un_code = $2;
my $un_name = $3;
# Produce UTF-8 sequence from character code;
my $un_utf8 = join('',
map { sprintf("%02X", $_) }
unpack("U0C*", pack("U", hex($un_code)))
);
print " $cs_code $un_utf8 ; $un_name\n";
} else {
warn "Unrecognized line: '$_'";
}
}
###############################################################################

View File

@ -0,0 +1,130 @@
charset_map windows-1251 utf-8 {
80 D082 ; #CYRILLIC CAPITAL LETTER DJE
81 D083 ; #CYRILLIC CAPITAL LETTER GJE
82 E2809A ; #SINGLE LOW-9 QUOTATION MARK
83 D193 ; #CYRILLIC SMALL LETTER GJE
84 E2809E ; #DOUBLE LOW-9 QUOTATION MARK
85 E280A6 ; #HORIZONTAL ELLIPSIS
86 E280A0 ; #DAGGER
87 E280A1 ; #DOUBLE DAGGER
88 E282AC ; #EURO SIGN
89 E280B0 ; #PER MILLE SIGN
8A D089 ; #CYRILLIC CAPITAL LETTER LJE
8B E280B9 ; #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
8C D08A ; #CYRILLIC CAPITAL LETTER NJE
8D D08C ; #CYRILLIC CAPITAL LETTER KJE
8E D08B ; #CYRILLIC CAPITAL LETTER TSHE
8F D08F ; #CYRILLIC CAPITAL LETTER DZHE
90 D192 ; #CYRILLIC SMALL LETTER DJE
91 E28098 ; #LEFT SINGLE QUOTATION MARK
92 E28099 ; #RIGHT SINGLE QUOTATION MARK
93 E2809C ; #LEFT DOUBLE QUOTATION MARK
94 E2809D ; #RIGHT DOUBLE QUOTATION MARK
95 E280A2 ; #BULLET
96 E28093 ; #EN DASH
97 E28094 ; #EM DASH
99 E284A2 ; #TRADE MARK SIGN
9A D199 ; #CYRILLIC SMALL LETTER LJE
9B E280BA ; #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
9C D19A ; #CYRILLIC SMALL LETTER NJE
9D D19C ; #CYRILLIC SMALL LETTER KJE
9E D19B ; #CYRILLIC SMALL LETTER TSHE
9F D19F ; #CYRILLIC SMALL LETTER DZHE
A0 C2A0 ; #NO-BREAK SPACE
A1 D08E ; #CYRILLIC CAPITAL LETTER SHORT U
A2 D19E ; #CYRILLIC SMALL LETTER SHORT U
A3 D088 ; #CYRILLIC CAPITAL LETTER JE
A4 C2A4 ; #CURRENCY SIGN
A5 D290 ; #CYRILLIC CAPITAL LETTER GHE WITH UPTURN
A6 C2A6 ; #BROKEN BAR
A7 C2A7 ; #SECTION SIGN
A8 D081 ; #CYRILLIC CAPITAL LETTER IO
A9 C2A9 ; #COPYRIGHT SIGN
AA D084 ; #CYRILLIC CAPITAL LETTER UKRAINIAN IE
AB C2AB ; #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
AC C2AC ; #NOT SIGN
AD C2AD ; #SOFT HYPHEN
AE C2AE ; #REGISTERED SIGN
AF D087 ; #CYRILLIC CAPITAL LETTER YI
B0 C2B0 ; #DEGREE SIGN
B1 C2B1 ; #PLUS-MINUS SIGN
B2 D086 ; #CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
B3 D196 ; #CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
B4 D291 ; #CYRILLIC SMALL LETTER GHE WITH UPTURN
B5 C2B5 ; #MICRO SIGN
B6 C2B6 ; #PILCROW SIGN
B7 C2B7 ; #MIDDLE DOT
B8 D191 ; #CYRILLIC SMALL LETTER IO
B9 E28496 ; #NUMERO SIGN
BA D194 ; #CYRILLIC SMALL LETTER UKRAINIAN IE
BB C2BB ; #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
BC D198 ; #CYRILLIC SMALL LETTER JE
BD D085 ; #CYRILLIC CAPITAL LETTER DZE
BE D195 ; #CYRILLIC SMALL LETTER DZE
BF D197 ; #CYRILLIC SMALL LETTER YI
C0 D090 ; #CYRILLIC CAPITAL LETTER A
C1 D091 ; #CYRILLIC CAPITAL LETTER BE
C2 D092 ; #CYRILLIC CAPITAL LETTER VE
C3 D093 ; #CYRILLIC CAPITAL LETTER GHE
C4 D094 ; #CYRILLIC CAPITAL LETTER DE
C5 D095 ; #CYRILLIC CAPITAL LETTER IE
C6 D096 ; #CYRILLIC CAPITAL LETTER ZHE
C7 D097 ; #CYRILLIC CAPITAL LETTER ZE
C8 D098 ; #CYRILLIC CAPITAL LETTER I
C9 D099 ; #CYRILLIC CAPITAL LETTER SHORT I
CA D09A ; #CYRILLIC CAPITAL LETTER KA
CB D09B ; #CYRILLIC CAPITAL LETTER EL
CC D09C ; #CYRILLIC CAPITAL LETTER EM
CD D09D ; #CYRILLIC CAPITAL LETTER EN
CE D09E ; #CYRILLIC CAPITAL LETTER O
CF D09F ; #CYRILLIC CAPITAL LETTER PE
D0 D0A0 ; #CYRILLIC CAPITAL LETTER ER
D1 D0A1 ; #CYRILLIC CAPITAL LETTER ES
D2 D0A2 ; #CYRILLIC CAPITAL LETTER TE
D3 D0A3 ; #CYRILLIC CAPITAL LETTER U
D4 D0A4 ; #CYRILLIC CAPITAL LETTER EF
D5 D0A5 ; #CYRILLIC CAPITAL LETTER HA
D6 D0A6 ; #CYRILLIC CAPITAL LETTER TSE
D7 D0A7 ; #CYRILLIC CAPITAL LETTER CHE
D8 D0A8 ; #CYRILLIC CAPITAL LETTER SHA
D9 D0A9 ; #CYRILLIC CAPITAL LETTER SHCHA
DA D0AA ; #CYRILLIC CAPITAL LETTER HARD SIGN
DB D0AB ; #CYRILLIC CAPITAL LETTER YERU
DC D0AC ; #CYRILLIC CAPITAL LETTER SOFT SIGN
DD D0AD ; #CYRILLIC CAPITAL LETTER E
DE D0AE ; #CYRILLIC CAPITAL LETTER YU
DF D0AF ; #CYRILLIC CAPITAL LETTER YA
E0 D0B0 ; #CYRILLIC SMALL LETTER A
E1 D0B1 ; #CYRILLIC SMALL LETTER BE
E2 D0B2 ; #CYRILLIC SMALL LETTER VE
E3 D0B3 ; #CYRILLIC SMALL LETTER GHE
E4 D0B4 ; #CYRILLIC SMALL LETTER DE
E5 D0B5 ; #CYRILLIC SMALL LETTER IE
E6 D0B6 ; #CYRILLIC SMALL LETTER ZHE
E7 D0B7 ; #CYRILLIC SMALL LETTER ZE
E8 D0B8 ; #CYRILLIC SMALL LETTER I
E9 D0B9 ; #CYRILLIC SMALL LETTER SHORT I
EA D0BA ; #CYRILLIC SMALL LETTER KA
EB D0BB ; #CYRILLIC SMALL LETTER EL
EC D0BC ; #CYRILLIC SMALL LETTER EM
ED D0BD ; #CYRILLIC SMALL LETTER EN
EE D0BE ; #CYRILLIC SMALL LETTER O
EF D0BF ; #CYRILLIC SMALL LETTER PE
F0 D180 ; #CYRILLIC SMALL LETTER ER
F1 D181 ; #CYRILLIC SMALL LETTER ES
F2 D182 ; #CYRILLIC SMALL LETTER TE
F3 D183 ; #CYRILLIC SMALL LETTER U
F4 D184 ; #CYRILLIC SMALL LETTER EF
F5 D185 ; #CYRILLIC SMALL LETTER HA
F6 D186 ; #CYRILLIC SMALL LETTER TSE
F7 D187 ; #CYRILLIC SMALL LETTER CHE
F8 D188 ; #CYRILLIC SMALL LETTER SHA
F9 D189 ; #CYRILLIC SMALL LETTER SHCHA
FA D18A ; #CYRILLIC SMALL LETTER HARD SIGN
FB D18B ; #CYRILLIC SMALL LETTER YERU
FC D18C ; #CYRILLIC SMALL LETTER SOFT SIGN
FD D18D ; #CYRILLIC SMALL LETTER E
FE D18E ; #CYRILLIC SMALL LETTER YU
FF D18F ; #CYRILLIC SMALL LETTER YA
}

View File

@ -0,0 +1,4 @@
au BufRead,BufNewFile *.nginx set ft=nginx
au BufRead,BufNewFile */etc/nginx/* set ft=nginx
au BufRead,BufNewFile */usr/local/nginx/conf/* set ft=nginx
au BufRead,BufNewFile nginx.conf set ft=nginx

View File

@ -0,0 +1 @@
setlocal commentstring=#\ %s

View File

@ -0,0 +1,11 @@
if exists("b:did_indent")
finish
endif
let b:did_indent = 1
setlocal indentexpr=
" cindent actually works for nginx' simple file structure
setlocal cindent
" Just make sure that the comments are not reset as defs would be.
setlocal cinkeys-=0#

File diff suppressed because it is too large Load Diff

8765
nginx-1.20.2/docs/CHANGES Normal file

File diff suppressed because it is too large Load Diff

8915
nginx-1.20.2/docs/CHANGES.ru Normal file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More