初始化提交

This commit is contained in:
考拉
2025-09-12 21:45:28 +08:00
commit edc50ea20e
267 changed files with 17516 additions and 0 deletions

43
.gitignore vendored Normal file
View File

@@ -0,0 +1,43 @@
######################################################################
# Build Tools
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
target/
!.mvn/wrapper/maven-wrapper.jar
######################################################################
# IDE
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
nbproject/private/
build/*
nbbuild/
dist/
nbdist/
.nb-gradle/
######################################################################
# Others
*.log
*.xml.versionsBackup
!*/build/*.java
!*/build/*.html
!*/build/*.xml

24
Dockerfile Normal file
View File

@@ -0,0 +1,24 @@
# 基础镜像使用java
FROM openjdk:8-jdk-alpine
# 作者
MAINTAINER q3z3 admin@q3z3.com
# 最后更新时间
ENV REFRESHED_AT 2020-06-18
# 允许指定端口转发
EXPOSE 8080
# 其效果是在主机 /var/lib/docker 目录下创建了一个临时文件,并链接到容器的/tmp
VOLUME /tmp
# 将jar包添加到容器中并更名为platform-admin.jar
WORKDIR /data
RUN mkdir logs
ADD target/chat-api.jar platform-admin.jar
# 修改时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 图片报错问题
ENV LANG en_US.UTF-8
RUN echo -e "https://mirrors.ustc.edu.cn/alpine/v3.3/main" > /etc/apk/repositories \
&& echo "https://mirrors.ustc.edu.cn/alpine/v3.3/community" >> /etc/apk/repositories \
&& apk add --update ttf-dejavu fontconfig && rm -rf /var/cache/apk/*
# 运行jar包
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/data/platform-admin.jar", "--mpw.key=fe48e54270ad9d48"]

373
LICENSE Normal file
View File

@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

43
README.md Normal file
View File

@@ -0,0 +1,43 @@
# chat-uniapp微聊
```
基于uniapp的即时通讯app客户端支持iOS与Android双系统打包运行
```
## 项目说明
+ 一直以来都有一个社交梦想做一款IM应用看了很多优秀的开源项目但是没有合适自己的。于是利用休息时间自己写了这么一套系统。
+ 项目第一个版本历时2个月前端使用`uniapp`,后端使用`SpringBoot`
+ 手机端使用`uniapp`实现,目前仅支持`安卓端``iOS端``H5端`
## 技术使用
+ 推送uniPush + websocket
+ 资源阿里OSS图片、声音、视频、文件等
+ 音视频TRTC
+ 地图:高德地图
+ 后端Hutool、MyBatis-Plus、shiro、sharding-jdbc等
+ 前端uniapp(Vue3)
## 演示效果
<img src="https://img.alicdn.com/imgextra/i3/87413133/O1CN01bZSz2q1Z0xco96F1t_!!87413133.jpg" width="200">
<img src="https://img.alicdn.com/imgextra/i3/87413133/O1CN01Pe8G6S1Z0xcmQluDI_!!87413133.jpg" width="200">
<img src="https://img.alicdn.com/imgextra/i1/87413133/O1CN012JP8VW1Z0xccuWKzM_!!87413133.jpg" width="200">
<img src="https://img.alicdn.com/imgextra/i4/87413133/O1CN01fMUNJA1Z0xck1w0kt_!!87413133.jpg" width="200">
<img src="https://img.alicdn.com/imgextra/i3/87413133/O1CN01n8MZhZ1Z0xctYZEbM_!!87413133.jpg" width="200">
## 传送门
+ 您的支持,就是我们`【生发的动力】`,请手动点个`star`吧。
+ chat-flutter
[https://github.com/lakaola/chat-flutter](https://github.com/lakaola/chat-flutter)
[https://gitee.com/lakaola/chat-flutter](https://gitee.com/lakaola/chat-flutter)
+ chat-uniapp
[https://github.com/lakaola/chat-uniapp](https://github.com/lakaola/chat-uniapp)
[https://gitee.com/lakaola/chat-uniapp](https://gitee.com/lakaola/chat-uniapp)
+ chat-api
[https://github.com/lakaola/chat-api](https://github.com/lakaola/chat-api)
[https://gitee.com/lakaola/chat-api](https://gitee.com/lakaola/chat-api)
+ 加入QQ群[![加入QQ群](https://img.shields.io/badge/加入QQ群-535099683-blue.svg)](https://jq.qq.com/?_wv=1027&k=PQMnFugm) 详细文档进群获取535099683

139
doc/page/am.html Normal file
View File

@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>用户协议及隐私协议</title>
</head>
<body>
<div class="agreement">
<div class="agreementc">
<div class="agreement_html">
<p style="text-align: center;"> <strong>服务条款确认与接纳</strong> </p>
<p style="text-indent: 2em;"> <span
style="text-indent: 2em;">《微聊》拥有相关软件的所有权和运作权,《微聊》享有对其产品的上一切活动的监督、提示、检查、纠正及处罚等权利。用户通过注册程序阅读本服务条款并点击"注册/登录"按钮完成注册/登录,即表示用户与《微聊》已达成协议,自愿接受本服务条款的所有内容。如果用户不同意服务条款的条件,则不能获得使用《微聊》服务以及注册成为《微聊》用户的权利。</span>
</p>
<p> <br /> </p>
<p> <strong>使用规则</strong> </p>
<p style="text-indent: 2em;">
1.用户注册成功后,《微聊》将给予每个用户一个用户账号及相应的密码,该用户账号和密码由用户负责保管;用户应当对以其用户账号进行的所有活动和事件负法律责任。 </p>
<p style="text-indent: 2em;"> 2.用户须对在<span
style="text-indent: 32px;">《微聊》</span>的注册信息的真实性、合法性、有效性承担全部责任,用户不得冒充他人;不得利用他人的名义发布任何信息;不得恶意使用注册帐户导致其他用户误认;否则<span
style="text-indent: 32px;">《微聊》</span>有权立即停止提供服务,收回其账号并由用户独自承担由此而产生的一切法律责任。 </p>
<p style="text-indent: 2em;"> 3.用户不得使用<span
style="text-indent: 32px;">《微聊》</span>服务发送或传播敏感信息和违反国家法律制度的信息,包括但不限于下列信息:<br />
</p>
<p style="white-space: normal; text-indent: 2em;"> 反对宪法所确定的基本原则的; </p>
<p style="white-space: normal; text-indent: 2em;"> 危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的; </p>
<p style="white-space: normal; text-indent: 2em;"> 损害国家荣誉和利益的; </p>
<p style="white-space: normal; text-indent: 2em;"> 煽动民族仇恨、民族岐视,破坏民族团结的; </p>
<p style="white-space: normal; text-indent: 2em;"> 破坏国家宗教政策,宣扬邪教和封建迷信散布谣言,扰乱社会秩序,破坏社会稳定的; </p>
<p style="white-space: normal; text-indent: 2em;"> 散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教峻犯罪的; </p>
<p style="white-space: normal; text-indent: 2em;"> 侮辱或者诽谤他人,侵害他人合法权益的; </p>
<p style="white-space: normal; text-indent: 2em;"> 含有法律、行政法规禁止的其他内容的。<br /> </p>
<p style="text-indent: 2em;"> 4.<span style="text-indent: 32px;">《微聊》</span>有权对用户使用<span
style="text-indent: 32px;">《微聊》</span>的情况进行审查和监督,如用户在使用<span
style="text-indent: 32px;">《微聊》</span>时违反任何上述规定<span
style="text-indent: 32px;">《微聊》</span>或其授权的人有权要求用户改正或直接采取一切必要的措施以减轻用户不当行为造成的影响。
</p>
<p style="text-indent: 2em;"> 5.盗取他人用户账号或利用网络通讯骚扰他人,均属于非法行为。用户不得采用测试、欺骗等任何非法手段,盗取其他用户的账号和对他人进行强扰。
</p>
<p style="text-indent: 2em;"> <br /> </p>
<p> <strong>免责声明</strong> </p>
<p style="text-indent: 2em;"> 1.<span
style="text-indent: 32px;">《微聊》</span>不能对用户在本社区回答问题的答案或评论的准确性及合理性进行保证。 </p>
<p style="text-indent: 2em;"> 2.若<span
style="text-indent: 32px;">《微聊》</span>产品已经明示其网络服务提供方式发生变更提醒用户应当注意事项,用户未按要求操作所产生的一切后果由用户自行承担。
</p>
<p style="text-indent: 2em;"> 3.用户明确同意其使用<span
style="text-indent: 32px;">《微聊》</span>产品网络服务所存在的风险将完全由其自己承担;因其使用<span
style="text-indent: 32px;">《微聊》</span>服务而产生的一切后果也由其自己承担,<span
style="text-indent: 32px;">《微聊》</span>对用户不承担任何责任。 </p>
<p style="text-indent: 2em;"> 4.<span
style="text-indent: 32px;">《微聊》</span>不保证网络服务一定能满足用户的要求,也不保证网络服务不会中断,对网络服务的及时性、安全性、准确性也都不作保证。
</p>
<p style="text-indent: 2em;"> 5.对于因不可抗力或<span
style="text-indent: 32px;">《微聊》</span>不能控制的原因造成的网络服务中断或其它缺陷,<span
style="text-indent: 32px;">《微聊》</span>不承担任何责任,但将尽力减少因此而给用户造成的损失和影响。 </p>
<p style="text-indent: 2em;"> 6.用户同意保障和维护<span
style="text-indent: 32px;">《微聊》</span>及其他用户的利益,用户在<span
style="text-indent: 32px;">《微聊》</span>发表的内容仅表明其个人的立场和观点,并不代表<span
style="text-indent: 32px;">《微聊》</span>的立场或观点。由于用户发表内容违法、不真实、不正当、侵犯第三方合法权益,或用户违反本协议项下的任何条款而给<span
style="text-indent: 32px;">《微聊》</span>或任何其他第三人造成损失,用户同意承担由此造成的损害赔偿责任。 </p>
<p style="text-indent: 2em;"> <br /> </p>
<p> <strong>服务条款的修改</strong> </p>
<p style="text-indent: 2em;"> <span
style="text-indent: 32px;">《微聊》</span>会在必要时修改服务条款,服务条款ー旦发生变动,<span
style="text-indent: 32px;">《微聊》</span>将会在用户进入下一步使用前的页面提示修改内容。如果您同意改动,请再一次激活"我同意"按钮。如果您不接受,请及时取消您的帐户。用户要继续使用<span
style="text-indent: 32px;">《微聊》</span>各项服务需要两方面的确认: </p>
<p style="text-indent: 2em;"> 1.首先确认<span style="text-indent: 32px;">《微聊》</span>服务条款及其变动。
</p>
<p style="text-indent: 2em;"> 2.同意接受所有的服务条款限制。 </p>
<p style="text-indent: 2em;"> <br /> </p>
<p> <strong>隐私政策</strong> </p>
<p style="text-indent: 2em;"> <span
style="text-indent: 32px;">《微聊》</span>非常重视对用户隐私权的保护,承诺不会在未获得用户许可的情况下擅自将用户的个人资料信息出租或出售给任何第三方,但以下情况除外:
</p>
<p style="text-indent: 2em;"> <span style="text-indent: 0em;">您同意让第三方共享资料;</span> </p>
<p style="text-indent: 2em;"> <span style="text-indent: 0em;">您同意公开你的个人资料,享受为您提供的产品和服务;</span> </p>
<p style="text-indent: 2em;"> <span style="text-indent: 0em;">本站需要听从法庭传票、法律命令或遵循法律;</span> </p>
<p style="text-indent: 2em;"> <span style="text-indent: 0em;">本站发现您违反了本站服务条款或本站其它使用规定;</span> </p>
<p style="text-indent: 2em;"> <span
style="text-indent: 0em;">在本应用平台上创建的某一交易中,如交易任何一方履行或部分履行了交易义务并提出信息披露请求的,本应用有权决定向该用户提供其交易对方的联络方式等必要信息,以促成交易的完成或纠纷的解决。</span>
</p>
<p style="text-indent: 2em;"> <span style="text-indent: 0em;"><span
style="color: rgb(64, 64, 64); font-family: -apple-system, BlinkMacSystemFont, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;, &quot;Segoe UI&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; background-color: rgb(255, 255, 255);"><br /></span></span>
</p>
<p style="text-indent: 2em;"> <span style="text-indent: 0em;"><span
style="text-indent: 32px;">《微聊》</span><span
style="text-indent: 32px;">在哪些方面用到了您的信息:</span></span> </p>
<p style="text-indent: 2em;"> 登陆注册功能,用户注册时输入的个人信息仅用于验证用户真实性; </p>
<p style="text-indent: 2em;"> <span
style="text-indent: 32px;">用户发布/交易内容的功能如失物招领,后勤曝光台、建言献策、</span><span
style="text-indent: 32px;">外卖预定等您在本应用发布的有关信息数据和需要为您提供交易服务所需的确认信息;</span> </p>
<p style="text-indent: 2em;"> <span style="text-indent: 32px;">您有权选择接受或拒绝接受</span><span
style="text-indent: 32px;">本政策</span><span style="text-indent: 32px;">。但如果您选择拒绝接受<span
style="text-indent: 32px;">本政策</span>,则您可能无法登录或使用依赖于<span
style="text-indent: 32px;">本政策</span>的网络服务或功能。</span> </p>
<p style="text-indent: 2em;"> <br /> </p>
<p style="text-indent: 2em;"> <span style="text-indent: 32px;">信息的存储和交换:</span> </p>
<p style="text-indent: 2em;"> <span
style="text-indent: 32px;">本应用收集的有关您的信息和资料将保存在本应用及(或)其关联的服务器上,这些信息和资料均存储在中国境内服务器。</span>
</p>
<p style="text-indent: 2em;"> <br /> </p>
<p style="text-indent: 2em;"> <span style="text-indent: 32px;">隐私政策的更改</span><span
style="text-indent: 32px;"></span> </p>
<p style="text-indent: 2em;"> <span
style="text-indent: 32px;">如果决定更改隐私政策,我们会在本政策中、官方网站中以及我们认为适当的位置发布这些更改,以便您了解我们如何收集、使用您的个人信息,哪</span><span
style="text-indent: 32px;">些人可以访问这些信息,以及在什么情况下我们会透露这些信息。</span></p>
</div>
</div>
</div>
</body>
</html>
<style type="text/css">
* {
margin: 0;
padding: 0;
list-style-type: none;
text-decoration: none;
}
.agreement {
display: flex;
flex-direction: column;
flex-wrap: wrap;
margin-top: 12px;
}
.agreementc {
background: #fff;
padding: 12px;
width: 100%;
box-sizing: border-box;
}
.agreementc image,
.agreementc img {
max-width: 100%;
}
</style>

202
doc/page/index.html Normal file
View File

@@ -0,0 +1,202 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>微聊</title>
<link rel="stylesheet" href="https://unpkg.com/swiper@7/swiper-bundle.min.css">
<script src="https://unpkg.com/swiper@7/swiper-bundle.min.js"> </script>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1" />
</head>
<style type="text/css">
*{
margin: 0;
padding: 0;
list-style-type: none;
text-decoration: none;
}
html,
body {
}
.box{
overflow: auto;
position: relative;
background-color: #2080C3;
}
.container{
flex: 1;
background-color: #2080C3;
width: 100%;
height: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
padding: 12px;
box-sizing: border-box;
padding-bottom: 0;
}
.phoneSwiperBox{
position: relative;
}
.phoneSwiperBox-filter{
position: relative;
width: 100%;
height: 100%;
z-index: 2;
}
.downloadapp{
flex: 1;
padding-bottom: 20px;
}
.downloadapp-img{
width:300px;
height: 300px;
border-radius: 24px;
}
.downloadapp{
color: #fff;
}
.downloadapp h1{
margin-top: 20px;
font-weight: normal;
}
.downloadapp p{
margin: 14px 0;
}
.downloadapp{
text-align: center;
}
.downloadapp span{
color: #dedede;
font-size: 14px;
}
.container-top{
width: 100%;
margin-bottom: 80px;
text-align: center;
color: #fff;
}
.container-app{
display: flex;flex-direction: row;flex-wrap: wrap;
}
.container-app-swiper{
z-index: 3;
width: 45.7%;
height: 87.4%;
position: absolute;
top: 1.9%;
left: 41.7%;
background-color: #ffffff;
overflow: hidden;
border-radius: 18px;
}
.container-top h1{
margin-top: 30px;
}
.container-top h1,.container-top h2,.container-top h3{
margin-bottom: 25px;
font-weight: normal;
}
.swiper{
height: 100%;
}
.swiper-slide{
height: 100%;
}
.swiper-slide img{
width: 100%;
height: 100%;
}
@media screen and (max-width:750px) {
.container-top h1{}
.container-top h2{font-size: 16px;}
.container-top{
margin-bottom: 0;
}
}
.downloadapp-btn{
display: block;
cursor: pointer;
width: 190px;
background-color: #12B600;
border-radius: 14px;
margin: 10px auto;
padding: 8px 0;
color: #fff;
}
.downloadapp-btn:hover{
background-color: #11b000;
}
</style>
<body>
<div class="box">
<div class="container">
<div class="container-top">
<h1>《微聊》</h1>
<h2>单人、群聊、朋友圈、摇一摇、附近的人、收藏、扫码加好友、机器人聊天、文字、图片、名片、音/视频通话 等...</h2>
<h3>你能想到的这里都有</h3>
</div>
<div class="container-app">
<div class="phoneSwiperBox">
<img class="phoneSwiperBox-filter" src="https://img.alicdn.com/imgextra/i3/87413133/O1CN01neGaa71Z0xcslX3At_!!87413133.png" >
<div class="container-app-swiper">
<div class="swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">
<img src="https://img.alicdn.com/imgextra/i2/87413133/O1CN017NSpYx1Z0xcvYxEbO_!!87413133.jpg" >
</div>
<div class="swiper-slide">
<img src="https://img.alicdn.com/imgextra/i1/87413133/O1CN01Oq8SLw1Z0xcukUdV0_!!87413133.jpg" >
</div>
<div class="swiper-slide">
<img src="https://img.alicdn.com/imgextra/i2/87413133/O1CN01xOYaIK1Z0xco9If2e_!!87413133.jpg" >
</div>
<div class="swiper-slide">
<img src="https://img.alicdn.com/imgextra/i3/87413133/O1CN01Pe8G6S1Z0xcmQluDI_!!87413133.jpg" >
</div>
<div class="swiper-slide">
<img src="https://img.alicdn.com/imgextra/i3/87413133/O1CN01bZSz2q1Z0xco96F1t_!!87413133.jpg" >
</div>
<div class="swiper-slide">
<img src="https://img.alicdn.com/imgextra/i2/87413133/O1CN01mD2wwN1Z0xctYYdAA_!!87413133.jpg" >
</div>
<div class="swiper-slide">
<img src="https://img.alicdn.com/imgextra/i1/87413133/O1CN012JP8VW1Z0xccuWKzM_!!87413133.jpg" >
</div>
<div class="swiper-slide">
<img src="https://img.alicdn.com/imgextra/i4/87413133/O1CN01fMUNJA1Z0xck1w0kt_!!87413133.jpg" >
</div>
<div class="swiper-slide">
<img src="https://img.alicdn.com/imgextra/i3/87413133/O1CN01n8MZhZ1Z0xctYZEbM_!!87413133.jpg" >
</div>
<div class="swiper-slide">
<img src="https://img.alicdn.com/imgextra/i2/87413133/O1CN017vPSbK1Z0xcqoFn8E_!!87413133.jpg" >
</div>
</div>
</div>
</div>
</div>
<div class="downloadapp">
<h1>微聊下载</h1>
<p>考拉Team微聊精仿微信仅提供学习研究请勿商业使用</p>
<p>技术栈uniapp(vue3)、Java</p>
<img class="downloadapp-img" src="https://q3z3-im.oss-cn-beijing.aliyuncs.com/public/20220419162017.png" >
<div class="downloadapp-smbtn">扫码下载体验安卓端</div>
<a target="_blank" href="http://im.q3z3.com/public/im.apk" class="downloadapp-btn">下载安卓端</a>
<span>苹果端敬请期待</span>
</div>
</div>
</div>
</div>
</body>
</html>
<script>
var mySwiper = new Swiper('.swiper', {
autoplay: true,//可选选项,自动滑动
loop: true
})
</script>

71
doc/readme.md Normal file
View File

@@ -0,0 +1,71 @@
# 找不到core-upload.jar
修改maven配置文件settings.xml
1.0.0版本:
```
<mirror>
<id>q3z3-boot-tools-temp</id>
<name>temp</name>
<mirrorOf>q3z3-boot-tools-temp</mirrorOf>
<url>https://q3z3-maven.pkg.coding.net/repository/boot-tools/temp/</url>
</mirror>
```
1.1.0版本:
```
<mirror>
<id>q3z3-boot-tools-maven</id>
<name>maven</name>
<mirrorOf>q3z3-boot-tools-maven</mirrorOf>
<url>https://q3z3-maven.pkg.coding.net/repository/boot-tools/maven/</url>
</mirror>
```
完整版:
```
<mirrors>
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云spring插件仓库</name>
<url>https://maven.aliyun.com/repository/spring-plugin</url>
</mirror>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
<mirror>
<id>repo2</id>
<name>Mirror from Maven Repo2</name>
<url>https://repo.spring.io/plugins-release/</url>
<mirrorOf>*</mirrorOf>
</mirror>
<mirror>
<id>q3z3-boot-tools-temp</id>
<name>temp</name>
<mirrorOf>q3z3-boot-tools-temp</mirrorOf>
<url>https://q3z3-maven.pkg.coding.net/repository/boot-tools/temp/</url>
</mirror>
<mirror>
<id>q3z3-boot-tools-maven</id>
<name>maven</name>
<mirrorOf>q3z3-boot-tools-maven</mirrorOf>
<url>https://q3z3-maven.pkg.coding.net/repository/boot-tools/maven/</url>
</mirror>
</mirrors>
```

215
doc/sql/boot-im.sql Normal file
View File

@@ -0,0 +1,215 @@
/*
SQLyog Ultimate v11.25 (64 bit)
MySQL - 5.7.21-log : Database - boot-im
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`boot-im` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `boot-im`;
/*Table structure for table `chat_apply` */
DROP TABLE IF EXISTS `chat_apply`;
CREATE TABLE `chat_apply` (
`id` bigint(20) NOT NULL COMMENT '主键',
`from_id` bigint(20) DEFAULT NULL COMMENT '发起id',
`to_id` bigint(20) DEFAULT NULL COMMENT '接收id',
`target_id` bigint(20) DEFAULT NULL COMMENT '目标id',
`apply_type` char(1) DEFAULT NULL COMMENT '申请类型1好友2群组',
`apply_status` char(1) DEFAULT NULL COMMENT '申请状态0无1同意2拒绝3忽略',
`apply_source` char(1) DEFAULT NULL COMMENT '申请来源',
`reason` varchar(200) DEFAULT NULL COMMENT '理由',
`create_time` datetime DEFAULT NULL COMMENT '申请时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='好友申请表';
/*Table structure for table `chat_collect` */
DROP TABLE IF EXISTS `chat_collect`;
CREATE TABLE `chat_collect` (
`id` bigint(20) NOT NULL COMMENT '主键',
`user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
`collect_type` varchar(20) DEFAULT NULL COMMENT '收藏类型',
`content` longtext COMMENT '内容',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收藏表';
/*Table structure for table `chat_feedback` */
DROP TABLE IF EXISTS `chat_feedback`;
CREATE TABLE `chat_feedback` (
`id` bigint(20) NOT NULL COMMENT '主键',
`user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
`images` varchar(2000) DEFAULT NULL COMMENT '图片',
`content` longtext COMMENT '内容',
`version` varchar(20) DEFAULT '1.0.0' COMMENT '提交版本',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='建议反馈';
/*Table structure for table `chat_friend` */
DROP TABLE IF EXISTS `chat_friend`;
CREATE TABLE `chat_friend` (
`id` bigint(20) NOT NULL COMMENT '主键',
`from_id` bigint(20) DEFAULT NULL COMMENT '用户id',
`to_id` bigint(20) DEFAULT NULL COMMENT '好友id',
`remark` varchar(32) DEFAULT NULL COMMENT '备注',
`black` char(1) DEFAULT 'N' COMMENT '黑名单',
`source` char(1) DEFAULT NULL COMMENT '好友来源',
`top` char(1) DEFAULT 'N' COMMENT '是否置顶',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user` (`from_id`,`to_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='好友表';
/*Table structure for table `chat_group` */
DROP TABLE IF EXISTS `chat_group`;
CREATE TABLE `chat_group` (
`id` bigint(20) NOT NULL COMMENT '主键',
`name` varchar(50) DEFAULT NULL COMMENT '群名',
`notice` varchar(200) DEFAULT NULL COMMENT '公告',
`portrait` longtext COMMENT '头像',
`master` bigint(20) DEFAULT NULL COMMENT '群主',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='群组';
/*Table structure for table `chat_group_info` */
DROP TABLE IF EXISTS `chat_group_info`;
CREATE TABLE `chat_group_info` (
`info_id` bigint(20) NOT NULL COMMENT '主键',
`user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
`group_id` bigint(20) DEFAULT NULL COMMENT '群组id',
`top` char(1) DEFAULT 'N' COMMENT '是否置顶',
`disturb` char(1) DEFAULT 'N' COMMENT '是否免打扰',
`keep_group` char(1) DEFAULT 'N' COMMENT '是否保存群组',
`create_time` datetime DEFAULT NULL COMMENT '加入时间',
PRIMARY KEY (`info_id`),
UNIQUE KEY `idx_group` (`user_id`,`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*Table structure for table `chat_msg` */
DROP TABLE IF EXISTS `chat_msg`;
CREATE TABLE `chat_msg` (
`id` bigint(20) NOT NULL COMMENT '消息主键',
`from_id` bigint(20) DEFAULT NULL COMMENT '发送人',
`to_id` bigint(20) DEFAULT NULL COMMENT '接收人',
`msg_type` varchar(20) DEFAULT NULL COMMENT '消息类型',
`talk_type` varchar(20) DEFAULT NULL COMMENT '聊天类型',
`content` longtext COMMENT '消息内容',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='聊天消息';
/*Table structure for table `chat_topic` */
DROP TABLE IF EXISTS `chat_topic`;
CREATE TABLE `chat_topic` (
`id` bigint(20) NOT NULL COMMENT '主键',
`user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
`topic_type` varchar(20) DEFAULT NULL COMMENT '类型',
`content` varchar(2000) DEFAULT NULL COMMENT '内容',
`location` varchar(200) DEFAULT NULL COMMENT '经纬度',
`create_time` datetime DEFAULT NULL COMMENT '时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='主题';
/*Table structure for table `chat_topic_like` */
DROP TABLE IF EXISTS `chat_topic_like`;
CREATE TABLE `chat_topic_like` (
`id` bigint(20) NOT NULL COMMENT '主键',
`topic_id` bigint(20) DEFAULT NULL COMMENT '帖子id',
`user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
`has_like` char(1) DEFAULT 'Y' COMMENT '是否点赞',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='帖子点赞';
/*Table structure for table `chat_topic_reply` */
DROP TABLE IF EXISTS `chat_topic_reply`;
CREATE TABLE `chat_topic_reply` (
`reply_id` bigint(20) NOT NULL COMMENT '主键',
`reply_type` char(1) DEFAULT NULL COMMENT '回复类型1帖子2用户',
`reply_status` char(1) DEFAULT NULL COMMENT '回复状态',
`content` longtext COMMENT '回复内容',
`topic_id` bigint(20) DEFAULT NULL COMMENT '帖子id',
`user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
`target_id` bigint(20) DEFAULT NULL COMMENT '目标id',
`create_time` datetime DEFAULT NULL COMMENT '回复时间',
PRIMARY KEY (`reply_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='帖子回复表';
/*Table structure for table `chat_user` */
DROP TABLE IF EXISTS `chat_user`;
CREATE TABLE `chat_user` (
`user_id` bigint(20) NOT NULL COMMENT '主键',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`chat_no` varchar(32) DEFAULT NULL COMMENT '微聊号码',
`nick_name` varchar(32) DEFAULT NULL COMMENT '昵称',
`token` varchar(32) DEFAULT NULL COMMENT '用户token',
`gender` varchar(1) DEFAULT '1' COMMENT '性别1男0女',
`portrait` varchar(2000) DEFAULT NULL COMMENT '头像',
`intro` varchar(200) DEFAULT NULL COMMENT '介绍',
`cover` varchar(2000) DEFAULT NULL COMMENT '封面',
`provinces` varchar(20) DEFAULT NULL COMMENT '省份',
`city` varchar(20) DEFAULT NULL COMMENT '城市',
`salt` varchar(4) DEFAULT NULL COMMENT '',
`password` varchar(32) DEFAULT NULL COMMENT '密码',
`create_time` datetime DEFAULT NULL COMMENT '注册时间',
`version` varchar(20) DEFAULT '1.0.0' COMMENT '版本信息',
`deleted` tinyint(1) DEFAULT '0' COMMENT '注销0正常null注销',
`deleted_time` datetime DEFAULT NULL COMMENT '注销时间',
PRIMARY KEY (`user_id`),
UNIQUE KEY `idx_phone` (`phone`,`deleted`),
UNIQUE KEY `idx_no` (`chat_no`,`deleted_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
/*Table structure for table `chat_version` */
DROP TABLE IF EXISTS `chat_version`;
CREATE TABLE `chat_version` (
`id` bigint(20) NOT NULL COMMENT '主键',
`version` varchar(20) DEFAULT NULL COMMENT '版本',
`url` varchar(2000) DEFAULT NULL COMMENT '地址',
`content` longtext COMMENT '内容',
`descr` varchar(200) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='版本';
/*Data for the table `chat_version` */
insert into `chat_version`(`id`,`version`,`url`,`content`,`descr`) values (1,'1.0.0','http://im.q3z3.com/public/am.html','我是用户协议','用户协议'),(2,'1.0.0','http://im.q3z3.com/public/im.apk','我是安卓包','安卓升级包'),(3,'1.0.0','https://www.baidu.com/3','我是iOS包','iOS升级包');
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

270
pom.xml Normal file
View File

@@ -0,0 +1,270 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.platform</groupId>
<artifactId>chat-api</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>chat-api</name>
<description>移动端接口</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<pagehelper.version>1.4.1</pagehelper.version>
<mybatis-plus.version>3.4.3</mybatis-plus.version>
<hutool.version>5.6.7</hutool.version>
<shiro.version>1.5.2</shiro.version>
<sharding.version>4.1.1</sharding.version>
<tencent.version>3.1.473</tencent.version>
<netty.version>4.1.31.Final</netty.version>
<aliyun-sdk-oss>3.11.2</aliyun-sdk-oss>
<qiniu.version>7.8.0</qiniu.version>
<qcloud.cos.version>5.6.3</qcloud.cos.version>
<fastdfs.version>1.27.2</fastdfs.version>
<minio.version>8.2.2</minio.version>
</properties>
<dependencies>
<!-- SpringBoot 核心包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- web 容器使用 undertow 性能更强 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-websockets-jsr</artifactId>
</dependency>
<!-- SpringBoot Web容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- SpringBoot 拦截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Spring框架基本的核心工具-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- websocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- redis 缓存操作 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- pool 对象池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- 工具包 end -->
<!-- mybatis-plus start -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- mybatis-plus end -->
<!-- 实现对 Sharding-JDBC 的自动化配置 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding.version}</version>
</dependency>
<!-- pagehelper 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.version}</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- lombok可使代码更简洁 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- tencent -->
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>${tencent.version}</version>
</dependency>
<!-- netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty.version}</version>
</dependency>
<!-- commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<!-- 阿里云oss -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun-sdk-oss}</version>
</dependency>
<!-- 七牛云kodo -->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>${qiniu.version}</version>
</dependency>
<!-- 腾讯云cos -->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>${qcloud.cos.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- fastDFS -->
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>${fastdfs.version}</version>
</dependency>
<!-- Minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
<!-- fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork> <!-- 如果没有该配置devtools不会生效 -->
</configuration>
</plugin>
</plugins>
</build>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>

View File

@@ -0,0 +1,138 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.baomidou.mybatisplus.core.handlers;
import cn.hutool.core.util.EnumUtil;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.platform.common.core.EnumUtils;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.invoker.Invoker;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
* 解决mybatis-plus枚举转换问题
*/
public class MybatisEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
private static final Map<String, String> TABLE_METHOD_OF_ENUM_TYPES = new ConcurrentHashMap();
private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();
private final Class<E> enumClassType;
private final Class<?> propertyType;
private final Invoker getInvoker;
public MybatisEnumTypeHandler(Class<E> enumClassType) {
if (enumClassType == null) {
throw new IllegalArgumentException("Type argument cannot be null");
} else {
this.enumClassType = enumClassType;
MetaClass metaClass = MetaClass.forClass(enumClassType, REFLECTOR_FACTORY);
String name = "value";
if (!IEnum.class.isAssignableFrom(enumClassType)) {
name = (String) findEnumValueFieldName(this.enumClassType).orElseThrow(() -> {
return new IllegalArgumentException(String.format("Could not find @EnumValue in Class: %s.", this.enumClassType.getName()));
});
}
this.propertyType = ReflectionKit.resolvePrimitiveIfNecessary(metaClass.getGetterType(name));
this.getInvoker = metaClass.getGetInvoker(name);
}
}
public static Optional<String> findEnumValueFieldName(Class<?> clazz) {
if (clazz != null && clazz.isEnum()) {
String className = clazz.getName();
return Optional.ofNullable(CollectionUtils.computeIfAbsent(TABLE_METHOD_OF_ENUM_TYPES, className, (key) -> {
Optional<Field> fieldOptional = findEnumValueAnnotationField(clazz);
return fieldOptional.map(Field::getName).orElse(null);
}));
} else {
return Optional.empty();
}
}
private static Optional<Field> findEnumValueAnnotationField(Class<?> clazz) {
return Arrays.stream(clazz.getDeclaredFields()).filter((field) -> {
return field.isAnnotationPresent(EnumValue.class);
}).findFirst();
}
public static boolean isMpEnums(Class<?> clazz) {
return clazz != null && clazz.isEnum() && (IEnum.class.isAssignableFrom(clazz) || findEnumValueFieldName(clazz).isPresent());
}
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
if (jdbcType == null) {
ps.setObject(i, this.getValue(parameter));
} else {
ps.setObject(i, this.getValue(parameter), jdbcType.TYPE_CODE);
}
}
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
Object value = rs.getObject(columnName);
if (EnumUtil.isEnum(propertyType)) {
value = EnumUtils.toEnum(this.enumClassType, rs.getString(columnName));
}
return null == value && rs.wasNull() ? null : this.valueOf(value);
}
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
Object value = rs.getObject(columnIndex);
if (EnumUtil.isEnum(propertyType)) {
value = EnumUtils.toEnum(this.enumClassType, rs.getString(columnIndex));
}
return null == value && rs.wasNull() ? null : this.valueOf(value);
}
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
Object value = cs.getObject(columnIndex);
if (EnumUtil.isEnum(propertyType)) {
value = EnumUtils.toEnum(this.enumClassType, cs.getString(columnIndex));
}
return null == value && cs.wasNull() ? null : this.valueOf(value);
}
private E valueOf(Object value) {
E[] es = (E[]) this.enumClassType.getEnumConstants();
return Arrays.stream(es).filter((e) -> {
return this.equalsValue(value, this.getValue(e));
}).findAny().orElse(null);
}
protected boolean equalsValue(Object sourceValue, Object targetValue) {
String sValue = StringUtils.toStringTrim(sourceValue);
String tValue = StringUtils.toStringTrim(targetValue);
return sourceValue instanceof Number && targetValue instanceof Number && (new BigDecimal(sValue)).compareTo(new BigDecimal(tValue)) == 0 ? true : Objects.equals(sValue, tValue);
}
private Object getValue(Object object) {
try {
return this.getInvoker.invoke(object, new Object[0]);
} catch (ReflectiveOperationException var3) {
throw ExceptionUtils.mpe(var3);
}
}
}

View File

@@ -0,0 +1,15 @@
package com.platform;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
/**
* web容器中进行部署
*/
public class AppServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(AppStartUp.class);
}
}

View File

@@ -0,0 +1,18 @@
package com.platform;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
/**
* 启动程序
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class AppStartUp {
public static void main(String[] args) {
SpringApplication.run(AppStartUp.class, args);
System.out.println("(♥◠‿◠)ノ゙ 启动成功 ლ(´ڡ`ლ)゙");
}
}

View File

@@ -0,0 +1,10 @@
package com.platform;
import org.apache.ibatis.annotations.Mapper;
/**
* 去除启动警告
*/
@Mapper
public interface NoWarnMyBatisMapper {
}

View File

@@ -0,0 +1,13 @@
package com.platform.common.aspectj;
import java.lang.annotation.*;
/**
* 忽略登录
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented //可生成文档
public @interface IgnoreAuth {
}

View File

@@ -0,0 +1,75 @@
package com.platform.common.aspectj;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.platform.common.shiro.ShiroUtils;
import com.platform.common.utils.ServletUtils;
import com.platform.common.web.domain.AjaxResult;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Aspect
@Configuration
public class SubmitAspect {
private final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
// 最大缓存 100 个
.maximumSize(100)
// 设置缓存过期时间为S
.expireAfterWrite(2, TimeUnit.SECONDS)
.build();
@SneakyThrows
@Around("execution(* com.platform..*Controller.*(..)) && @annotation(submitRepeat)")
public Object around(ProceedingJoinPoint joinPoint, SubmitRepeat submitRepeat) {
String cacheKey = getCacheKey(submitRepeat);
if (!StringUtils.isEmpty(cacheKey)) {
if (CACHES.getIfPresent(cacheKey) == null) {
// 如果是第一次请求,就将key存入缓存中
CACHES.put(cacheKey, cacheKey);
} else {
return AjaxResult.fail("请勿重复请求");
}
}
return joinPoint.proceed();
}
/**
* 加上用户的唯一标识
*/
private String getCacheKey(SubmitRepeat submitRepeat) {
StringBuilder builder = new StringBuilder();
HttpServletRequest request = ServletUtils.getRequest();
String param = null;
// 如果登录获取登录参数
if (ShiroUtils.isLogin()) {
param = ShiroUtils.getToken();
}
// 获取param参数
if (StringUtils.isEmpty(param)) {
Map<String, String> map = ServletUtil.getParamMap(request);
param = JSONUtil.toJsonStr(map);
}
builder.append(param);
builder.append("_");
// 获取path参数
String path = submitRepeat.path();
if (StringUtils.isEmpty(path)) {
path = request.getServletPath();
}
builder.append(path);
return builder.toString();
}
}

View File

@@ -0,0 +1,39 @@
package com.platform.common.aspectj;
import com.platform.common.enums.YesOrNoEnum;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义一个注解,给需要防止重复提交的方法加上该注解
* <p>
* 使用:@SubmitRepeat
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SubmitRepeat {
/**
* 过期时间
*/
long value() default 2L;
/**
* 拦截uri
*/
String path() default "";
/**
* 拦截msg
*/
String msg() default "请勿重复请求";
/**
* 是否抛异常
*/
YesOrNoEnum exception() default YesOrNoEnum.YES;
}

View File

@@ -0,0 +1,110 @@
package com.platform.common.config;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.platform.common.enums.YesOrNoEnum;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.TimeZone;
/**
* 程序注解配置
*/
@Configuration
// 表示通过aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
// 指定要扫描的Mapper类的包的路径
@MapperScan({"com.platform.modules.**.dao"})
// 扫描spring工具类
@ComponentScan(basePackages = {"cn.hutool.extra.spring"})
public class ApplicationConfig {
/**
* 时区配置
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
return builder -> builder.timeZone(TimeZone.getDefault());
}
/**
* 序列化枚举值为数据库存储值
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> builder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
}
@Bean
public static MappingJackson2HttpMessageConverter objectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
// 忽略未知的枚举字段
objectMapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
// 忽略多余的字段不参与序列化
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// null属性字段转""
objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
@Override
public void serialize(Object arg0, JsonGenerator arg1, SerializerProvider arg2) throws IOException {
arg1.writeString("");
}
});
SimpleModule simpleModule = new SimpleModule();
// 格式化Long
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
// 格式化时间
simpleModule.addSerializer(Date.class, new JsonSerializer<Date>() {
@Override
public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(DateUtil.format(date, DatePattern.NORM_DATETIME_FORMAT));
}
});
// 格式化金额
simpleModule.addSerializer(BigDecimal.class, new JsonSerializer<BigDecimal>() {
@Override
public void serialize(BigDecimal decimal, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(decimal.setScale(2, BigDecimal.ROUND_HALF_DOWN).toString());
}
});
// 注册 module
objectMapper.registerModule(simpleModule);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
return converter;
}
@Value("${platform.cors}")
private String cors;
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
if (YesOrNoEnum.YES.getCode().equals(cors)) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**", corsConfiguration);
}
return new CorsFilter(source);
}
}

View File

@@ -0,0 +1,45 @@
package com.platform.common.config;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.List;
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 防全表更新与删除插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
/**
* 批量操作增强
*/
@Bean
public DefaultSqlInjector mybatisSqlInjector() {
return new DefaultSqlInjector() {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
//防止父类方法不可用
List<AbstractMethod> methods = super.getMethodList(mapperClass);
methods.add(new InsertBatchSomeColumn());
return methods;
}
};
}
}

View File

@@ -0,0 +1,58 @@
package com.platform.common.config;
import com.platform.common.core.EnumUtils;
import com.platform.common.enums.YesOrNoEnum;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* 读取项目相关配置
*/
@Component
@Configuration
@ConfigurationProperties(prefix = "platform")
public class PlatformConfig {
/**
* 上传路径
*/
public static String ROOT_PATH;
/**
* 文件预览
*/
public static String PREVIEW = "/preview/**";
/**
* 图标
*/
public static String FAVICON = "/favicon.ico";
/**
* token超时时间分钟
*/
public static Integer TIMEOUT;
/**
* 是否开启短信
*/
public static YesOrNoEnum SMS;
@Value("${platform.timeout}")
public void setTokenTimeout(Integer timeout) {
PlatformConfig.TIMEOUT = timeout;
}
@Value("${platform.sms:N}")
public void setSms(String sms) {
PlatformConfig.SMS = EnumUtils.toEnum(YesOrNoEnum.class, sms, YesOrNoEnum.NO);
}
@Value("${platform.rootPath}")
public void setRootPath(String rootPath) {
PlatformConfig.ROOT_PATH = rootPath;
}
}

View File

@@ -0,0 +1,64 @@
package com.platform.common.config;
import cn.hutool.core.io.file.FileNameUtil;
import com.platform.common.version.DeviceInterceptor;
import com.platform.common.version.VersionHandlerMapping;
import com.platform.common.version.VersionInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.annotation.Resource;
import java.util.List;
/**
* 通用配置
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Resource
private VersionInterceptor versionInterceptor;
@Resource
private DeviceInterceptor deviceInterceptor;
@Override
public RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
return new VersionHandlerMapping();
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(ApplicationConfig.objectMapper());
}
@Value("${platform.rootPath}")
private String rootPath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// favicon.ico
registry.addResourceHandler(PlatformConfig.FAVICON).addResourceLocations("classpath:/static/");
// file
registry.addResourceHandler(PlatformConfig.PREVIEW).addResourceLocations("file:" + rootPath + FileNameUtil.UNIX_SEPARATOR);
}
/**
* 自定义拦截规则
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(versionInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(PlatformConfig.FAVICON, PlatformConfig.PREVIEW);
registry.addInterceptor(deviceInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(PlatformConfig.FAVICON, PlatformConfig.PREVIEW);
}
}

View File

@@ -0,0 +1,183 @@
package com.platform.common.constant;
/**
* 通用常量信息
*/
public class AppConstants {
/**
* user_id
*/
public static final String USER_ID = "user_id";
/**
* 默认头像
*/
public static final String DEFAULT_PORTRAIT = "http://q3z3-im.oss-cn-beijing.aliyuncs.com/61bed1c563de173eb00e8d8c.png";
/**
* 注销昵称
*/
public static final String DELETED_NICK_NAME = "已注销";
/**
* 用户二维码
*/
public static final String QR_CODE_USER = "user:";
/**
* 群组二维码
*/
public static final String QR_CODE_GROUP = "group:";
/**
* 视频参数
*/
public static final String VIDEO_PARAM = "?x-oss-process=video/snapshot,t_1000,f_png,w_600,m_fast";
/**
* 新建群名称:好友创建通知
*/
public static final String GROUP_CREATE_NAME = "{}的群聊-{}";
/**
* 不是你的好友
*/
public static final String FRIEND_NOT_EXIST = "对方不是你的好友";
/**
* 通知:好友创建通知
*/
public static final String NOTICE_FRIEND_CREATE = "你们已经是好友啦,现在开始聊天吧";
/**
* 通知:群组创建通知
*/
public static final String NOTICE_GROUP_CREATE_MASTER = "你邀请{}加入了群聊";
/**
* 通知:群组创建通知
*/
public static final String NOTICE_GROUP_CREATE_MEMBER = "{}邀请你加入了群聊";
/**
* 通知:群组退出通知
*/
public static final String NOTICE_GROUP_LOGOUT = "{}退出了群聊";
/**
* 通知:群组转让通知
*/
public static final String NOTICE_GROUP_TRANSFER = "你已成为新群主";
/**
* 通知:群组加入通知
*/
public static final String NOTICE_GROUP_JOIN = "{}加入了群聊";
/**
* 通知:群组踢出通知
*/
public static final String NOTICE_GROUP_KICKED_MASTER = "{}被移出群聊";
/**
* 通知:群组踢出通知
*/
public static final String NOTICE_GROUP_KICKED_MEMBER = "你被移出群聊";
/**
* 通知:群组解散通知
*/
public static final String NOTICE_GROUP_DISSOLVE = "群主解散了群聊";
/**
* 通知:群组改名
*/
public static final String NOTICE_GROUP_EDIT = "{}修改群名为“{}”";
/**
* 通知:群组公告
*/
public static final String NOTICE_GROUP_NOTICE = "{}修改群公告为“{}”";
/**
* 帖子_通知
*/
public static final String REDIS_TOPIC_NOTICE = "topic:notice:";
/**
* 帖子_回复
*/
public static final String REDIS_TOPIC_REPLY = "topic:reply:";
/**
* 好友_通知
*/
public static final String REDIS_FRIEND_NOTICE = "friend:notice:";
/**
* redis_附近的人
*/
public static final String REDIS_NEAR = "chat:near";
/**
* redis_摇一摇
*/
public static final String REDIS_SHAKE = "chat:shake";
/**
* redis_摇一摇
*/
public static final String REDIS_GEO = "chat:geo";
/**
* redis_消息
*/
public static final String REDIS_MSG = "chat:msg:{}:{}";
/**
* redis_消息(天数)
*/
public static final Integer REDIS_MSG_TIME = 7;
/**
* redis_天气
*/
public static final String REDIS_MP_WEATHER = "mp:weather:{}:{}";
/**
* redis_天气(分钟)
*/
public static final Integer REDIS_MP_WEATHER_TIME = 30;
/**
* redis_好友
*/
public static final String REDIS_FRIEND = "chat:friend:{}:{}";
/**
* redis_好友(天)
*/
public static final Integer REDIS_FRIEND_TIME = 60;
/**
* redis_群组
*/
public static final String REDIS_GROUP_INFO = "chat:group:{}:{}";
/**
* redis_群组(天)
*/
public static final Integer REDIS_GROUP_TIME = 60;
/**
* redis_实时音视频_签名
*/
public static final String REDIS_TRTC_SIGN = "chat:trtc:sign:";
/**
* redis_实时音视频_用户前缀
*/
public static final String REDIS_TRTC_USER = "u";
}

View File

@@ -0,0 +1,43 @@
package com.platform.common.constant;
/**
* 头部常量
*/
public class HeadConstant {
/**
* 登录用户 redis key
*/
public static final String TOKEN_REDIS_APP = "token:app:";
/**
* 令牌
*/
public static final String TOKEN_HEADER_ADMIN = "Authorization";
/**
* 版本号
*/
public static final String VERSION = "version";
/**
* 签名
*/
public static final String SIGN = "sign";
/**
* appId
*/
public static final String APP_ID = "appId";
/**
* 签名
*/
public static final String TIMESTAMP = "timestamp";
/**
* 设备
*/
public static final String DEVICE = "device";
}

View File

@@ -0,0 +1,89 @@
package com.platform.common.core;
import org.springframework.util.StringUtils;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* 字符集工具类
*/
public class CharsetKit {
/**
* ISO-8859-1
*/
public static final String ISO_8859_1 = "ISO-8859-1";
/**
* UTF-8
*/
public static final String UTF_8 = "UTF-8";
/**
* GBK
*/
public static final String GBK = "GBK";
/**
* ISO-8859-1
*/
public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1);
/**
* UTF-8
*/
public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8);
/**
* GBK
*/
public static final Charset CHARSET_GBK = Charset.forName(GBK);
/**
* 转换为Charset对象
* <p>
* charset 字符集,为空则返回默认字符集
*/
public static Charset charset(String charset) {
return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset);
}
/**
* 转换字符串的字符集编码
* <p>
* source 字符串
* srcCharset 源字符集默认ISO-8859-1
* destCharset 目标字符集默认UTF-8
* 转换后的字符集
*/
public static String convert(String source, String srcCharset, String destCharset) {
return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset));
}
/**
* 转换字符串的字符集编码
* <p>
* source 字符串
* srcCharset 源字符集默认ISO-8859-1
* destCharset 目标字符集默认UTF-8
* 转换后的字符集
*/
public static String convert(String source, Charset srcCharset, Charset destCharset) {
if (null == srcCharset) {
srcCharset = StandardCharsets.ISO_8859_1;
}
if (null == destCharset) {
srcCharset = StandardCharsets.UTF_8;
}
if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) {
return source;
}
return new String(source.getBytes(srcCharset), destCharset);
}
/**
* 系统字符集编码
*/
public static String sysCharset() {
return Charset.defaultCharset().name();
}
}

View File

@@ -0,0 +1,30 @@
package com.platform.common.core;
import cn.hutool.core.util.EnumUtil;
import org.springframework.util.StringUtils;
import java.util.Map;
/**
* 类型转换器
*/
public class EnumUtils {
public static <E extends Enum<E>> E toEnum(Class<E> clazz, String code) {
return toEnum(clazz, code, null);
}
public static <E extends Enum<E>> E toEnum(Class<E> clazz, String code, E defaultValue) {
if (StringUtils.isEmpty(code)) {
return defaultValue;
}
Map<String, Object> enumMap = EnumUtil.getNameFieldMap(clazz, "code");
for (String key : enumMap.keySet()) {
if (code.equals(enumMap.get(key).toString())) {
return EnumUtil.fromString(clazz, key);
}
}
return defaultValue;
}
}

View File

@@ -0,0 +1,53 @@
package com.platform.common.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
/**
* 设备类型枚举
*/
@Getter
public enum DeviceEnum {
/**
* 安卓
*/
ANDROID("android", "安卓"),
/**
* 苹果
*/
IOS("ios", "苹果"),
/**
* 微软
*/
WINDOWS("windows", "微软"),
/**
* MAC
*/
MAC("mac", "MAC"),
/**
* H5
*/
H5("h5", "h5"),
/**
* PC
*/
PC("pc", "PC"),
/**
* 小程序
*/
MINI("mini", "小程序"),
;
@EnumValue
@JsonValue
private final String code;
private final String info;
DeviceEnum(String code, String info) {
this.code = code;
this.info = info;
}
}

View File

@@ -0,0 +1,36 @@
package com.platform.common.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
/**
* 性别类型枚举
*/
@Getter
public enum GenderEnum {
/**
* 未知
*/
UNKNOWN("0", "未知"),
/**
* 男
*/
MALE("1", ""),
/**
* 女
*/
FEMALE("2", "");
@EnumValue
@JsonValue
private final String code;
private final String info;
GenderEnum(String code, String info) {
this.code = code;
this.info = info;
}
}

View File

@@ -0,0 +1,62 @@
package com.platform.common.enums;
import lombok.Getter;
/**
* 返回码枚举
*/
@Getter
public enum ResultCodeEnum {
/**
* 操作成功
*/
SUCCESS(200, "操作成功"),
/**
* 未授权
*/
UNAUTHORIZED(401, "登录已过期,请重新登录"),
/**
* 无访问权限
*/
AUTH(403, "无访问权限"),
/**
* 客户证书已过期或无效
*/
CERTIFICATE(40317, "客户证书已过期或无效"),
/**
* 资源/服务未找到
*/
NOT_FOUND(404, "路径不存在,请检查路径是否正确"),
/**
* 操作失败
*/
FAIL(500, "系统异常,请联系管理员"),
/**
* 版本号
*/
VERSION(601, "版本过低,请升级"),
/**
* 安全验证
*/
SECURITY(602, "安全验证"),
;
private final Integer code;
private final String info;
ResultCodeEnum(Integer code, String info) {
this.code = code;
this.info = info;
}
public static ResultCodeEnum init(Integer code) {
for (ResultCodeEnum resultCode : ResultCodeEnum.values()) {
if (resultCode.getCode().equals(code)) {
return resultCode;
}
}
return ResultCodeEnum.FAIL;
}
}

View File

@@ -0,0 +1,45 @@
package com.platform.common.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
/**
* 是否类型枚举
*/
@Getter
public enum YesOrNoEnum {
/**
* 是
*/
YES("Y", ""),
/**
* 否
*/
NO("N", ""),
/**
* 退
*/
REFUND("R", "退"),
;
@EnumValue
@JsonValue
private final String code;
private final String info;
YesOrNoEnum(String code, String info) {
this.code = code;
this.info = info;
}
public static boolean transform(YesOrNoEnum result) {
return YesOrNoEnum.YES.equals(result);
}
public static YesOrNoEnum transform(boolean result) {
return result ? YesOrNoEnum.YES : YesOrNoEnum.NO;
}
}

View File

@@ -0,0 +1,48 @@
package com.platform.common.exception;
import com.platform.common.enums.ResultCodeEnum;
import com.platform.common.utils.MessageUtils;
import lombok.Getter;
/**
* 自定义异常
*/
public class BaseException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
@Getter
private ResultCodeEnum resultCode;
/**
* 错误消息
*/
private String message;
public BaseException(String message) {
this.message = message;
}
public BaseException(ResultCodeEnum resultCode) {
this.resultCode = resultCode;
this.message = resultCode.getInfo();
}
public BaseException(ResultCodeEnum resultCode, String message) {
this.resultCode = resultCode;
this.message = message;
}
@Override
public String getMessage() {
String message = this.message;
if (1 + 1 != 2) {
message = MessageUtils.message("", "");
}
return message;
}
}

View File

@@ -0,0 +1,20 @@
package com.platform.common.exception;
import com.platform.common.enums.ResultCodeEnum;
import lombok.Getter;
import org.apache.shiro.authc.AuthenticationException;
/**
* 登录异常
*/
public class LoginException extends AuthenticationException {
@Getter
private ResultCodeEnum code;
public LoginException(ResultCodeEnum resultCode) {
super(resultCode.getInfo());
this.code = resultCode;
}
}

View File

@@ -0,0 +1,148 @@
package com.platform.common.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* GeoHash工具类
*/
@Component
public class GeoHashUtils {
@Autowired
private RedisTemplate redisTemplate;
/***
* 将指定的地理空间位置纬度、经度、名称添加到指定的key中。
* @param key redis的key
* @param longitude 经度
* @param latitude 纬度
* @param name 名称
* @return
*/
public Long add(String key, double longitude, double latitude, String name) {
// Long addedNum = redisTemplate.opsForGeo().add("city", new Point(121.47, 31.23), "上海");
// Long addedNum = redisTemplate.opsForGeo().add("city", new Point(113.27, 23.13), "广州");
return redisTemplate.opsForGeo().add(key, new Point(longitude, latitude), name);
}
/***
* 将指定的地理空间位置纬度、经度、名称添加到指定的key中。
* @param key redis的key
* @param map 名称 - 经度 - 纬度
* @return
*/
public Long add(String key, Map<String, Point> map) {
return redisTemplate.opsForGeo().add(key, map);
}
/***
* 将指定的地理空间移除。
* @param key redis的key
* @param name 名称
* @return
*/
public Long remove(String key, String... name) {
return redisTemplate.opsForGeo().remove(key, name);
}
/***
* 将指定的地理空间移除。
* @param key redis的key
* @param nameList 名称集合
* @return
*/
public Long remove(String key, List<String> nameList) {
return redisTemplate.opsForGeo().remove(key, nameList.toArray());
}
/***
* 从key里返回所有给定位置元素的位置经度和纬度
* @param key redis的key
* @param name 名称
*/
public List<Point> get(String key, String... name) {
List<Point> points = redisTemplate.opsForGeo().position(key, name);//params: key, 地方名称...
return points;
}
/***
* 从key里返回所有给定位置元素的位置经度和纬度
* @param key redis的key
* @param nameList 名称的集合
*/
public List<Point> get(String key, List<String> nameList) {
List<Point> points = redisTemplate.opsForGeo().position(key, nameList.toArray());
return points;
}
/***
* 返回两个给定位置之间的距离。
* @param key redis的key
* @param name1 地方名称1
* @param name2 地方名称2
* @return
*/
public Distance dist(String key, String name1, String name2) {
Distance distance = redisTemplate.opsForGeo()
.distance(key, name1, name2, RedisGeoCommands.DistanceUnit.KILOMETERS);
return distance;
}
/***
* 以给定的城市为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素,并给出所有位置元素与中心的平均距离。
* @param key redis的key
* @param name 名称
* @param distance 距离
* @param count 人数
* @return
*/
public List<GeoResult<GeoVo>> radius(String key, String name, Integer distance, Integer count) {
Distance distances = new Distance(distance, Metrics.KILOMETERS);
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(count);
return redisTemplate.opsForGeo().radius(key, name, distances, args).getContent();
}
/***
* 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素,并给出所有位置元素与中心的平均距离。
* @param key redis的key
* @param longitude 经度
* @param latitude 纬度
* @param distance 距离
* @param count 人数
* @return
*/
public List<GeoResult<GeoVo>> radius(String key, double longitude, double latitude, Integer distance, Integer count) {
Circle circle = new Circle(new Point(longitude, latitude), new Distance(distance, Metrics.KILOMETERS));
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(count);
return redisTemplate.opsForGeo().radius(key, circle, args).getContent();
}
/***
* 返回一个或多个位置元素的 Geohash 表示
* @param key redis的key
* @param name 名称的集合
*/
public List<String> hash(String key, String... name) {
List<String> results = redisTemplate.opsForGeo().hash(key, name);
return results;
}
/***
* 返回一个或多个位置元素的 Geohash 表示
* @param key redis的key
* @param nameList 名称的集合
*/
public List<String> hash(String key, List<String> nameList) {
List<String> results = redisTemplate.opsForGeo().hash(key, nameList.toArray());
return results;
}
}

View File

@@ -0,0 +1,10 @@
package com.platform.common.redis;
import lombok.Data;
@Data
public class GeoVo {
private String name;
}

View File

@@ -0,0 +1,31 @@
package com.platform.common.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* redis配置
*/
@Configuration
@EnableCaching
public class RedisConfig {
@Autowired
private RedisTemplate redisTemplate;
@Bean
public RedisTemplate<String, Object> stringSerializerRedisTemplate() {
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(stringSerializer);
return redisTemplate;
}
}

View File

@@ -0,0 +1,19 @@
package com.platform.common.redis;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
@Configuration
public class RedisListenerConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//container.addMessageListener(new RedisExpiredListener(), new PatternTopic("__keyevent@0__:expired"));
return container;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
package com.platform.common.shiro;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 登录用户身份权限
*/
@Data
@Accessors(chain = true) // 链式调用
public class LoginUser {
/**
* 用户唯一标识
*/
private String token;
/**
* 用户ID
*/
private Long userId;
/**
* 用户手机号
*/
private String phone;
/**
* 登录IP地址
*/
private String ipAddr;
}

View File

@@ -0,0 +1,49 @@
package com.platform.common.shiro;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.SecureUtil;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.util.ByteSource;
/**
* Md5Util
*/
public class Md5Utils {
/**
* credentials
*/
public static final String credentials(String password, String salt) {
//盐:为了即使相同的密码不同的盐加密后的结果也不同
ByteSource byteSalt = ByteSource.Util.bytes(salt);
Md5Hash result = new Md5Hash(password, byteSalt);
return result.toString();
}
/**
* md5
*/
public static final String md5(String str) {
return SecureUtil.md5(str);
}
/**
* 加密盐
*/
public static String salt() {
return RandomUtil.randomString(4);
}
/**
* 基础密码
*/
private static final String baseStr = "abcdefghgkmnprstwxyz123456789";
/**
* 密码
*/
public static String password() {
return RandomUtil.randomString(baseStr, 8);
}
}

View File

@@ -0,0 +1,110 @@
package com.platform.common.shiro;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* ShiroConfiguration
*/
@Configuration
public class ShiroConfiguration {
/**
* 下面两个方法对 注解权限起作用有很大的关系,请把这两个方法,放在配置的最上面
*/
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 身份认证Realm此处的注入不可以缺少。否则会在UserRealm中注入对象会报空指针.
* 将自己的验证方式加入容器
*/
@Bean
public ShiroRealm shiroRealm() {
ShiroRealm realm = new ShiroRealm();
realm.setCredentialsMatcher(hashedCredentialsMatcher());
return realm;
}
/**
* 配置shiro session 的一个管理器
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionListeners(new ArrayList<>());
return sessionManager;
}
/**
* 核心的安全事务管理器
* 设置realm、cacheManager等
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm
securityManager.setRealm(shiroRealm());
// 设置sessionManager
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;否则@RequiresRoles等注解无法生效
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 哈希密码比较器。在myShiroRealm中作用参数使用
* 登陆时会比较用户输入的密码跟数据库密码配合盐值salt解密后是否一致。
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用md5算法;
hashedCredentialsMatcher.setHashIterations(1);//散列的次数,比如散列两次,相当于 md5( md5(""));
return hashedCredentialsMatcher;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//oauth过滤
Map<String, Filter> filters = new HashMap<>(16);
filters.put("oauth2", new ShiroTokenFilter());
shiroFilterFactoryBean.setFilters(filters);
//权限控制map
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 自定义拦截全部写到下面↓↓↓
// 免登录接口,增加@IgnoreAuth注解
// 自定义拦截全部写到上面↑↑↑
filterChainDefinitionMap.put("/**", "oauth2");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
}

View File

@@ -0,0 +1,30 @@
package com.platform.common.shiro;
import lombok.Data;
import org.apache.shiro.authc.AuthenticationToken;
/**
* token
*/
@Data
public class ShiroLoginAuth implements AuthenticationToken {
private String phone;
private char[] password;
public ShiroLoginAuth(String phone, String password) {
this.phone = phone;
this.password = password.toCharArray();
}
@Override
public Object getPrincipal() {
return phone;
}
@Override
public Object getCredentials() {
return password;
}
}

View File

@@ -0,0 +1,28 @@
package com.platform.common.shiro;
import lombok.Data;
import org.apache.shiro.authc.AuthenticationToken;
/**
* token
*/
@Data
public class ShiroLoginPhone implements AuthenticationToken {
private String phone;
public ShiroLoginPhone(String phone) {
this.phone = phone;
}
@Override
public Object getPrincipal() {
return phone;
}
@Override
public Object getCredentials() {
return phone;
}
}

View File

@@ -0,0 +1,26 @@
package com.platform.common.shiro;
import org.apache.shiro.authc.AuthenticationToken;
/**
* token
*/
public class ShiroLoginToken implements AuthenticationToken {
private static final long serialVersionUID = 1L;
private String token;
public ShiroLoginToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}

View File

@@ -0,0 +1,129 @@
package com.platform.common.shiro;
import cn.hutool.core.util.RandomUtil;
import com.platform.common.enums.ResultCodeEnum;
import com.platform.common.enums.YesOrNoEnum;
import com.platform.common.exception.LoginException;
import com.platform.common.utils.IpUtils;
import com.platform.common.utils.ServletUtils;
import com.platform.modules.auth.service.TokenService;
import com.platform.modules.chat.domain.ChatUser;
import com.platform.modules.chat.service.ChatUserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.context.annotation.Lazy;
import javax.annotation.Resource;
/**
* ShiroRealm
*/
public class ShiroRealm extends AuthorizingRealm {
@Lazy
@Resource
private TokenService tokenService;
@Lazy
@Resource
private ChatUserService chatUserService;
/**
* 提供用户信息,返回权限信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Object object = ShiroUtils.getSubject().getPrincipal();
if (object == null) {
return null;
}
// 后台管理
if (object instanceof LoginUser) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
return info;
}
return null;
}
/**
* 必须重写此方法,不然会报错
*/
@Override
public boolean supports(AuthenticationToken authenticationToken) {
return authenticationToken instanceof ShiroLoginToken
|| authenticationToken instanceof ShiroLoginAuth
|| authenticationToken instanceof ShiroLoginPhone;
}
/**
* 身份认证/登录,验证用户是不是拥有相应的身份; 用于登陆认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// token
if (authenticationToken instanceof ShiroLoginToken) {
String token = (String) authenticationToken.getPrincipal();
LoginUser loginUser = tokenService.queryByToken(token);
if (loginUser == null) {
throw new LoginException(ResultCodeEnum.UNAUTHORIZED);
}
// 加密盐
String salt = Md5Utils.salt();
// 对token加密
String credentials = Md5Utils.credentials(token, salt);
return new SimpleAuthenticationInfo(loginUser, credentials, ByteSource.Util.bytes(salt), getName());
}
// 手机+密码登录
if (authenticationToken instanceof ShiroLoginAuth) {
ShiroLoginAuth token = (ShiroLoginAuth) authenticationToken;
return makeLoginUser(token.getPhone(), true);
}
// 手机+验证码登录
if (authenticationToken instanceof ShiroLoginPhone) {
ShiroLoginPhone token = (ShiroLoginPhone) authenticationToken;
return makeLoginUser(token.getPhone(), false);
}
return null;
}
/**
* 组装登录对象
*/
private SimpleAuthenticationInfo makeLoginUser(String phone, boolean isPassword) {
// 查询用户
ChatUser chatUser = chatUserService.queryByPhone(phone);
// 处理用户
if (chatUser == null) {
throw new AuthenticationException("手机号或密码不正确"); // 手机不存在
}
// 查询权限
LoginUser loginUser = new LoginUser()
.setUserId(chatUser.getUserId())
.setPhone(chatUser.getPhone())
.setIpAddr(IpUtils.getIpAddr(ServletUtils.getRequest()))
.setToken(RandomUtil.randomString(32));
String credentials = chatUser.getPassword();
String salt = chatUser.getSalt();
if (!isPassword) {
// 加密盐
salt = Md5Utils.salt();
// 对token加密
credentials = Md5Utils.credentials(phone, salt);
}
// 登录
return new SimpleAuthenticationInfo(loginUser, credentials, ByteSource.Util.bytes(salt), getName());
}
@Override
public boolean isPermitted(PrincipalCollection principals, String permission) {
return super.isPermitted(principals, permission);
}
}

View File

@@ -0,0 +1,126 @@
package com.platform.common.shiro;
import cn.hutool.json.JSONUtil;
import com.platform.common.aspectj.IgnoreAuth;
import com.platform.common.constant.HeadConstant;
import com.platform.common.enums.ResultCodeEnum;
import com.platform.common.exception.BaseException;
import com.platform.common.exception.LoginException;
import com.platform.common.web.domain.AjaxResult;
import lombok.SneakyThrows;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.annotation.Annotation;
/**
* auth2过滤器
*/
public class ShiroTokenFilter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse servletResponse) {
//获取请求token
ShiroLoginToken token = getToken(request);
if (token == null) {
return null;
}
return token;
}
@SneakyThrows
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if (RequestMethod.OPTIONS.name().equalsIgnoreCase(httpServletRequest.getMethod())) {
return true;
}
if ("/favicon.ico".equals(httpServletRequest.getRequestURI())) {
return error(request, response, ResultCodeEnum.SUCCESS);
}
try {
WebApplicationContext ctx = RequestContextUtils.findWebApplicationContext(httpServletRequest);
RequestMappingHandlerMapping mapping = ctx.getBean(RequestMappingHandlerMapping.class);
HandlerExecutionChain handler = mapping.getHandler(httpServletRequest);
if (handler == null) {
return true;
}
Annotation[] declaredAnnotations = ((HandlerMethod) handler.getHandler()).getMethod().getDeclaredAnnotations();
if (declaredAnnotations.length != 0) {
for (Annotation annotation : declaredAnnotations) {
if (IgnoreAuth.class.equals(annotation.annotationType())) {
return true;
}
}
}
} catch (BaseException e) {
return error(request, response, e.getResultCode());
} catch (Exception e) {
return error(request, response, ResultCodeEnum.FAIL);
}
return false;
}
private boolean error(ServletRequest request, ServletResponse response, ResultCodeEnum resultCode) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
try {
// 请求转发401controller
httpServletRequest.getRequestDispatcher("/error/" + resultCode.getCode()).forward(request, response);
} catch (Exception e) {
}
return true;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
// 请求token
ShiroLoginToken token = getToken(request);
if (token == null) {
return error(response, ResultCodeEnum.UNAUTHORIZED, null);
}
try {
getSubject(request, response).login(token);
return true;
} catch (LoginException e) {
return error(response, ResultCodeEnum.UNAUTHORIZED, e.getMessage());
} catch (AuthenticationException e) {
return error(response, ResultCodeEnum.UNAUTHORIZED, null);
}
}
private boolean error(ServletResponse response, ResultCodeEnum resultCode, String msg) throws IOException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.getWriter().print(JSONUtil.toJsonStr(AjaxResult.result(resultCode, msg)));
return false;
}
/**
* 请求的token
*/
private ShiroLoginToken getToken(ServletRequest request) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String token = httpRequest.getHeader(HeadConstant.TOKEN_HEADER_ADMIN);
if (StringUtils.isEmpty(token)) {
token = httpRequest.getParameter(HeadConstant.TOKEN_HEADER_ADMIN);
}
if (!StringUtils.isEmpty(token)) {
return new ShiroLoginToken(token);
}
return null;
}
}

View File

@@ -0,0 +1,45 @@
package com.platform.common.shiro;
import com.platform.common.constant.HeadConstant;
import com.platform.common.utils.ServletUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
/**
* Shiro工具类
*/
public class ShiroUtils {
public static Subject getSubject() {
return SecurityUtils.getSubject();
}
public static LoginUser getLoginUser() {
return (LoginUser) getSubject().getPrincipal();
}
/**
* 是否登录
*/
public static boolean isLogin() {
return getLoginUser() != null;
}
public static String getToken() {
LoginUser loginUser = getLoginUser();
if (loginUser != null) {
return loginUser.getToken();
}
return ServletUtils.getRequest().getHeader(HeadConstant.TOKEN_HEADER_ADMIN);
}
public static String getPhone() {
return getLoginUser().getPhone();
}
public static Long getUserId() {
return getLoginUser().getUserId();
}
}

View File

@@ -0,0 +1,45 @@
package com.platform.common.upload.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 文件上传
*/
@Component
@Data
public class UploadConfig {
/**
* 服务端域名
*/
@Value("${upload.serverUrl}")
private String serverUrl;
/**
* accessKey
*/
@Value("${upload.accessKey}")
private String accessKey;
/**
* secretKey
*/
@Value("${upload.secretKey}")
private String secretKey;
/**
* bucket
*/
@Value("${upload.bucket}")
private String bucket;
/**
* region
*/
@Value("${upload.region}")
private String region;
/**
* 封面
*/
@Value("${upload.post}")
private String post;
}

View File

@@ -0,0 +1,53 @@
package com.platform.common.upload.enums;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
/**
* 上传类型枚举
*/
@Getter
public enum UploadTypeEnum {
/**
* 阿里oss
*/
OSS("oss", "阿里oss"),
/**
* 腾讯cos
*/
COS("cos", "腾讯cos"),
/**
* 七牛kodo
*/
KODO("kodo", "七牛kodo"),
/**
* FAST
*/
FAST("fast", "FAST_DFS"),
/**
* MINIO
*/
MINIO("minio", "MINIO"),
/**
* 本地local
*/
LOCAL("local", "本地上传"),
;
@JsonValue
private final String code;
private final String info;
UploadTypeEnum(String code, String info) {
this.code = code;
this.info = info;
}
}

View File

@@ -0,0 +1,54 @@
package com.platform.common.upload.service;
import cn.hutool.core.lang.Dict;
import com.platform.common.upload.vo.UploadFileVo;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.InputStream;
/**
* 上传服务
*/
public interface UploadService {
/**
* 获取服务端域名
*/
String getServerUrl();
/**
* 获取上传凭证
*/
Dict getToken(String fileType);
/**
* 文件上传
*/
UploadFileVo uploadFile(MultipartFile file);
/**
* 文件上传
*/
UploadFileVo uploadFile(MultipartFile file, String folder);
/**
* 文件上传
*/
UploadFileVo uploadFile(File file);
/**
* 文件上传
*/
UploadFileVo uploadFile(File file, String folder);
/**
* 获取文件流
*/
InputStream getInputStream(String urlPath);
/**
* 删除本地文件
*/
boolean delFile(File file);
}

View File

@@ -0,0 +1,171 @@
package com.platform.common.upload.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.IdUtil;
import com.platform.common.upload.vo.UploadFileVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* 基础上传
*/
@Slf4j
public class UploadBaseService {
/**
* 获取文件名称
*/
protected static String getFileName(MultipartFile file) {
String fileName = file.getOriginalFilename();
if (StringUtils.isEmpty(fileName)) {
// 随机名称
fileName = IdUtil.objectId() + "." + getFileType(file);
}
return fileName;
}
/**
* 获取文件名称
*/
protected static String getFileName(File file) {
String fileName = file.getName();
if (StringUtils.isEmpty(fileName)) {
// 随机名称
fileName = IdUtil.objectId() + "." + getFileType(file);
}
return fileName;
}
/**
* 获取文件全名
*/
protected static String getFileKey(MultipartFile file) {
return IdUtil.objectId() + "." + getFileType(file);
}
/**
* 获取文件全名
*/
protected static String getFileKey(MultipartFile file, String folder) {
String fileKey = IdUtil.objectId() + "." + getFileType(file);
if (StringUtils.isEmpty(folder)) {
return fileKey;
}
return folder + "/" + fileKey;
}
/**
* 获取文件全名
*/
protected static String getFileKey(File file) {
return IdUtil.objectId() + "." + getFileType(file);
}
/**
* 获取文件全名
*/
protected static String getFileKey(File file, String folder) {
String fileKey = IdUtil.objectId() + "." + getFileType(file);
if (StringUtils.isEmpty(folder)) {
return fileKey;
}
return folder + "/" + fileKey;
}
/**
* 获取文件全名
*/
protected static String getFileKey(String uploadPath, String fileType) {
// 文件路径
String filePath = DateUtil.format(DateUtil.date(), "yyyy/MM/dd");
// 生成文件夹
FileUtil.mkdir(uploadPath + FileNameUtil.UNIX_SEPARATOR + filePath);
return filePath + FileNameUtil.UNIX_SEPARATOR + IdUtil.objectId() + "." + fileType;
}
/**
* 获取文件全名
*/
protected static String getFileType(MultipartFile file) {
// 文件扩展名
String fileType = FileNameUtil.extName(file.getOriginalFilename());
if (StringUtils.isEmpty(fileType)) {
// 文件后缀
try {
fileType = FileTypeUtil.getType(file.getInputStream());
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
return fileType;
}
/**
* 获取文件全名
*/
protected static String getFileType(File file) {
// 文件扩展名
String fileType = FileNameUtil.extName(file.getName());
if (StringUtils.isEmpty(fileType)) {
// 文件后缀
fileType = FileTypeUtil.getType(file);
}
if (StringUtils.isEmpty(fileType)) {
fileType = "txt";
}
return fileType;
}
/**
* 获取文件流
*/
public InputStream getInputStream(String urlPath) {
try {
URL url = new URL(urlPath);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
// 设置网络连接超时时间
httpURLConnection.setConnectTimeout(5000);
// 设置应用程序要从网络连接读取数据
httpURLConnection.setDoInput(true);
// 从服务器返回一个输入流
return httpURLConnection.getInputStream();
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException("获取文件流失败");
}
}
/**
* 封装对象
*/
protected static UploadFileVo format(String fileName, String serverUrl, String fileKey, String fileType) {
// 服务器地址
UploadFileVo fileVo = new UploadFileVo()
.setFileName(fileName)
.setFullPath(serverUrl + FileNameUtil.UNIX_SEPARATOR + fileKey)
.setFileType(fileType);
return fileVo;
}
/**
* 删除本地文件
*/
public boolean delFile(File file) {
try {
return FileUtil.del(file);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException("删除上传失败");
}
}
}

View File

@@ -0,0 +1,154 @@
package com.platform.common.upload.service.impl;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.platform.common.upload.config.UploadConfig;
import com.platform.common.upload.enums.UploadTypeEnum;
import com.platform.common.upload.service.UploadService;
import com.platform.common.upload.vo.UploadFileVo;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.region.Region;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.InputStream;
/**
* 腾讯云上传
*/
@Slf4j
@Service("uploadCosService")
@Configuration
@ConditionalOnProperty(prefix = "upload", name = "uploadType", havingValue = "cos")
public class UploadCosServiceImpl extends UploadBaseService implements UploadService {
@Resource
private UploadConfig uploadConfig;
/**
* 初始化cos
*/
private COSClient initCOS() {
return new COSClient(new BasicCOSCredentials(uploadConfig.getAccessKey(), uploadConfig.getSecretKey()),
new com.qcloud.cos.ClientConfig(new Region(uploadConfig.getRegion())));
}
@Override
public String getServerUrl() {
return uploadConfig.getServerUrl();
}
@Override
public Dict getToken(String fileType) {
// 1、默认固定值
Long expire = 1800L;
String algorithm = "sha1";
String accessKey = uploadConfig.getAccessKey();
String post = uploadConfig.getPost();
String serverUrl = uploadConfig.getServerUrl();
// 2、生成KeyTime
Long startTime = DateUtil.currentSeconds();
Long endTime = startTime + expire;
String keyTime = StrUtil.format("{};{}", startTime, endTime);
// 3、构造“策略”Policy
JSONArray conditions = new JSONArray()
.set(new JSONObject().set("q-sign-algorithm", algorithm))
.set(new JSONObject().set("q-ak", accessKey))
.set(new JSONObject().set("q-sign-time", keyTime));
JSONObject policyObj = new JSONObject()
.set("expiration", DateUtil.format(DateUtil.date(endTime * 1000), DatePattern.UTC_MS_PATTERN))
.set("conditions", conditions);
String policy = JSONUtil.toJsonStr(policyObj);
// 4、生成 SignKey
String signKey = SecureUtil.hmacSha1(uploadConfig.getSecretKey()).digestHex(keyTime);
// 5、生成 Signature
String signature = SecureUtil.hmacSha1(signKey).digestHex(SecureUtil.sha1(policy));
return Dict.create()
.set("uploadType", UploadTypeEnum.COS)
.set("serverUrl", serverUrl)
.set("fileName", IdUtil.objectId() + "." + fileType)
.set("accessKey", accessKey)
.set("policy", Base64.encode(policy))
.set("signature", signature)
.set("keyTime", keyTime)
.set("algorithm", algorithm)
.set("post", post);
}
@Override
public UploadFileVo uploadFile(MultipartFile file) {
return uploadFile(file, null);
}
@Override
public UploadFileVo uploadFile(MultipartFile file, String folder) {
String fileName = getFileName(file);
String fileKey = getFileKey(file, folder);
String fileType = getFileType(file);
// 3 生成 cos 客户端。
COSClient client = null;
try {
client = initCOS();
//上传到腾讯云
PutObjectRequest putObjectRequest = new PutObjectRequest(uploadConfig.getBucket()
, fileKey, file.getInputStream(), new ObjectMetadata());
client.putObject(putObjectRequest);
return format(fileName, uploadConfig.getServerUrl(), fileKey, fileType);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException("文件上传失败");
} finally {
if (client != null) {
client.shutdown();
}
}
}
@Override
public UploadFileVo uploadFile(File file) {
return uploadFile(file, null);
}
@Override
public UploadFileVo uploadFile(File file, String folder) {
String fileName = getFileName(file);
String fileKey = getFileKey(file, folder);
String fileType = getFileType(file);
InputStream inputStream = FileUtil.getInputStream(file);
// 3 生成 cos 客户端。
COSClient client = null;
try {
client = initCOS();
//上传到腾讯云
PutObjectRequest putObjectRequest = new PutObjectRequest(uploadConfig.getBucket()
, fileKey, inputStream, new ObjectMetadata());
client.putObject(putObjectRequest);
return format(fileName, uploadConfig.getServerUrl(), fileKey, fileType);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException("文件上传失败");
} finally {
if (client != null) {
client.shutdown();
}
}
}
}

View File

@@ -0,0 +1,92 @@
package com.platform.common.upload.service.impl;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.IdUtil;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.platform.common.upload.config.UploadConfig;
import com.platform.common.upload.enums.UploadTypeEnum;
import com.platform.common.upload.service.UploadService;
import com.platform.common.upload.utils.FastUtils;
import com.platform.common.upload.vo.UploadFileVo;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
/**
* fast上传
*/
@Slf4j
@Service("uploadFastService")
@Configuration
@NoArgsConstructor
@ConditionalOnProperty(prefix = "upload", name = "uploadType", havingValue = "fast")
public class UploadFastServiceImpl extends UploadBaseService implements UploadService {
@Resource
private UploadConfig uploadConfig;
@Override
public String getServerUrl() {
return uploadConfig.getServerUrl();
}
@Override
public Dict getToken(String fileType) {
String serverUrl = uploadConfig.getServerUrl();
String post = uploadConfig.getPost();
return Dict.create()
.set("uploadType", UploadTypeEnum.LOCAL)
.set("serverUrl", serverUrl)
.set("fileName", IdUtil.objectId() + "." + fileType)
.set("post", post);
}
@Override
public UploadFileVo uploadFile(MultipartFile file) {
return uploadFile(file, null);
}
@Override
public UploadFileVo uploadFile(MultipartFile file, String folder) {
StorePath storePath;
try {
storePath = FastUtils.uploadFile(file);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException("文件上传失败");
}
String fileKey = storePath.getFullPath();
String fileName = getFileName(file);
String fileType = getFileType(file);
String serverUrl = uploadConfig.getServerUrl();
return format(fileName, serverUrl, fileKey, fileType);
}
@Override
public UploadFileVo uploadFile(File file) {
return uploadFile(file, null);
}
@Override
public UploadFileVo uploadFile(File file, String folder) {
StorePath storePath;
try {
storePath = FastUtils.uploadFile(file);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException("文件上传失败");
}
String fileKey = storePath.getFullPath();
String fileType = getFileType(file);
String fileName = getFileName(file);
String serverUrl = uploadConfig.getServerUrl();
return format(fileName, serverUrl, fileKey, fileType);
}
}

View File

@@ -0,0 +1,130 @@
package com.platform.common.upload.service.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.IdUtil;
import com.platform.common.upload.config.UploadConfig;
import com.platform.common.upload.enums.UploadTypeEnum;
import com.platform.common.upload.service.UploadService;
import com.platform.common.upload.vo.UploadFileVo;
import com.qiniu.http.Response;
import com.qiniu.storage.UploadManager;
import com.qiniu.util.Auth;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.InputStream;
/**
* 七牛云上传
*/
@Slf4j
@Service("uploadKodoService")
@Configuration
@ConditionalOnProperty(prefix = "upload", name = "uploadType", havingValue = "kodo")
public class UploadKodoServiceImpl extends UploadBaseService implements UploadService {
@Resource
private UploadConfig uploadConfig;
/**
* 获取Auth
*/
private Auth getAuth() {
return Auth.create(uploadConfig.getAccessKey(), uploadConfig.getSecretKey());
}
/**
* 获取Token
*/
private String getUploadToken(String fileType) {
return getAuth().uploadToken(uploadConfig.getBucket(), fileType);
}
@Override
public String getServerUrl() {
return uploadConfig.getServerUrl();
}
@Override
public Dict getToken(String fileType) {
String serverUrl = uploadConfig.getServerUrl();
String region = uploadConfig.getRegion();
String post = uploadConfig.getPost();
String fileName = IdUtil.objectId();
String token;
if (StringUtils.isEmpty(fileType)) {
token = getUploadToken(fileType);
} else {
fileName += "." + fileType;
token = getUploadToken(fileName);
}
return Dict.create()
.set("uploadType", UploadTypeEnum.KODO)
.set("serverUrl", serverUrl)
.set("fileName", fileName)
.set("region", region)
.set("uploadToken", token)
.set("post", post);
}
@Override
public UploadFileVo uploadFile(MultipartFile file) {
return uploadFile(file, null);
}
@Override
public UploadFileVo uploadFile(MultipartFile file, String folder) {
String fileName = getFileName(file);
String fileKey = getFileKey(file, folder);
String fileType = getFileType(file);
String token = getUploadToken(fileKey);
Response response = null;
try {
UploadManager uploadManager = new UploadManager(new com.qiniu.storage.Configuration());
response = uploadManager.put(file.getInputStream(), fileKey, token, null, fileType);
return format(fileName, uploadConfig.getServerUrl(), fileKey, fileType);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException("文件上传失败");
} finally {
if (response != null) {
response.close();
}
}
}
@Override
public UploadFileVo uploadFile(File file) {
return uploadFile(file, null);
}
@Override
public UploadFileVo uploadFile(File file, String folder) {
String fileName = getFileName(file);
String fileKey = getFileKey(file, folder);
String fileType = getFileType(file);
String token = getUploadToken(fileKey);
InputStream inputStream = FileUtil.getInputStream(file);
Response response = null;
try {
UploadManager uploadManager = new UploadManager(new com.qiniu.storage.Configuration());
response = uploadManager.put(inputStream, fileKey, token, null, fileType);
return format(fileName, uploadConfig.getServerUrl(), fileKey, fileType);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException("文件上传失败");
} finally {
if (response != null) {
response.close();
}
}
}
}

View File

@@ -0,0 +1,89 @@
package com.platform.common.upload.service.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.lang.Dict;
import com.platform.common.upload.config.UploadConfig;
import com.platform.common.upload.enums.UploadTypeEnum;
import com.platform.common.upload.service.UploadService;
import com.platform.common.upload.vo.UploadFileVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
/**
* 本地上传
*/
@Slf4j
@Service("uploadLocalService")
@Configuration
@ConditionalOnProperty(prefix = "upload", name = "uploadType", havingValue = "local")
public class UploadLocalServiceImpl extends UploadBaseService implements UploadService {
@Resource
private UploadConfig uploadConfig;
@Override
public String getServerUrl() {
return uploadConfig.getServerUrl();
}
@Override
public Dict getToken(String fileType) {
return Dict.create()
.set("uploadType", UploadTypeEnum.LOCAL);
}
@Override
public UploadFileVo uploadFile(MultipartFile file) {
return uploadFile(file, null);
}
@Override
public UploadFileVo uploadFile(MultipartFile file, String folder) {
String serverUrl = uploadConfig.getServerUrl();
String uploadPath = uploadConfig.getRegion();
String fileName = getFileName(file);
String fileType = getFileType(file);
// 文件路径
String fileKey = getFileKey(uploadPath, fileType);
try {
// 文件拷贝
file.transferTo(new File(uploadPath + FileNameUtil.UNIX_SEPARATOR + fileKey));
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException("文件上传失败");
}
// 组装对象
UploadFileVo fileVo = format(fileName, serverUrl, fileKey, fileType)
.setFullPath(serverUrl + fileKey);
return fileVo;
}
@Override
public UploadFileVo uploadFile(File file) {
return uploadFile(file, null);
}
@Override
public UploadFileVo uploadFile(File file, String folder) {
String serverUrl = uploadConfig.getServerUrl();
String uploadPath = uploadConfig.getRegion();
String fileName = getFileName(file);
String fileType = getFileType(file);
// 文件路径
String fileKey = getFileKey(uploadPath, fileType);
// 文件拷贝
FileUtil.copyFile(file, new File(uploadPath + FileNameUtil.UNIX_SEPARATOR + fileKey));
// 组装对象
UploadFileVo fileVo = format(fileName, serverUrl, fileKey, fileType)
.setFullPath(serverUrl + fileKey);
return fileVo;
}
}

View File

@@ -0,0 +1,119 @@
package com.platform.common.upload.service.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.IdUtil;
import com.platform.common.upload.config.UploadConfig;
import com.platform.common.upload.enums.UploadTypeEnum;
import com.platform.common.upload.service.UploadService;
import com.platform.common.upload.vo.UploadFileVo;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
/**
* minio上传
*/
@Slf4j
@Service("uploadMinioService")
@Configuration
@ConditionalOnProperty(prefix = "upload", name = "uploadType", havingValue = "minio")
public class UploadMinioServiceImpl extends UploadBaseService implements UploadService {
@Resource
private UploadConfig uploadConfig;
/**
* 获取上传凭证
*/
private MinioClient initClient() {
return MinioClient.builder()
.endpoint(uploadConfig.getRegion())
.credentials(uploadConfig.getAccessKey(), uploadConfig.getSecretKey())
.build();
}
@Override
public String getServerUrl() {
return uploadConfig.getServerUrl();
}
@Override
public Dict getToken(String fileType) {
String serverUrl = uploadConfig.getServerUrl();
String post = uploadConfig.getPost();
return Dict.create()
.set("uploadType", UploadTypeEnum.LOCAL)
.set("serverUrl", serverUrl)
.set("fileName", IdUtil.objectId() + "." + fileType)
.set("post", post);
}
@Override
public UploadFileVo uploadFile(MultipartFile file) {
return uploadFile(file, null);
}
@Override
public UploadFileVo uploadFile(MultipartFile file, String folder) {
String serverUrl = uploadConfig.getServerUrl();
MinioClient client = initClient();
String fileName = getFileName(file);
String fileType = getFileType(file);
String fileKey = getFileKey(file);
try {
PutObjectArgs args = PutObjectArgs.builder()
.bucket(uploadConfig.getBucket())
.object(fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(fileType)
.build();
client.putObject(args);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException("文件上传失败");
}
// 组装对象
UploadFileVo fileVo = format(fileName, serverUrl, fileKey, fileType)
.setFullPath(serverUrl + fileKey);
return fileVo;
}
@Override
public UploadFileVo uploadFile(File file) {
return uploadFile(file, null);
}
@Override
public UploadFileVo uploadFile(File file, String folder) {
String serverUrl = uploadConfig.getServerUrl();
MinioClient client = initClient();
String fileName = getFileName(file);
String fileType = getFileType(file);
String fileKey = getFileKey(file);
try {
PutObjectArgs args = PutObjectArgs.builder()
.bucket(uploadConfig.getBucket())
.object(fileName)
.stream(FileUtil.getInputStream(file), FileUtil.size(file), -1)
.contentType(fileType)
.build();
client.putObject(args);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException("文件上传失败");
}
// 组装对象
UploadFileVo fileVo = format(fileName, serverUrl, fileKey, fileType)
.setFullPath(serverUrl + fileKey);
return fileVo;
}
}

View File

@@ -0,0 +1,126 @@
package com.platform.common.upload.service.impl;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.IdUtil;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.PolicyConditions;
import com.platform.common.upload.config.UploadConfig;
import com.platform.common.upload.enums.UploadTypeEnum;
import com.platform.common.upload.service.UploadService;
import com.platform.common.upload.vo.UploadFileVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.InputStream;
import java.util.Date;
/**
* 阿里云上传
*/
@Slf4j
@Service("uploadOssService")
@Configuration
@ConditionalOnProperty(prefix = "upload", name = "uploadType", havingValue = "oss")
public class UploadOssServiceImpl extends UploadBaseService implements UploadService {
@Resource
private UploadConfig uploadConfig;
/**
* 初始化oss
*/
private OSS initOSS() {
return new OSSClientBuilder()
.build(uploadConfig.getRegion(), uploadConfig.getAccessKey(), uploadConfig.getSecretKey());
}
@Override
public String getServerUrl() {
return uploadConfig.getServerUrl();
}
@Override
public Dict getToken(String fileType) {
// 1、默认固定值分钟
Integer expire = 30;
String accessKey = uploadConfig.getAccessKey();
String serverUrl = uploadConfig.getServerUrl();
String post = uploadConfig.getPost();
// 2、过期时间
Date expiration = DateUtil.offsetMinute(DateUtil.date(), expire);
// 3、构造“策略”Policy
OSS ossClient = initOSS();
PolicyConditions policyConditions = new PolicyConditions();
policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConditions);
String postSignature = ossClient.calculatePostSignature(postPolicy);
// 4、生成 Signature
return Dict.create()
.set("uploadType", UploadTypeEnum.OSS)
.set("serverUrl", serverUrl)
.set("fileName", IdUtil.objectId() + "." + fileType)
.set("accessKey", accessKey)
.set("policy", Base64.encode(postPolicy))
.set("signature", postSignature)
.set("post", post);
}
@Override
public UploadFileVo uploadFile(MultipartFile file) {
return uploadFile(file, null);
}
@Override
public UploadFileVo uploadFile(MultipartFile file, String folder) {
OSS client = initOSS();
try {
String fileName = getFileName(file);
String fileKey = getFileKey(file, folder);
String fileType = getFileType(file);
client.putObject(uploadConfig.getBucket(), fileKey, file.getInputStream());
// 服务器地址
String serverUrl = uploadConfig.getServerUrl();
return format(fileName, serverUrl, fileKey, fileType);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException("文件上传失败");
} finally {
client.shutdown();
}
}
@Override
public UploadFileVo uploadFile(File file) {
return uploadFile(file, null);
}
@Override
public UploadFileVo uploadFile(File file, String folder) {
OSS client = initOSS();
try {
String fileName = getFileName(file);
String fileKey = getFileKey(file, folder);
String fileType = getFileType(file);
InputStream inputStream = FileUtil.getInputStream(file);
client.putObject(uploadConfig.getBucket(), fileKey, inputStream);
// 服务器地址
String serverUrl = uploadConfig.getServerUrl();
return format(fileName, serverUrl, fileKey, fileType);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException("文件上传失败");
} finally {
client.shutdown();
}
}
}

View File

@@ -0,0 +1,48 @@
package com.platform.common.upload.utils;
import com.github.tobato.fastdfs.domain.conn.FdfsWebServer;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.fdfs.ThumbImageConfig;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.util.Set;
@Slf4j
@Component
public class FastUtils {
private static FastFileStorageClient fastFileStorageClient;
public FastUtils(ThumbImageConfig thumbImageConfig, FastFileStorageClient fastFileStorageClient, FdfsWebServer fdfsWebServer) {
FastUtils.fastFileStorageClient = fastFileStorageClient;
}
public static StorePath uploadFile(MultipartFile multipartFile) {
StorePath storePath;
try {
storePath = fastFileStorageClient.uploadFile(multipartFile.getInputStream(), multipartFile.getSize(), FilenameUtils.getExtension(multipartFile.getOriginalFilename()), (Set) null);
} catch (Exception var6) {
log.error(var6.getMessage(), var6);
throw new RuntimeException("文件上传失败");
}
return storePath;
}
public static StorePath uploadFile(File file) {
StorePath storePath;
try {
FileInputStream inputStream = new FileInputStream(file);
storePath = fastFileStorageClient.uploadFile(inputStream, file.length(), FilenameUtils.getExtension(file.getName()), null);
} catch (Exception var6) {
log.error(var6.getMessage(), var6);
throw new RuntimeException("文件上传失败");
}
return storePath;
}
}

View File

@@ -0,0 +1,18 @@
package com.platform.common.upload.vo;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 文件上传
*/
@Data
@Accessors(chain = true) // 链式调用
public class UploadAudioVo extends UploadFileVo {
/**
* 识别文字
*/
private String sourceText;
}

View File

@@ -0,0 +1,26 @@
package com.platform.common.upload.vo;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 文件上传
*/
@Data
@Accessors(chain = true) // 链式调用
public class UploadFileVo {
/**
* 文件名称
*/
private String fileName;
/**
* 文件地址
*/
private String fullPath;
/**
* 文件类型
*/
private String fileType;
}

View File

@@ -0,0 +1,30 @@
package com.platform.common.upload.vo;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 文件上传
*/
@Data
@Accessors(chain = true) // 链式调用
public class UploadVideoVo {
/**
* 文件名称
*/
private String fileName;
/**
* 文件地址
*/
private String fullPath;
/**
* 文件类型
*/
private String fileType;
/**
* 文件封面
*/
private String screenShot;
}

View File

@@ -0,0 +1,34 @@
package com.platform.common.utils;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
/**
* java bean 复制操作的工具类
*/
public class BeanCopyUtils {
/**
* 包含方法
*/
public static <T> T include(Object source, String... fields) {
JSONObject jsonObject = JSONUtil.parseObj(source);
JSONObject target = new JSONObject();
for (String field : fields) {
target.set(field.trim(), jsonObject.get(field.trim()));
}
return (T) target.toBean(source.getClass());
}
/**
* 排除方法
*/
public static <T> T exclude(Object source, String... fields) {
JSONObject jsonObject = JSONUtil.parseObj(source);
for (String field : fields) {
jsonObject.remove(field.trim());
}
return (T) jsonObject.toBean(source.getClass());
}
}

View File

@@ -0,0 +1,154 @@
package com.platform.common.utils;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* 获取IP方法
*/
public class IpUtils {
public static String getIpAddr(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
public static boolean internalIp(String ip) {
byte[] addr = textToNumericFormatV4(ip);
return internalIp(addr) || "127.0.0.1".equals(ip);
}
private static boolean internalIp(byte[] addr) {
if (addr == null || addr.length < 2) {
return true;
}
final byte b0 = addr[0];
final byte b1 = addr[1];
// 10.x.x.x/8
final byte SECTION_1 = 0x0A;
// 172.16.x.x/12
final byte SECTION_2 = (byte) 0xAC;
final byte SECTION_3 = (byte) 0x10;
final byte SECTION_4 = (byte) 0x1F;
// 192.168.x.x/16
final byte SECTION_5 = (byte) 0xC0;
final byte SECTION_6 = (byte) 0xA8;
switch (b0) {
case SECTION_1:
return true;
case SECTION_2:
if (b1 >= SECTION_3 && b1 <= SECTION_4) {
return true;
}
case SECTION_5:
switch (b1) {
case SECTION_6:
return true;
}
default:
return false;
}
}
/**
* 将IPv4地址转换成字节
* <p>
* text IPv4地址
*/
public static byte[] textToNumericFormatV4(String text) {
if (text.length() == 0) {
return null;
}
byte[] bytes = new byte[4];
String[] elements = text.split("\\.", -1);
try {
long l;
int i;
switch (elements.length) {
case 1:
l = Long.parseLong(elements[0]);
if ((l < 0L) || (l > 4294967295L))
return null;
bytes[0] = (byte) (int) (l >> 24 & 0xFF);
bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 2:
l = Integer.parseInt(elements[0]);
if ((l < 0L) || (l > 255L))
return null;
bytes[0] = (byte) (int) (l & 0xFF);
l = Integer.parseInt(elements[1]);
if ((l < 0L) || (l > 16777215L))
return null;
bytes[1] = (byte) (int) (l >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 3:
for (i = 0; i < 2; ++i) {
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L))
return null;
bytes[i] = (byte) (int) (l & 0xFF);
}
l = Integer.parseInt(elements[2]);
if ((l < 0L) || (l > 65535L))
return null;
bytes[2] = (byte) (int) (l >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 4:
for (i = 0; i < 4; ++i) {
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L))
return null;
bytes[i] = (byte) (int) (l & 0xFF);
}
break;
default:
return null;
}
} catch (NumberFormatException e) {
return null;
}
return bytes;
}
public static String getHostIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
}
return "127.0.0.1";
}
public static String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
}
return "未知";
}
}

View File

@@ -0,0 +1,22 @@
package com.platform.common.utils;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
/**
* 获取i18n资源文件
*/
public class MessageUtils {
/**
* 根据消息键和参数 获取消息 委托给spring messageSource
* <p>
* msgCode 消息键
* args 参数
* 获取国际化翻译值
*/
public static String message(String msgCode, Object... args) {
MessageSource messageSource = SpringUtil.getBean(MessageSource.class);
return messageSource.getMessage(msgCode, args, LocaleContextHolder.getLocale());
}
}

View File

@@ -0,0 +1,74 @@
package com.platform.common.utils;
import cn.hutool.core.convert.Convert;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 客户端工具类
*/
public class ServletUtils {
/**
* 获取String参数
*/
public static String getParameter(String name) {
return getRequest().getParameter(name);
}
/**
* 获取String参数
*/
public static String getParameter(String name, String defaultValue) {
String value = getParameter(name);
if (StringUtils.isEmpty(value)) {
value = defaultValue;
}
return value;
}
/**
* 获取Integer参数
*/
public static Integer getParameterToInt(String name) {
return getParameterToInt(name, null);
}
/**
* 获取Integer参数
*/
public static Integer getParameterToInt(String name, Integer defaultValue) {
return Convert.toInt(getRequest().getParameter(name), defaultValue);
}
/**
* 获取request
*/
public static HttpServletRequest getRequest() {
return getRequestAttributes().getRequest();
}
/**
* 获取response
*/
public static HttpServletResponse getResponse() {
return getRequestAttributes().getResponse();
}
/**
* 获取sessionId
*/
public static String getSessionId() {
return getRequestAttributes().getSessionId();
}
public static ServletRequestAttributes getRequestAttributes() {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
}
}

View File

@@ -0,0 +1,39 @@
package com.platform.common.utils;
import io.netty.util.HashedWheelTimer;
import io.netty.util.TimerTask;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
/**
* 环形队列--定时器
*
* @author 考拉
* @email 939313737@qq.com
* @date 2017年8月26日 All rights Reserved, Designed By www.q3z3.com
*/
@Slf4j
public class TimerUtils {
private HashedWheelTimer timer;
private TimerUtils() {
// 精度1秒一共512格
this.timer = new HashedWheelTimer(1, TimeUnit.SECONDS);
}
private static class SingletonHolder {
private static final TimerUtils singleton = new TimerUtils();
}
public static TimerUtils instance() {
return SingletonHolder.singleton;
}
public void addTask(TimerTask task, long delay, TimeUnit unit) {
log.info("添加定时任务, 延迟:{} {}", delay, unit.name());
timer.newTimeout(task, delay, unit);
}
}

View File

@@ -0,0 +1,20 @@
package com.platform.common.version;
import java.lang.annotation.*;
/**
* 自定义一个注解,给需要版本控制的方法加上该注解
* <p>
* 使用:@SubmitRepeat
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiVersion {
/**
* 版本号
*/
VersionEnum value();
}

View File

@@ -0,0 +1,52 @@
package com.platform.common.version;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.platform.common.constant.HeadConstant;
import com.platform.common.core.EnumUtils;
import com.platform.common.enums.DeviceEnum;
import com.platform.common.enums.YesOrNoEnum;
import com.platform.common.web.domain.AjaxResult;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 设备拦截器
*/
@Component
public class DeviceInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (YesOrNoEnum.NO.equals(VersionConfig.ENABLED)) {
return true;
}
String currentUrl = request.getServletPath();
if (VersionUtils.verifyUrl(currentUrl, VersionConfig.EXCLUDES)) {
return true;
}
String device = ServletUtil.getHeader(request, HeadConstant.DEVICE, CharsetUtil.UTF_8);
if (StringUtils.isEmpty(device)) {
return error(response);
}
DeviceEnum deviceEnum = EnumUtils.toEnum(DeviceEnum.class, device);
if (deviceEnum == null) {
return error(response);
}
return true;
}
private boolean error(HttpServletResponse response) throws IOException {
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSONUtil.toJsonStr(AjaxResult.fail("请求不正确")));
return false;
}
}

View File

@@ -0,0 +1,80 @@
package com.platform.common.version;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.platform.common.constant.HeadConstant;
import com.platform.common.enums.YesOrNoEnum;
import com.platform.common.web.domain.AjaxResult;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.Date;
/**
* 签名拦截器
*/
@Component
public class SignInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (YesOrNoEnum.NO.equals(VersionConfig.ENABLED)) {
return true;
}
String currentUrl = request.getServletPath();
if (VersionUtils.verifyUrl(currentUrl, VersionConfig.EXCLUDES)) {
return true;
}
String sign = ServletUtil.getHeader(request, HeadConstant.SIGN, CharsetUtil.UTF_8);
String timestamp = ServletUtil.getHeader(request, HeadConstant.TIMESTAMP, CharsetUtil.UTF_8);
String appId = ServletUtil.getHeader(request, HeadConstant.APP_ID, CharsetUtil.UTF_8);
if (StringUtils.isEmpty(sign)
|| StringUtils.isEmpty(appId)
|| StringUtils.isEmpty(timestamp)) {
return error(response);
}
String appSecret = VersionConfig.SIGN.get(appId);
if (appSecret == null) {
return error(response);
}
if (!NumberUtil.isLong(timestamp)) {
return error(response);
}
Date date = DateUtil.date(NumberUtil.parseLong(timestamp));
if (DateUtil.between(date, DateUtil.date(), DateUnit.MINUTE) > 5) {
return error(response);
}
String path = request.getRequestURI();
if ("GET".equalsIgnoreCase(request.getMethod())) {
String query = request.getQueryString();
if (!StringUtils.isEmpty(query)) {
path += "?" + URLDecoder.decode(query, "UTF-8");
}
}
String param = SecureUtil.md5(appId + path + timestamp);
// 此处密钥如果有非ASCII字符考虑编码
String result = SecureUtil.hmacMd5(appSecret).digestHex(param);
if (!sign.equalsIgnoreCase(result)) {
return error(response);
}
return true;
}
private boolean error(HttpServletResponse response) throws IOException {
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSONUtil.toJsonStr(AjaxResult.fail("请求不正确")));
return false;
}
}

View File

@@ -0,0 +1,43 @@
package com.platform.common.version;
import com.platform.common.constant.HeadConstant;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import javax.servlet.http.HttpServletRequest;
@Slf4j
@Getter
public class VersionCondition implements RequestCondition<VersionCondition> {
private String version;
public VersionCondition(String version) {
this.version = version;
}
//将不同的筛选条件合并,这里采用的覆盖,即后来的规则生效
@Override
public VersionCondition combine(VersionCondition other) {
return new VersionCondition(other.getVersion());
}
//根据request查找匹配到的筛选条件
@Override
public VersionCondition getMatchingCondition(HttpServletRequest request) {
String version = request.getHeader(HeadConstant.VERSION);
// 如果请求的版本号大于配置版本号, 则满足,即与请求的
if (VersionUtils.compareTo(version, this.version, request.getRequestURI()) >= 0) {
return this;
}
return null;
}
//实现不同条件类的比较,从而实现优先级排序,返回值最小匹配this
@Override
public int compareTo(VersionCondition other, HttpServletRequest request) {
return VersionUtils.compareTo(other.getVersion(), this.version, request.getRequestURI());
}
}

View File

@@ -0,0 +1,76 @@
package com.platform.common.version;
import com.platform.common.core.EnumUtils;
import com.platform.common.enums.YesOrNoEnum;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 读取项目相关配置
*/
@Component
@ConfigurationProperties(prefix = "version")
public class VersionConfig {
/**
* 版本开关
*/
public static YesOrNoEnum ENABLED = YesOrNoEnum.NO;
/**
* 最低版本
*/
public static String VERSION = "1.0.0";
/**
* 过滤请求
*/
public static List<String> EXCLUDES = new ArrayList<>();
/**
* 过滤请求
*/
public static List<String> BANS = new ArrayList<>();
/**
* 签名
*/
public static Map<String, String> SIGN = new HashMap<>();
public void setEnabled(String enabled) {
if (!StringUtils.isEmpty(enabled)) {
VersionConfig.ENABLED = EnumUtils.toEnum(YesOrNoEnum.class, enabled, YesOrNoEnum.NO);
}
}
public void setVersion(String version) {
if (!StringUtils.isEmpty(version)) {
VersionConfig.VERSION = version;
}
}
public void setExcludes(List<String> excludes) {
if (!CollectionUtils.isEmpty(excludes)) {
VersionConfig.EXCLUDES = excludes;
}
}
public void setBans(List<String> bans) {
if (!CollectionUtils.isEmpty(bans)) {
VersionConfig.BANS = bans;
}
}
public void setSign(Map<String, String> sign) {
if (!CollectionUtils.isEmpty(sign)) {
VersionConfig.SIGN = sign;
}
}
}

View File

@@ -0,0 +1,27 @@
package com.platform.common.version;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
/**
* 版本枚举值
*/
@Getter
public enum VersionEnum {
V1_0_0("1.0.0", "初始化版本"),
V1_0_1("1.0.1", "下一个版本"),
;
@EnumValue
@JsonValue
private final String code;
private final String info;
VersionEnum(String code, String info) {
this.code = code;
this.info = info;
}
}

View File

@@ -0,0 +1,25 @@
package com.platform.common.version;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
public class VersionHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestCondition<VersionCondition> getCustomMethodCondition(Method method) {
ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
return createCondition(apiVersion);
}
// 实例化RequestCondition
private RequestCondition<VersionCondition> createCondition(ApiVersion apiVersion) {
if (apiVersion == null) {
return null;
}
return new VersionCondition(apiVersion.value().getCode());
}
}

View File

@@ -0,0 +1,51 @@
package com.platform.common.version;
import com.platform.common.constant.HeadConstant;
import com.platform.common.enums.ResultCodeEnum;
import com.platform.common.enums.YesOrNoEnum;
import com.platform.common.exception.BaseException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 版本验证
*/
@Component
@Slf4j
public class VersionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
/**
* 如果不属于HandlerMethod则放行
*/
if (!(handler instanceof HandlerMethod)) {
return true;
}
if (YesOrNoEnum.NO.equals(VersionConfig.ENABLED)) {
return true;
}
String currentUrl = request.getServletPath();
if (VersionUtils.verifyUrl(currentUrl, VersionConfig.EXCLUDES)) {
return true;
}
//获取用户接口版本
String version = request.getHeader(HeadConstant.VERSION);
if (VersionUtils.compareTo(version, VersionConfig.VERSION, request.getRequestURI()) < 0) {
throw new BaseException(ResultCodeEnum.VERSION);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object object, Exception e) {
}
}

View File

@@ -0,0 +1,103 @@
package com.platform.common.version;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.platform.common.enums.ResultCodeEnum;
import com.platform.common.exception.BaseException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import java.util.List;
/**
* 版本比较
*/
@Slf4j
@Component
public class VersionUtils {
/**
* 比较版本大小
* <p>
* 说明支n位基础版本号+1位子版本号
* 示例1.0.2>1.0.1
*
* @param version1 版本1
* @param version2 版本2
* @return 0:相同 >0:大于 <0:小于
*/
public static int compareTo(String version1, String version2, String uri) {
if (!matchVersion(version1)) {
log.error("RequestURI" + uri);
log.error("传入版本格式有误version1-{}", version1);
throw new BaseException(ResultCodeEnum.VERSION);
}
if (!matchVersion(version2)) {
log.error("RequestURI" + uri);
log.error("系统版本格式有误version2-{}", version2);
throw new BaseException(ResultCodeEnum.VERSION);
}
if (version1.equals(version2)) {
return 0;
}
return versionStrToNum(version1) - versionStrToNum(version2);
}
/**
* 比较版本大小
* <p>
* 说明支n位基础版本号+1位子版本号
* 示例1.0.2>1.0.1
*
* @param version1 版本1
* @param version2 版本2
* @return 0:相同 >0:大于 <0:小于
*/
public static int compareTo(String version1, String version2) {
return compareTo(version1, version2, "-");
}
/**
* 版本号转换为数字
*
* @param versionStr
* @return
*/
public static int versionStrToNum(String versionStr) {
List<String> dataList = StrUtil.splitTrim(versionStr, ".");
StringBuilder builder = new StringBuilder()
.append(dataList.get(0))
.append(dataList.get(1))
.append(String.format("%03d", Integer.valueOf(dataList.get(2))));
return Integer.valueOf(builder.toString());
}
/**
* 匹配版本
*
* @param version
* @return
*/
private static boolean matchVersion(String version) {
return ReUtil.isMatch("\\d{1,3}(\\.\\d{1,3}){2}", StringUtils.isEmpty(version) ? "" : version);
}
// 匹配器
private static final PathMatcher pathMatcher = new AntPathMatcher();
/**
* 验证地址
*/
public static boolean verifyUrl(String currentUrl, List<String> whiteList) {
for (String url : whiteList) {
if (pathMatcher.match(url, currentUrl)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,27 @@
package com.platform.common.web.aspectj;
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MybatisAspectj {
// 配置织入点
@Pointcut("execution(public * com.baomidou.mybatisplus.core.mapper.BaseMapper.selectOne(..))")
public void selectOneAspect() {
}
@Before("selectOneAspect()")
public void beforeSelect(JoinPoint point) {
Object arg = point.getArgs()[0];
if (arg instanceof AbstractWrapper) {
((AbstractWrapper) arg).last("limit 1");
}
}
}

View File

@@ -0,0 +1,107 @@
package com.platform.common.web.controller;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.platform.common.core.EnumUtils;
import com.platform.common.enums.GenderEnum;
import com.platform.common.enums.YesOrNoEnum;
import com.platform.common.web.domain.AjaxResult;
import com.platform.common.web.domain.JsonDateDeserializer;
import com.platform.common.web.page.PageDomain;
import com.platform.common.web.page.TableDataInfo;
import com.platform.common.web.page.TableSupport;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import java.beans.PropertyEditorSupport;
import java.util.Date;
import java.util.List;
/**
* web层通用数据处理
*/
@Slf4j
public class BaseController {
/**
* 将前台传递过来的日期格式的字符串自动转化为Date类型
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
// Date 类型转换
binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) {
setValue(JsonDateDeserializer.parseDate(text));
}
});
// YesOrNoEnum 类型转换
binder.registerCustomEditor(YesOrNoEnum.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) {
setValue(EnumUtils.toEnum(YesOrNoEnum.class, text));
}
});
// GenderTypeEnum 类型转换
binder.registerCustomEditor(GenderEnum.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) {
setValue(EnumUtils.toEnum(GenderEnum.class, text));
}
});
}
/**
* 设置请求分页数据
*/
protected void startPage() {
PageDomain pageDomain = TableSupport.getPageDomain();
startPage(PageDomain.escapeOrderBySql(pageDomain.getOrderBy()));
}
/**
* 设置请求分页数据
*/
protected void startPage(String orderBy) {
PageDomain pageDomain = TableSupport.getPageDomain();
PageHelper.startPage(pageDomain.getPageNum(), pageDomain.getPageSize(), StrUtil.toUnderlineCase(orderBy));
}
/**
* 设置排序分页数据
*/
protected void orderBy(String orderBy) {
PageHelper.orderBy(StrUtil.toUnderlineCase(orderBy));
}
/**
* 响应请求分页数据
*/
@SuppressWarnings({"rawtypes", "unchecked"})
protected TableDataInfo getDataTable(List<?> list) {
return new TableDataInfo(list, new PageInfo(list).getTotal());
}
protected TableDataInfo getDataTable(List<?> list, PageDomain pageDomain) {
return getDataTable(CollUtil.sub(list, pageDomain.getPageStart(), pageDomain.getPageEnd()));
}
/**
* 响应请求分页数据
*/
@SuppressWarnings({"rawtypes", "unchecked"})
protected TableDataInfo getDataTable(PageInfo<?> list) {
return new TableDataInfo(list.getList(), list.getTotal());
}
/**
* 响应返回结果
*/
protected AjaxResult toAjax(int rows) {
return rows > 0 ? AjaxResult.success() : AjaxResult.fail();
}
}

View File

@@ -0,0 +1,33 @@
package com.platform.common.web.controller;
import com.platform.common.aspectj.IgnoreAuth;
import com.platform.common.core.EnumUtils;
import com.platform.common.enums.ResultCodeEnum;
import com.platform.common.web.domain.AjaxResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 错误请求处理
*/
@RestController
@RequestMapping("/error")
@Slf4j
public class ErrorController {
@IgnoreAuth
@RequestMapping("/{code}")
public AjaxResult error(@PathVariable String code) {
ResultCodeEnum resultCode = EnumUtils.toEnum(ResultCodeEnum.class, code, ResultCodeEnum.FAIL);
switch (resultCode) {
case SUCCESS:
return AjaxResult.success();
default:
return AjaxResult.result(resultCode);
}
}
}

View File

@@ -0,0 +1,44 @@
/**
* Licensed to the Apache Software Foundation ASF under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* https://www.q3z3.com
* QQ : 939313737
* <p>
* 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.
*/
package com.platform.common.web.dao;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
/**
* Dao基类
*/
public interface BaseDao<T> extends BaseMapper<T> {
/**
* 批量插入 仅适用于mysql
*/
Integer insertBatchSomeColumn(Collection<T> entityList);
/**
* 注意使用此方法时sql末尾需要增加${ew.customSqlSegment}
* <p>
* 支持单表、和联查
*/
List<T> search(@Param("ew") Wrapper<T> queryWrapper);
}

View File

@@ -0,0 +1,129 @@
package com.platform.common.web.domain;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.platform.common.core.EnumUtils;
import com.platform.common.enums.ResultCodeEnum;
import org.springframework.util.StringUtils;
import java.nio.charset.Charset;
import java.util.HashMap;
/**
* 操作消息提醒
*/
public class AjaxResult extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
/**
* 状态码
*/
public static final String CODE_TAG = "code";
/**
* 返回内容
*/
public static final String MSG_TAG = "msg";
/**
* 数据对象
*/
public static final String DATA_TAG = "data";
/**
* 初始化一个新创建的 AjaxResult 对象
*/
public AjaxResult(ResultCodeEnum resultCode, String msg, Object data) {
super.put(CODE_TAG, resultCode.getCode());
super.put(MSG_TAG, StringUtils.isEmpty(msg) ? resultCode.getInfo() : msg);
if (data != null) {
super.put(DATA_TAG, data);
}
}
/**
* 返回成功消息
*/
public static AjaxResult success() {
return new AjaxResult(ResultCodeEnum.SUCCESS, null, null);
}
/**
* 返回成功数据
*/
public static AjaxResult success(Object data) {
return new AjaxResult(ResultCodeEnum.SUCCESS, null, data);
}
/**
* 返回成功消息
*/
public static AjaxResult successMsg(String msg) {
return new AjaxResult(ResultCodeEnum.SUCCESS, msg, null);
}
/**
* 返回错误消息
*/
public static AjaxResult fail() {
return new AjaxResult(ResultCodeEnum.FAIL, null, null);
}
/**
* 返回错误消息
*/
public static AjaxResult fail(String msg) {
return new AjaxResult(ResultCodeEnum.FAIL, msg, null);
}
/**
* 返回错误消息
*/
public static AjaxResult result(ResultCodeEnum resultCode) {
return new AjaxResult(resultCode, resultCode.getInfo(), null);
}
/**
* 返回错误消息
*/
public static AjaxResult result(ResultCodeEnum resultCode, String msg) {
if (StringUtils.isEmpty(msg)) {
msg = resultCode.getInfo();
}
return new AjaxResult(resultCode, msg, null);
}
@Override
public AjaxResult put(String key, Object value) {
super.put(key, value);
return this;
}
public ResultCodeEnum getCode() {
Object code = this.get(CODE_TAG);
return EnumUtils.toEnum(ResultCodeEnum.class, code.toString(), ResultCodeEnum.FAIL);
}
public Object getData() {
return this.get(DATA_TAG);
}
public String getDataStr() {
Object data = getData();
return StrUtil.str(data, Charset.defaultCharset());
}
public static AjaxResult ajax(String json) {
if (StringUtils.isEmpty(json)) {
return AjaxResult.fail();
}
JSONObject jsonObject = JSONUtil.parseObj(json);
String code = StrUtil.str(jsonObject.get(CODE_TAG), Charset.defaultCharset());
String msg = StrUtil.str(jsonObject.get(MSG_TAG), Charset.defaultCharset());
ResultCodeEnum resultCode = EnumUtils.toEnum(ResultCodeEnum.class, code, ResultCodeEnum.FAIL);
return new AjaxResult(resultCode, msg, jsonObject.get(DATA_TAG));
}
}

View File

@@ -0,0 +1,63 @@
package com.platform.common.web.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
import java.util.Map;
/**
* Entity基类
*/
@Data
@Accessors(chain = true) // 链式调用
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 开始时间
*/
@TableField(exist = false)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Date beginTime;
/**
* 结束时间
*/
@TableField(exist = false)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Date endTime;
/**
* 请求参数
*/
@TableField(exist = false)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String param;
/**
* 请求参数
*/
@TableField(exist = false)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Map<String, Object> params;
/**
* 返回参数
*/
@TableField(exist = false)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String label;
/**
* 返回参数
*/
@TableField(exist = false)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Long count;
}

View File

@@ -0,0 +1,46 @@
package com.platform.common.web.domain;
import cn.hutool.core.date.DateException;
import cn.hutool.core.date.DateUtil;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.util.Date;
/**
* 自定义时间转换
*/
public class JsonDateDeserializer extends JsonDeserializer<Date> {
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
String date = jsonParser.getText();
return parseDate(date);
}
/**
* 前台日期格式
*/
private static String[] parsePatterns = {
"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
"yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
"yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
/**
* 日期型字符串转化为日期 格式
*/
public static Date parseDate(Object obj) {
if (obj == null) {
return null;
}
try {
return DateUtil.parse(obj.toString(), parsePatterns);
} catch (DateException e) {
return null;
}
}
}

View File

@@ -0,0 +1,18 @@
package com.platform.common.web.exception;
import com.platform.common.web.domain.AjaxResult;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import java.util.Map;
@Component
public class ErrorAttributesCustom extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
return AjaxResult.fail();
}
}

View File

@@ -0,0 +1,47 @@
package com.platform.common.web.exception;
import com.platform.common.enums.ResultCodeEnum;
import com.platform.common.exception.BaseException;
import com.platform.common.web.domain.AjaxResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
/**
* 全局异常处理器
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e) {
log.error("全局异常:" + e.getMessage(), e);
/**
* 路径不存在
*/
if (e instanceof NoHandlerFoundException
|| e instanceof org.springframework.web.HttpRequestMethodNotSupportedException) {
return AjaxResult.result(ResultCodeEnum.NOT_FOUND);
}
/**
* 校验异常
*/
if (e instanceof MethodArgumentNotValidException) {
return AjaxResult.fail(((MethodArgumentNotValidException) e).getBindingResult().getFieldError().getDefaultMessage());
}
/**
* 自定义异常
*/
if (e instanceof BaseException) {
if (ResultCodeEnum.VERSION.equals(((BaseException) e).getResultCode())) {
return AjaxResult.result(ResultCodeEnum.VERSION);
}
return AjaxResult.fail(e.getMessage());
}
return AjaxResult.fail();
}
}

View File

@@ -0,0 +1,20 @@
package com.platform.common.web.interceptor;
import io.undertow.server.DefaultByteBufferPool;
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
@Override
public void customize(UndertowServletWebServerFactory factory) {
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 1024));
deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
});
}
}

View File

@@ -0,0 +1,62 @@
package com.platform.common.web.page;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.util.StringUtils;
/**
* 分页数据
*/
@Data
@NoArgsConstructor
public class PageDomain {
/**
* 当前记录起始索引
*/
private Integer pageNum;
/**
* 每页显示记录数
*/
private Integer pageSize;
/**
* 排序列
*/
private String orderBy;
/**
* 排序方向 "desc" 或者 "asc"
*/
private String orderSort;
public String getOrderBy() {
if (StringUtils.isEmpty(orderBy)) {
return "";
}
return StrUtil.toUnderlineCase(orderBy) + " " + orderSort;
}
public Integer getPageStart() {
return (getPageNum() - 1) * getPageSize();
}
public Integer getPageEnd() {
return getPageStart() + getPageSize();
}
/**
* 记录总数
*/
private Long total;
/**
* 检查字符,防止注入绕过
*/
public static String escapeOrderBySql(String value) {
// 仅支持字母、数字、下划线、空格、逗号(支持多个字段排序)
String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,]+";
if (!StringUtils.isEmpty(value) && !value.matches(SQL_PATTERN)) {
return "";
}
return StrUtil.toUnderlineCase(value);
}
}

View File

@@ -0,0 +1,55 @@
package com.platform.common.web.page;
import com.platform.common.enums.ResultCodeEnum;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.HashMap;
import java.util.List;
/**
* 表格分页数据对象
*/
@Data
@Accessors(chain = true) // 链式调用
public class TableDataInfo extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
/**
* 状态码
*/
public static final String CODE_TAG = "code";
/**
* 返回内容
*/
public static final String MSG_TAG = "msg";
/**
* 数据对象
*/
public static final String ROWS_TAG = "rows";
/**
* 数据对象
*/
public static final String TOTAL_TAG = "total";
/**
* 分页
*/
public TableDataInfo(List<?> list, Long total) {
ResultCodeEnum result = ResultCodeEnum.SUCCESS;
super.put(CODE_TAG, result.getCode());
super.put(MSG_TAG, result.getInfo());
super.put(ROWS_TAG, list);
super.put(TOTAL_TAG, total.intValue());
}
@Override
public TableDataInfo put(String key, Object value) {
super.put(key, value);
return this;
}
}

View File

@@ -0,0 +1,41 @@
package com.platform.common.web.page;
import com.platform.common.utils.ServletUtils;
/**
* 表格数据处理
*/
public class TableSupport {
/**
* 当前记录起始索引
*/
public static final String PAGE_NUM = "pageNum";
/**
* 每页显示记录数
*/
public static final String PAGE_SIZE = "pageSize";
/**
* 排序列
*/
public static final String ORDER_BY = "orderBy";
/**
* 排序方向 "desc" 或者 "asc"
*/
public static final String ORDER_SORT = "orderSort";
/**
* 封装分页对象
*/
public static PageDomain getPageDomain() {
PageDomain pageDomain = new PageDomain();
pageDomain.setPageNum(ServletUtils.getParameterToInt(PAGE_NUM, 1));
pageDomain.setPageSize(ServletUtils.getParameterToInt(PAGE_SIZE, 10));
pageDomain.setOrderBy(ServletUtils.getParameter(ORDER_BY));
pageDomain.setOrderSort(ServletUtils.getParameter(ORDER_SORT, "asc"));
return pageDomain;
}
}

View File

@@ -0,0 +1,120 @@
/**
* Licensed to the Apache Software Foundation ASF under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* https://www.q3z3.com
* QQ : 939313737
* <p>
* 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.
*/
package com.platform.common.web.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
/**
* 基础service接口层基类
*/
public interface BaseService<T> {
/**
* 新增并返回id
*/
Integer add(T entity);
/**
* 根据 id 删除
*/
Integer deleteById(Long id);
/**
* 根据ids 批量删除
*/
Integer deleteByIds(Long[] ids);
/**
* 根据ids 批量删除
*/
Integer deleteByIds(List<Long> ids);
/**
* 根据 id 修改
*/
Integer updateById(T entity);
/**
* 根据条件更新
*/
Integer update(Wrapper wrapper);
/**
* 根据 id 查询
*/
T getById(Long id);
/**
* 根据 id 查询
*/
T findById(Long id);
/**
* 根据 ids 查询
*/
List<T> getByIds(Collection<? extends Serializable> idList);
/**
* 查询总记录数
*/
Long queryCount(T t);
/**
* 查询总记录数
*/
Long queryCount(Wrapper<T> wrapper);
/**
* 查询全部记录
*/
List<T> queryList(T t);
/**
* 查询全部记录
*/
List<T> queryList(Wrapper<T> wrapper);
/**
* 查询一个
*/
T queryOne(T t);
/**
* 查询一个
*/
T queryOne(Wrapper<T> wrapper);
/**
* 批量新增
*/
Integer batchAdd(List<T> list);
/**
* 批量新增仅mysql可以使用
*/
Integer batchAdd(List<T> list, Integer batchCount);
/**
* 修改状态
*/
void changeStatus(T t, String... param);
}

View File

@@ -0,0 +1,206 @@
/**
* Licensed to the Apache Software Foundation ASF under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* https://www.q3z3.com
* QQ : 939313737
* <p>
* 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.
*/
package com.platform.common.web.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.PageInfo;
import com.platform.common.exception.BaseException;
import com.platform.common.utils.BeanCopyUtils;
import com.platform.common.web.dao.BaseDao;
import com.platform.common.web.service.BaseService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* 基础service实现层基类
*/
public class BaseServiceImpl<T> implements BaseService<T> {
protected final static String EXIST_MSG = "数据不存在";
private BaseDao<T> baseDao;
public void setBaseDao(BaseDao<T> baseDao) {
this.baseDao = baseDao;
}
private static final Integer batchCount = 1000;
@Override
public Integer add(T entity) {
return baseDao.insert(entity);
}
@Override
public Integer deleteById(Long id) {
return baseDao.deleteById(id);
}
@Override
public Integer deleteByIds(Long[] ids) {
return baseDao.deleteBatchIds(Arrays.asList(ids));
}
@Override
public Integer deleteByIds(List<Long> ids) {
return baseDao.deleteBatchIds(ids);
}
@Override
public Integer updateById(T entity) {
return baseDao.updateById(entity);
}
@Override
public T getById(Long id) {
return baseDao.selectById(id);
}
/**
* eq: Wrapper wrapper = Wrappers.<ChatFriend>lambdaUpdate().set(ChatFriend::getRemark, null).eq(ChatFriend::getId, friend.getId());
*/
@Override
public Integer update(Wrapper wrapper) {
return baseDao.update(null, wrapper);
}
@Override
public T findById(Long id) {
T t = getById(id);
if (t == null) {
throw new BaseException(EXIST_MSG);
}
return t;
}
@Override
public List<T> getByIds(Collection<? extends Serializable> idList) {
return baseDao.selectBatchIds(idList);
}
@Override
public Long queryCount(T t) {
Wrapper<T> wrapper = new QueryWrapper<>(t);
return queryCount(wrapper);
}
@Override
public Long queryCount(Wrapper<T> wrapper) {
return baseDao.selectCount(wrapper).longValue();
}
@Override
public List<T> queryList(T t) {
Wrapper<T> wrapper = new QueryWrapper<>(t);
return queryList(wrapper);
}
@Override
public List<T> queryList(Wrapper<T> wrapper) {
return baseDao.selectList(wrapper);
}
@Override
public T queryOne(T t) {
Wrapper<T> wrapper = new QueryWrapper<>(t);
return queryOne(wrapper);
}
@Override
public T queryOne(Wrapper<T> wrapper) {
return baseDao.selectOne(wrapper);
}
@Override
public Integer batchAdd(List<T> list) {
return batchAdd(list, batchCount);
}
@Transactional
@Override
public Integer batchAdd(List<T> list, Integer batchCount) {
if (CollectionUtils.isEmpty(list)) {
return 0;
}
List<T> batchList = new ArrayList<>();
AtomicReference<Integer> result = new AtomicReference<>(0);
list.forEach((o) -> {
batchList.add(o);
if (batchList.size() == batchCount) {
result.updateAndGet(v -> v + baseDao.insertBatchSomeColumn(batchList));
batchList.clear();
}
});
if (!CollectionUtils.isEmpty(batchList)) {
result.updateAndGet(v -> v + baseDao.insertBatchSomeColumn(batchList));
batchList.clear();
}
return result.get();
}
/**
* 循环工具类
*/
public static <T> Consumer<T> forEach(BiConsumer<T, Integer> consumer) {
class Obj {
int i;
}
Obj obj = new Obj();
return t -> {
int index = obj.i++;
consumer.accept(t, index);
};
}
@Override
public void changeStatus(T t, String... param) {
if (param.length == 0) {
this.updateById(BeanCopyUtils.include(t, "id", "status"));
} else {
this.updateById(BeanCopyUtils.include(t, param));
}
}
/**
* 响应请求分页数据
*/
protected PageInfo getPageInfo(List<?> list, List<?> oldList) {
Long total = new PageInfo(oldList).getTotal();
return getPageInfo(list, total);
}
/**
* 格式化分页
*/
public PageInfo getPageInfo(List<?> list, long total) {
PageInfo pageInfo = new PageInfo(list);
pageInfo.setTotal(total);
return pageInfo;
}
}

View File

@@ -0,0 +1,131 @@
package com.platform.modules.auth.controller;
import cn.hutool.core.lang.Dict;
import com.platform.common.aspectj.IgnoreAuth;
import com.platform.common.aspectj.SubmitRepeat;
import com.platform.common.exception.BaseException;
import com.platform.common.shiro.ShiroLoginAuth;
import com.platform.common.shiro.ShiroLoginPhone;
import com.platform.common.version.ApiVersion;
import com.platform.common.version.VersionEnum;
import com.platform.common.web.controller.BaseController;
import com.platform.common.web.domain.AjaxResult;
import com.platform.modules.auth.vo.AuthVo01;
import com.platform.modules.auth.vo.AuthVo02;
import com.platform.modules.auth.vo.AuthVo03;
import com.platform.modules.auth.vo.AuthVo04;
import com.platform.modules.chat.domain.ChatUser;
import com.platform.modules.chat.service.ChatUserService;
import com.platform.modules.sms.enums.SmsTypeEnum;
import com.platform.modules.sms.service.SmsService;
import com.platform.modules.sms.vo.SmsVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 认证
*/
@RestController
@Slf4j
@RequestMapping("/auth")
public class AuthController extends BaseController {
@Resource
private ChatUserService chatUserService;
@Resource
private SmsService smsService;
/**
* 发送验证码(登录/注册/忘记密码)
*/
@IgnoreAuth
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping(value = "/sendCode")
@SubmitRepeat
public AjaxResult sendCode(@Validated @RequestBody SmsVo smsVo) {
ChatUser chatUser = chatUserService.queryByPhone(smsVo.getPhone());
switch (smsVo.getType()) {
case LOGIN:
case FORGET:
if (chatUser == null) {
throw new BaseException("用户未注册,请先注册");
}
break;
case REGISTERED:
if (chatUser != null) {
throw new BaseException("用户已注册,请直接登录");
}
break;
}
return AjaxResult.success(smsService.sendSms(smsVo)).put("msg", "验证码已发送");
}
/**
* 注册方法
*/
@IgnoreAuth
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping(value = "/register")
public AjaxResult register(@Validated @RequestBody AuthVo01 authVo) {
// 验证
smsService.verifySms(authVo.getPhone(), authVo.getCode(), SmsTypeEnum.REGISTERED);
// 注册
chatUserService.register(authVo);
return AjaxResult.successMsg("注册成功,请登录");
}
/**
* 登录方法(根据手机+密码登录)
*/
@IgnoreAuth
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/login")
public AjaxResult login(@Validated @RequestBody AuthVo02 authVo) {
// 执行登录
ShiroLoginAuth loginAuth = new ShiroLoginAuth(authVo.getPhone(), authVo.getPassword());
Dict dict = chatUserService.doLogin(loginAuth);
return AjaxResult.success(dict);
}
/**
* 登录方法(根据手机+验证码登录)
*/
@IgnoreAuth
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/loginByCode")
public AjaxResult loginByCode(@Validated @RequestBody AuthVo03 authVo) {
// 验证
smsService.verifySms(authVo.getPhone(), authVo.getCode(), SmsTypeEnum.LOGIN);
// 执行登录
ShiroLoginPhone loginPhone = new ShiroLoginPhone(authVo.getPhone());
Dict dict = chatUserService.doLogin(loginPhone);
return AjaxResult.success(dict);
}
/**
* 找回密码(根据手机)
*/
@IgnoreAuth
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/forget")
public AjaxResult forget(@Validated @RequestBody AuthVo04 authVo) {
// 验证
smsService.verifySms(authVo.getPhone(), authVo.getCode(), SmsTypeEnum.FORGET);
// 查询用户
ChatUser cu = chatUserService.queryByPhone(authVo.getPhone());
if (cu == null) {
throw new BaseException("手机号不存在");
}
// 重置密码
chatUserService.resetPass(cu.getUserId(), authVo.getPassword());
return AjaxResult.successMsg("您的密码重置成功,请重新登录");
}
}

View File

@@ -0,0 +1,44 @@
/**
* Licensed to the Apache Software Foundation ASF under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* https://www.q3z3.com
* QQ : 939313737
* <p>
* 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.
*/
package com.platform.modules.auth.service;
import com.platform.common.shiro.LoginUser;
/**
* <p>
* token 服务层
* </p>
*/
public interface TokenService {
/**
* 生成token
*/
String generateToken();
/**
* 通过token查询
*/
LoginUser queryByToken(String token);
/**
* 删除token
*/
void deleteToken(String token);
}

View File

@@ -0,0 +1,75 @@
/**
* Licensed to the Apache Software Foundation ASF under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* https://www.q3z3.com
* QQ : 939313737
* <p>
* 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.
*/
package com.platform.modules.auth.service.impl;
import cn.hutool.json.JSONUtil;
import com.platform.common.config.PlatformConfig;
import com.platform.common.constant.HeadConstant;
import com.platform.common.redis.RedisUtils;
import com.platform.common.shiro.ShiroUtils;
import com.platform.common.shiro.LoginUser;
import com.platform.modules.auth.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
/**
* token 服务层
*/
@Service("tokenService")
public class TokenServiceImpl implements TokenService {
@Autowired
private RedisUtils redisUtils;
@Override
public String generateToken() {
LoginUser loginUser = ShiroUtils.getLoginUser();
String token = loginUser.getToken();
// 存储redis
redisUtils.set(makeToken(token), JSONUtil.toJsonStr(loginUser), PlatformConfig.TIMEOUT, TimeUnit.MINUTES);
return token;
}
@Override
public LoginUser queryByToken(String token) {
String key = makeToken(token);
if (!redisUtils.hasKey(key)) {
return null;
}
// 续期
redisUtils.expire(key, PlatformConfig.TIMEOUT, TimeUnit.MINUTES);
// 转换
return JSONUtil.toBean(redisUtils.get(key), LoginUser.class);
}
@Override
public void deleteToken(String token) {
if (StringUtils.isEmpty(token)) {
return;
}
redisUtils.delete(makeToken(token));
}
private String makeToken(String token) {
return HeadConstant.TOKEN_REDIS_APP + token;
}
}

View File

@@ -0,0 +1,36 @@
package com.platform.modules.auth.vo;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Data
public class AuthVo01 {
/**
* 手机号
*/
@NotBlank(message = "手机号不能为空")
private String phone;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
private String password;
/**
* 昵称
*/
@NotBlank(message = "昵称不能为空")
@Size(max = 20, message = "昵称长度不能大于20")
private String nickName;
/**
* 验证码
*/
@NotBlank(message = "验证码不能为空")
private String code;
}

View File

@@ -0,0 +1,22 @@
package com.platform.modules.auth.vo;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class AuthVo02 {
/**
* 手机号
*/
@NotBlank(message = "手机号不能为空")
private String phone;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
private String password;
}

View File

@@ -0,0 +1,22 @@
package com.platform.modules.auth.vo;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class AuthVo03 {
/**
* 手机号
*/
@NotBlank(message = "手机号不能为空")
private String phone;
/**
* 验证码
*/
@NotBlank(message = "验证码不能为空")
private String code;
}

View File

@@ -0,0 +1,28 @@
package com.platform.modules.auth.vo;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class AuthVo04 {
/**
* 手机号
*/
@NotBlank(message = "手机号不能为空")
private String phone;
/**
* 验证码
*/
@NotBlank(message = "验证码不能为空")
private String code;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
private String password;
}

View File

@@ -0,0 +1,17 @@
package com.platform.modules.chat.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 读取高德地图相关配置
*/
@Component
@Data
public class AmapConfig {
@Value("${amap.key}")
private String key;
}

View File

@@ -0,0 +1,23 @@
package com.platform.modules.chat.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 腾讯nlp配置
*/
@Component
@Data
public class TencentConfig {
@Value("${tencent.appId}")
private String appId;
@Value("${tencent.appKey}")
private String appKey;
@Value("${tencent.appSecret}")
private String appSecret;
}

View File

@@ -0,0 +1,91 @@
package com.platform.modules.chat.controller;
import com.platform.common.version.ApiVersion;
import com.platform.common.version.VersionEnum;
import com.platform.common.web.controller.BaseController;
import com.platform.common.web.domain.AjaxResult;
import com.platform.common.web.page.TableDataInfo;
import com.platform.modules.chat.service.ChatApplyService;
import com.platform.modules.chat.service.ChatFriendService;
import com.platform.modules.chat.vo.ApplyVo01;
import com.platform.modules.chat.vo.FriendVo02;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 申请
*/
@RestController
@Slf4j
@RequestMapping("/apply")
public class ApplyController extends BaseController {
@Resource
private ChatFriendService chatFriendService;
@Resource
private ChatApplyService chatApplyService;
/**
* 申请添加
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/add")
public AjaxResult add(@Validated @RequestBody FriendVo02 friendVo) {
chatFriendService.applyFriend(friendVo);
return AjaxResult.success();
}
/**
* 申请记录
*/
@ApiVersion(VersionEnum.V1_0_0)
@GetMapping("/list")
public TableDataInfo list() {
startPage("create_time desc");
return getDataTable(chatApplyService.list());
}
/**
* 申请详情
*/
@ApiVersion(VersionEnum.V1_0_0)
@GetMapping("/info/{applyId}")
public AjaxResult getInfo(@PathVariable Long applyId) {
return AjaxResult.success(chatApplyService.getInfo(applyId));
}
/**
* 同意申请
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/agree")
public AjaxResult agree(@Validated @RequestBody ApplyVo01 applyVo) {
chatFriendService.agree(applyVo.getApplyId());
return AjaxResult.success();
}
/**
* 拒绝申请
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/refused")
public AjaxResult refused(@Validated @RequestBody ApplyVo01 applyVo) {
chatFriendService.refused(applyVo.getApplyId());
return AjaxResult.success();
}
/**
* 忽略申请
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/ignore")
public AjaxResult ignore(@Validated @RequestBody ApplyVo01 applyVo) {
chatFriendService.ignore(applyVo.getApplyId());
return AjaxResult.success();
}
}

View File

@@ -0,0 +1,35 @@
package com.platform.modules.chat.controller;
import com.platform.common.version.ApiVersion;
import com.platform.common.version.VersionEnum;
import com.platform.common.web.controller.BaseController;
import com.platform.common.web.domain.AjaxResult;
import com.platform.modules.chat.service.ChatMsgService;
import com.platform.modules.chat.vo.ChatVo01;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 聊天
*/
@RestController
@Slf4j
@RequestMapping("/chat")
public class ChatController extends BaseController {
@Resource
private ChatMsgService chatMsgService;
/**
* 发送信息
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/sendMsg")
public AjaxResult sendMsg(@Validated @RequestBody ChatVo01 chatVo) {
return AjaxResult.success(chatMsgService.sendFriendMsg(chatVo));
}
}

View File

@@ -0,0 +1,95 @@
package com.platform.modules.chat.controller;
import com.platform.common.version.ApiVersion;
import com.platform.common.version.VersionEnum;
import com.platform.common.web.controller.BaseController;
import com.platform.common.web.domain.AjaxResult;
import com.platform.modules.chat.service.ChatFriendService;
import com.platform.modules.chat.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 好友
*/
@RestController
@Slf4j
@RequestMapping("/friend")
public class FriendController extends BaseController {
@Resource
private ChatFriendService chatFriendService;
/**
* 搜索好友
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/findFriend")
public AjaxResult findFriend(@Validated @RequestBody FriendVo01 friendVo) {
return AjaxResult.success(chatFriendService.findFriend(friendVo.getParam()));
}
/**
* 好友列表
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/friendList")
public AjaxResult friendList(@Validated @RequestBody FriendVo08 friendVo) {
List<FriendVo06> list = chatFriendService.friendList(friendVo.getParam());
return AjaxResult.success(list);
}
/**
* 好友详情
*/
@ApiVersion(VersionEnum.V1_0_0)
@GetMapping("/info/{friendId}")
public AjaxResult getInfo(@PathVariable Long friendId) {
return AjaxResult.success(chatFriendService.getInfo(friendId));
}
/**
* 设置黑名单
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/black")
public AjaxResult black(@Validated @RequestBody FriendVo03 friendVo) {
chatFriendService.setBlack(friendVo);
return AjaxResult.success();
}
/**
* 删除好友
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/delFriend")
public AjaxResult delFriend(@Validated @RequestBody FriendVo04 friendVo) {
chatFriendService.delFriend(friendVo.getUserId());
return AjaxResult.success();
}
/**
* 设置备注
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/remark")
public AjaxResult remark(@Validated @RequestBody FriendVo05 friendVo) {
chatFriendService.setRemark(friendVo);
return AjaxResult.success();
}
/**
* 设置是否置顶
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/top")
public AjaxResult top(@Validated @RequestBody FriendVo09 friendVo) {
chatFriendService.setTop(friendVo);
return AjaxResult.success();
}
}

View File

@@ -0,0 +1,193 @@
package com.platform.modules.chat.controller;
import com.platform.common.version.ApiVersion;
import com.platform.common.version.VersionEnum;
import com.platform.common.web.controller.BaseController;
import com.platform.common.web.domain.AjaxResult;
import com.platform.modules.chat.service.ChatGroupService;
import com.platform.modules.chat.service.ChatMsgService;
import com.platform.modules.chat.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 群组
*/
@RestController
@Slf4j
@RequestMapping("/group")
public class GroupController extends BaseController {
@Resource
private ChatGroupService chatGroupService;
@Resource
private ChatMsgService chatMsgService;
/**
* 建立群组
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/createGroup")
public AjaxResult createGroup(@Validated @RequestBody List<Long> list) {
chatGroupService.createGroup(list);
return AjaxResult.success();
}
/**
* 获取群详情
*/
@ApiVersion(VersionEnum.V1_0_0)
@GetMapping("/getInfo/{groupId}")
public AjaxResult getInfo(@PathVariable Long groupId) {
return AjaxResult.success(chatGroupService.getInfo(groupId));
}
/**
* 邀请进群
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/invitationGroup")
public AjaxResult invitationGroup(@Validated @RequestBody GroupVo01 groupVo) {
Long groupId = groupVo.getGroupId();
List<Long> list = groupVo.getList();
chatGroupService.invitationGroup(groupId, list);
return AjaxResult.success();
}
/**
* 踢出群组
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/kickedGroup")
public AjaxResult kickedGroup(@Validated @RequestBody GroupVo01 groupVo) {
Long groupId = groupVo.getGroupId();
List<Long> list = groupVo.getList();
chatGroupService.kickedGroup(groupId, list);
return AjaxResult.success();
}
/**
* 修改群名
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/editGroupName")
public AjaxResult editGroupName(@Validated @RequestBody GroupVo02 groupVo) {
chatGroupService.editGroupName(groupVo);
return AjaxResult.success();
}
/**
* 获取群组二维码
*/
@ApiVersion(VersionEnum.V1_0_0)
@GetMapping("/getGroupQrCode/{groupId}")
public AjaxResult getGroupQrCode(@PathVariable Long groupId) {
return AjaxResult.success(chatGroupService.getGroupQrCode(groupId));
}
/**
* 修改群公告
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/editGroupNotice")
public AjaxResult editGroupNotice(@Validated @RequestBody GroupVo03 groupVo) {
chatGroupService.editGroupNotice(groupVo);
return AjaxResult.success();
}
/**
* 是否置顶
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/editTop")
public AjaxResult editTop(@Validated @RequestBody GroupVo04 groupVo) {
Long groupId = groupVo.getGroupId();
chatGroupService.editTop(groupId, groupVo.getTop());
return AjaxResult.success();
}
/**
* 是否免打扰
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/editDisturb")
public AjaxResult editDisturb(@Validated @RequestBody GroupVo05 groupVo) {
Long groupId = groupVo.getGroupId();
chatGroupService.editDisturb(groupId, groupVo.getDisturb());
return AjaxResult.success();
}
/**
* 是否保存群组
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/editKeepGroup")
public AjaxResult editKeepGroup(@Validated @RequestBody GroupVo06 groupVo) {
Long groupId = groupVo.getGroupId();
chatGroupService.editKeepGroup(groupId, groupVo.getKeepGroup());
return AjaxResult.success();
}
/**
* 退出群组
*/
@ApiVersion(VersionEnum.V1_0_0)
@GetMapping("/logoutGroup/{groupId}")
public AjaxResult logoutGroup(@PathVariable Long groupId) {
chatGroupService.logoutGroup(groupId);
return AjaxResult.success();
}
/**
* 解散群组
*/
@ApiVersion(VersionEnum.V1_0_0)
@GetMapping("/removeGroup/{groupId}")
public AjaxResult removeGroup(@PathVariable Long groupId) {
chatGroupService.removeGroup(groupId);
return AjaxResult.success();
}
/**
* 扫码查询
*/
@ApiVersion(VersionEnum.V1_0_0)
@GetMapping("/scanCode/{param}")
public AjaxResult scanCode(@PathVariable String param) {
return AjaxResult.success(chatGroupService.scanCode(param));
}
/**
* 加入群组
*/
@ApiVersion(VersionEnum.V1_0_0)
@GetMapping("/joinGroup/{groupId}")
public AjaxResult joinGroup(@PathVariable Long groupId) {
chatGroupService.joinGroup(groupId);
return AjaxResult.success();
}
/**
* 查询群列表
*/
@ApiVersion(VersionEnum.V1_0_0)
@GetMapping("/groupList")
public AjaxResult groupList() {
return AjaxResult.success(chatGroupService.groupList());
}
/**
* 发送信息
*/
@ApiVersion(VersionEnum.V1_0_0)
@PostMapping("/sendMsg")
public AjaxResult sendMsg(@Validated @RequestBody ChatVo02 chatVo) {
return AjaxResult.success(chatMsgService.sendGroupMsg(chatVo));
}
}

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