wildfire chat server source initial submit

This commit is contained in:
heavyrain2012
2019-03-16 17:04:23 +08:00
parent e59217bd10
commit cab3ca0916
1466 changed files with 528862 additions and 15919 deletions
-103
View File
@@ -1,103 +0,0 @@
Version 0.12:
Fixed issue #415
Dropped Hazelcast because at the moment Moquette is not designed to be clusterizable
Rewritten completly the broker to introduce the concept of MqttConnection
Dropped suppoert for MapDB and keeped only H2 MvStore for persistence
Version 0.10:
Netty epoll system added
Separated in memory and MapDB storage implementations
Cleanly separated inflight part (inbound and outbound)
Version 0.9:
Moved canRead check to topic from publish phase to subscribe (#144)
Added javadoc and src jars creations for JCenter/Maven central
Added persisting of clean session flag for already existing client session (fix issue #145)
Moved the message logic management fo FSM, to better control the concurrency.
Changed default config to use in memory store and not default moquette.mapdb
Version 0.8:
Introduced authenticator and authorizator implementations.
Fixed bug, doesn't close the channel when connect doesn't contains a valid proto name, issue #65
Fixed bug, does not check for zero length topic (filters) in PUBLISH, SUBSCRIBE, and UNSUBSCRIBE, issue #67
Fixed issue #69
Fixed bug #68, UNSUBSCRIBE has to fail on bad topicFilter names
Fixed bug #81, on stop of moquette broker it doesn't shutdown due to no shutdown of ringbuffer used on the back of ProtocolProcessor
Moved config to be not only filesystem based, but also classpath and memory (fix issue #73)
Switched from commit persistence on every change to time based to fix delay in handling many msgs/sec (fix issue #79)
Changed willMessage and password in CONNECT message from String to byte[] (fix issue #94 and #95)
Fixed ugly empty session storage, avoid fake NullObject empty subscription subscription
Added check to avoid creation of LostConnectionEvents for not yet completed connects (fix issue #88)
Removed LMAX ringbuffer to use multi threading with persistent (copy on write) data structures
Fixed bug that on connection stolen dropped also the new connection (fix issue #108)
Refactored the storage part to be more rational and use ClientSession concept
Added config property 'autosave_interval' (issue #97)
Removed leaking ref to parent's node (issue #119 and #126)
Added internal publish once the broker is embedded (issue #113)
Removed client active/not active state from persistence (issue #118)
Avoid deletion of client descriptor in case of multiple clients with same clientID (issues #116 and #124)
Removed a queue per client of inFlightIds (never cleaned) in favor of single atomic generator
Version 0.7:
Fix issue #38, added aligned to MQTT 3.1.1
Refactored big event switch to @Annotation dispatcher
Switched from HawtDB to MapDB
Fixed bug that didn't update client subscription on persistent store so that on server restart there where ghost subscriptions.
Fixed Issue #11, multiple republish of persisted QoS1/2 messages on clients reconnections.
Refactored the publish logic for QoS 1 2 to try to be more readable
Added packetIdentifier storage and generation
Fixed issue #16, bad packetId for will messages with qos > 0
Added store path variable "persistent_store" to configuration file (picked from Steven Warwick fork)
Removed OSGi bundle, developer time burner
Fixed github issue #30, returning error on mapdb file not deleted that corrupt reiterated builds
Added handling of allow_anonymous clients (as in mosquitto issue #34)
Modified the password_file format to use password stored in sha256 and not plain text
Added ACL file for topic read|write operations.
Removed cause of leakage of Netty Sessions (fix issue #39)
Cleaned Netty's stuff reference from Events used in Disruptor (fix issue #40)
Bad assignment of PacketID during publish to subscribed QoS0 topics (fix issue #38)
Fixed duplicated subscription with different QoS same client and topic (fix issue #49)
Fixed UNSUBSCRIBE that missed to remove the subscription from the persistent store (fix issue #50)
Fixed issue on multiple publish for overlapping subscription by the same client (fix issue #52).
Moved the authorization check before dropping an already connected client with same clientID (fix issue #44).
Fixed subscription tree that was unable to update clean session flag on subs with same client, topic and qos (fix issue #55)
Fixed overwriting of published qos in forward publish loop (fix issue #56)
Version 0.6:
Fix issue #42, bad password file path composition
Fix issue #43, missed topic case (finance/+ does has to match finance)
Integrated WebSocket support (issue #37)
Integrated SSL support (issue #41)
Fixed issue #40, check Logging inside the Felix OSGi
Fixed issue #46, implement correct behaviour for Will stuff
Splitted stores to persist messages and sessions into different interfaces
Renamed some methods in sessions store
Moved logging conf and slf4j-log4j deps out of broker to let it use the container's log configuration
Version 0.5:
Fix issue 20 (no flush of message on PINGREQ)
Fix issue 21 (bad channel timeout handler)
Created a self contained distribution package (issue 22)
Fix issue 28, OSGi-fied the broker
Fixed issue #31 missed to insert the hawtdb jars
Added a separate mvn module to host the OSGi bundle declaration
Fixed issue #30
Added password file for identity management on connect (Fix #23)
Version 0.4:
Dropped support for MINA
Fix Issue 15 (bad re-activation of client's subscriptions after a recconection)
Fix Issue 16 (bad re-publish of stored messages after a client reconnection)
Fixed a bug in unsubscribe due to missed clientID check
Fixed Issue 18 (descriptor of a disconnected (not active) client)
Bug fix for override subscriptions to the same /topic but with different QoS (latest wins)
Added test to check the downgrading of QoS of messages sent to registered subscribers (fixed a bug of double delivery of QoS2 messages)
Version 0.3:
Added Netty IO connectors
Dropped tests based on Fuse source client lib moving to Paho java client
Version 0.2:
Fixed issues (10, 11, 12, 13)
Fixed interdependency bug in test
+54
View File
@@ -0,0 +1,54 @@
Creative Commons Attribution-NoDerivs 3.0 Unported
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
1. Definitions
a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License.
b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License.
c. "Distribute" means to make available to the public the original and copies of the Work through sale or other transfer of ownership.
d. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License.
e. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast.
f. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work.
g. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
h. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images.
i. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; and,
b. to Distribute and Publicly Perform the Work including as incorporated in Collections.
c. For the avoidance of doubt:
i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License;
ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and,
iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License.
The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats, but otherwise you have no rights to make Adaptations. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved.
4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested.
b. If You Distribute, or Publicly Perform the Work or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work. The credit required by this Section 4(b) may be implemented in any reasonable manner; provided, however, that in the case of a Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties.
c. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
8. Miscellaneous
a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
b. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
c. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
d. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
e. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License.
Creative Commons may be contacted at http://creativecommons.org/.
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+17 -68
View File
@@ -1,81 +1,30 @@
# server
本工程为野火IM 社区版IM服务软件。野火IM作为一个通用的即时通讯SDK,可以集成到各种应用中。请阅读[docs](http://docs.wildfirechat.cn)或下载服务器[发布版本](https://github.com/wildfirechat/server/releases)
## What is Moquette?
#### 体验Demo
我们提供了体验demo,请使用微信扫码下载安装体验
[![Build Status](https://api.travis-ci.org/moquette-io/moquette.svg?branch=master)](https://travis-ci.org/moquette-io/moquette)
![野火IM](http://static.wildfirechat.cn/download_qrcode.png)
* [Documentation reference guide](http://moquette-io.github.io/moquette/) Guide on how to use and configure Moquette
* [Google Group](https://groups.google.com/forum/#!forum/moquette-mqtt) Google Group to participate in development discussions.
#### 应用截图
![ios-demo](http://static.wildfirechat.cn/ios-demo.gif)
Moquette aims to be a MQTT compliant broker. The broker supports QoS 0, QoS 1 and QoS 2.
<img src="http://static.wildfirechat.cn/ios-message-view.png" width = 50% height = 50% />
Its designed to be evented, uses Netty for the protocol encoding and decoding part.
## Embeddable
<img src="http://static.wildfirechat.cn/ios-contact-view.png" width = 50% height = 50% />
[Freedomotic](http://www.freedomotic.com/) Is an home automation framework, uses Moquette embedded to interface with MQTT world.
<img src="http://static.wildfirechat.cn/ios-discover-view.png" width = 50% height = 50% />
Moquette is also used into [Atomize Spin](http://atomizesoftware.com/spin) a software solution for the logistic field.
<img src="http://static.wildfirechat.cn/ios-settings-view.png" width = 50% height = 50% />
Part of moquette are used into the [Vertx MQTT module](https://github.com/giovibal/vertx-mqtt-broker-mod), into [MQTT spy](http://kamilfb.github.io/mqtt-spy/)
and into [WSO2 Messge broker](http://techexplosives-pamod.blogspot.it/2014/05/mqtt-transport-architecture-wso2-mb-3x.html).
<img src="http://static.wildfirechat.cn/ios-messagelist-view.png" width = 50% height = 50% />
## Try the demo instance
<img src="http://static.wildfirechat.cn/ios-chat-setting-view.png" width = 50% height = 50% />
Point your browser to [cloud instance](http://broker.moquette.io), request an account then use it from your MQTT clients.
<img src="http://static.wildfirechat.cn/ios-takephoto-view.png" width = 50% height = 50% />
## 1 minute set up
<img src="http://static.wildfirechat.cn/ios-record-voice-view.png" width = 50% height = 50% />
Start play with it, download the self distribution tar from [BinTray](https://bintray.com/artifact/download/andsel/generic/moquette-0.12.1.tar.gz) ,
the un untar and start the broker listening on `1883` port and enjoy!
<img src="http://static.wildfirechat.cn/ios-location-view.png" width = 50% height = 50% />
```
tar xvf moquette-distribution-0.12.1.tar.gz
cd bin
./moquette.sh
```
Or if you are on Windows shell
```
cd bin
.\moquette.bat
```
## Embedding in other projects
To embed Moquette in another maven project is sufficient to include a repository and declare the dependency:
```
<repositories>
<repository>
<id>bintray</id>
<url>https://jcenter.bintray.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
```
Include dependency in your project:
```
<dependency>
<groupId>io.moquette</groupId>
<artifactId>moquette-broker</artifactId>
<version>0.12.1</version>
</dependency>
```
## Build from sources
After a git clone of the repository, cd into the cloned sources and: `./gradlew clean moquette-distribution:distMoquetteTar` or
`./gradlew clean moquette-distribution:distMoquetteZip`.
In distribution/build directory will be produced the selfcontained file for the broker with all dependencies and a running script.
<img src="http://static.wildfirechat.cn/ios-voip-view.png" width = 50% height = 50% />
+18
View File
@@ -0,0 +1,18 @@
# 野火IM社区版
## 运行
在[release](https://github.com/wildfirechat/wildfirechat/releases),下载最新版本。然后按照[部署说明](http://docs.wildfirechat.cn/quick_start/server.html)部署
## 源码编译
下载最新代码后,进入到工程目录,执行`mvn clean install -Dmaven.test.skip=true`。编译成功后,在`distribution/target`目录找到版本。
## 特别说明
本服务软件基于或使用了如下开源软件,对他们的贡献表示感谢
* [Moquette](https://github.com/moquette-io/moquette)
* [LoServer](https://github.com/looly/loServer)
* [mqtt-jmeter](https://github.com/emqx/mqtt-jmeter)
## 版权说明
This work is licensed under a Creative Commons Attribution-NoDerivs 3.0 Unported License.
本工程遵循CC-BY-ND-3.0(Creative Commons Attribution-NoDerivs 3.0 Unported)协议,使用的开源组件版权说明请参考工程中的license文件
-27
View File
@@ -1,27 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<title>About</title>
</head>
<body lang="EN-US">
<h2>About Moquette MQT broker</h2>
<p>June 11, 2014</p>
<h3>License</h3>
<p>The Content is provided to you under the terms and conditions of the
Eclipse Public License Version 1.0 (&quot;EPL&quot;). A copy of the EPL is available
at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
<p>The Content is
being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
apply to your use of any object code in the Content. Check the Redistributor's license that was
provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
indicated below, the terms and conditions of the EPL still apply to any source code in the Content
and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
</body>
</html>
-39
View File
@@ -1,39 +0,0 @@
description = 'Moquette - broker'
dependencies {
compile group: 'io.netty', name: 'netty-common', version: nettyVersion
compile group: 'io.netty', name: 'netty-buffer', version: nettyVersion
compile group: 'io.netty', name: 'netty-transport', version: nettyVersion
compile group: 'io.netty', name: 'netty-handler', version: nettyVersion
compile group: 'io.netty', name: 'netty-codec', version: nettyVersion
compile group: 'io.netty', name: 'netty-codec-http', version: nettyVersion
compile group: 'io.netty', name: 'netty-codec-mqtt', version: nettyVersion
compile group: 'io.netty', name: 'netty-transport-native-epoll', version: nettyVersion, classifier: 'linux-x86_64'
// <classifier>${os.detected.name}-${os.detected.arch}</classifier>
compile group: 'com.h2database', name: 'h2-mvstore', version: '1.4.196'
compile group: 'com.zaxxer', name: 'HikariCP', version:'2.4.7'
compile group: 'io.dropwizard.metrics', name: 'metrics-core', version:'3.2.2'
compile group: 'io.dropwizard.metrics', name: 'metrics-jvm', version:'3.2.2'
compile group: 'com.librato.metrics', name: 'metrics-librato', version:'5.1.0'
compile group: 'com.librato.metrics', name: 'librato-java' , version:'2.1.0'
compile group: 'com.bugsnag', name: 'bugsnag', version:'[3.0,4.0)'
compile group: 'commons-codec', name: 'commons-codec', version:'1.10'
compile group: 'org.slf4j', name: 'slf4j-api', version:'1.7.5'
runtime group: 'org.slf4j', name: 'slf4j-log4j12', version:'1.7.5' // only for local tests
testCompile group: 'org.slf4j', name: 'slf4j-log4j12', version:'1.7.5'
testCompile group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version:'1.12'
testCompile group: 'org.eclipse.paho', name: 'org.eclipse.paho.client.mqttv3', version:'1.2.0'
testCompile group: 'org.eclipse.jetty.websocket', name: 'websocket-client', version:'9.2.0.M1'
testCompile group: 'com.h2database', name: 'h2', version:'1.4.196'
testRuntime group: 'io.netty', name: 'netty-tcnative', version: '2.0.10.Final', classifier: 'linux-x86_64'
}
+15
View File
@@ -0,0 +1,15 @@
#Generated by Git-Commit-Id-Plugin
#Sat Mar 16 16:47:17 CST 2019
git.commit.id.abbrev=80d2a9d
git.commit.user.email=[email protected]
git.commit.message.full=update\n
git.commit.id=80d2a9d1c84db6f17845c6c43de6c84e6de2a913
git.commit.message.short=update
git.commit.user.name=heavyrain.lee
git.build.user.name=heavyrain.lee
git.commit.id.describe=v0.10-411-g80d2a9d-dirty
git.build.user.email=[email protected]
git.branch=IM
git.commit.time=20190315085044
git.build.time=20190316164717
git.remote.origin.url=https\://github.com/heavyrain2012/wildfirechat.git
+130
View File
@@ -0,0 +1,130 @@
<hazelcast
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/config
http://www.hazelcast.com/schema/config/hazelcast-config-3.5.xsd"
xmlns="http://www.hazelcast.com/schema/config">
<group>
<!--<name>wfc-dev</name>-->
<name>wfc-pro</name>
</group>
<network>
<join>
<multicast enabled="true"/>
</join>
</network>
<!-- map eviction -->
<!-- http://docs.hazelcast.org/docs/latest-development/manual/html/Distributed_Data_Structures/Map/Map_Eviction.html -->
<map name="messages_map">
<!-- 7 days -->
<time-to-live-seconds>604800</time-to-live-seconds>
<eviction-policy>LRU</eviction-policy>
<max-size policy="PER_NODE">1000000</max-size>
<eviction-percentage>10</eviction-percentage>
<map-store enabled="true">
<class-name>io.moquette.persistence.MessageLoader</class-name>
<write-delay-seconds>0</write-delay-seconds>
</map-store>
</map>
<map name="groups_map">
<!-- 7 days -->
<time-to-live-seconds>604800</time-to-live-seconds>
<eviction-policy>LRU</eviction-policy>
<max-size policy="PER_NODE">1000000</max-size>
<eviction-percentage>10</eviction-percentage>
<map-store enabled="true">
<class-name>io.moquette.persistence.GroupLoader</class-name>
<write-delay-seconds>0</write-delay-seconds>
</map-store>
</map>
<map name="users">
<time-to-live-seconds>604800</time-to-live-seconds>
<eviction-policy>LRU</eviction-policy>
<max-size policy="PER_NODE">1000000</max-size>
<eviction-percentage>10</eviction-percentage>
<map-store enabled="true">
<class-name>io.moquette.persistence.UserLoader</class-name>
<write-delay-seconds>0</write-delay-seconds>
</map-store>
</map>
<map name="user_status">
<time-to-live-seconds>604800</time-to-live-seconds>
<eviction-policy>LRU</eviction-policy>
<max-size policy="PER_NODE">1000000</max-size>
<eviction-percentage>10</eviction-percentage>
<map-store enabled="true">
<class-name>io.moquette.persistence.UserStatusLoader</class-name>
<write-delay-seconds>0</write-delay-seconds>
</map-store>
</map>
<set name="node_ids"/>
<multimap name="user_setting">
</multimap>
<multimap name="group_members">
</multimap>
<map name="chatrooms">
<time-to-live-seconds>604800</time-to-live-seconds>
<eviction-policy>LRU</eviction-policy>
<map-store enabled="true">
<class-name>io.moquette.persistence.ChatroomLoader</class-name>
<write-delay-seconds>0</write-delay-seconds>
</map-store>
</map>
<map name="user_chatroom">
</map>
<map name="robots">
<!-- 7 days -->
<time-to-live-seconds>604800</time-to-live-seconds>
<eviction-policy>LRU</eviction-policy>
<max-size policy="PER_NODE">1000000</max-size>
<eviction-percentage>10</eviction-percentage>
<map-store enabled="true">
<class-name>io.moquette.persistence.RobotLoader</class-name>
<write-delay-seconds>0</write-delay-seconds>
</map-store>
</map>
<map name="things">
</map>
<multimap name="user_robots">
</multimap>
<multimap name="user_things">
</multimap>
<multimap name="chatroom_members">
</multimap>
<multimap name="user_friends">
</multimap>
<multimap name="user_friends_request">
</multimap>
<map name="channels_map">
<!-- 7 days -->
<time-to-live-seconds>604800</time-to-live-seconds>
<eviction-policy>LRU</eviction-policy>
<max-size policy="PER_NODE">1000000</max-size>
<eviction-percentage>10</eviction-percentage>
<map-store enabled="true">
<class-name>io.moquette.persistence.ChannelLoader</class-name>
<write-delay-seconds>0</write-delay-seconds>
</map-store>
</map>
<multimap name="channel_listeners">
</multimap>
</hazelcast>
-22
View File
@@ -1,22 +0,0 @@
##############################################
# Moquette configuration file.
#
# The synthax is equals to mosquitto.conf
#
##############################################
port 1883
websocket_port 8080
host 0.0.0.0
password_file config/password_file.conf
#false to accept only client connetions with credentials
#true to accept client connection without credentails, validating only the one that provides
allow_anonymous true
#false to prohibit clients from connecting without a clientid.
#true to allow clients to connect without a clientid. One will be generated for them.
allow_zero_byte_client_id false
-2
View File
@@ -1,2 +0,0 @@
#the password is sha256(passwd)
testuser:0d6be69b264717f2dd33652e212b173104b4a647b7c11ae72e9885f11cd312fb
+101
View File
@@ -0,0 +1,101 @@
#*********************************************************************
# Server configuration
#*********************************************************************
#服务器的接入IP。给客户端提供是${server.ip}和${http_port}。
#客户端会从${http_port}端口获取到长链接端口。
server.ip 0.0.0.0
##原生客户端长链接端口
port 1883
##客户端短链接端口
http_port 8080
##WEB客户端长链接端口
websocket_port 8083
#管理端口
http.admin.port 18080
##节点ID,当集群部署时,一定不能有重复。
node_id 1
##绑定IP,保持不变即可
host 0.0.0.0
##本地绑定端口
local_port 8080
##是否使用内置DB。0使用mysql;1使用h2db
embed.db 1
##服务器管理接口密钥
http.admin.secret_key 123456
##客户端协议栈密钥,需要与客户端协议栈文件libemqq.cc文件中的aes_key值保持一致,16进制byte用逗号分割
client.proto.secret_key 0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F
#*********************************************************************
# hazelcase configuration
#*********************************************************************
hazelcast.configuration config/hazelcast.xml
#*********************************************************************
# Media server configuration
#*********************************************************************
##是否使用七牛云存储。1使用七牛;0使用内存文件服务器。
media.server.use_qiniu 0
##媒体类型分类
#Media_Type_GENERAL = 0,
#Media_Type_IMAGE = 1,
#Media_Type_VOICE = 2,
#Media_Type_VIDEO = 3,
#Media_Type_FILE = 4,
#Media_Type_PORTRAIT = 5,
#Media_Type_FAVORITE = 6
# qiniu media server configuration
qiniu.server_url http://up.qbox.me
qiniu.access_key tU3vdBK5BL5j4N7jI5N5uZgq_HQDo170w5C9Amnn
qiniu.secret_key YfQIJdgp5YGhwEw14vGpaD2HJZsuJldWtqens7i5
qiniu.bucket_general_name media
qiniu.bucket_general_domain http://pghnpyzos.bkt.clouddn.com
qiniu.bucket_image_name media
qiniu.bucket_image_domain http://pghnpyzos.bkt.clouddn.com
qiniu.bucket_voice_name media
qiniu.bucket_voice_domain http://pghnpyzos.bkt.clouddn.com
qiniu.bucket_video_name media
qiniu.bucket_video_domain http://pghnpyzos.bkt.clouddn.com
qiniu.bucket_file_name media
qiniu.bucket_file_domain http://pghnpyzos.bkt.clouddn.com
qiniu.bucket_portrait_name storage
qiniu.bucket_portrait_domain http://cdn2.wildfirechat.cn
qiniu.bucket_favorite_name storage
qiniu.bucket_favorite_domain http://cdn2.wildfirechat.cn
# local media server configuration
# 本地媒体服务器配置。
local.media.storage.root media
#*********************************************************************
# Push server configuration
#*********************************************************************
##安卓推送服务器地址
push.android.server.address http://localhost:8085/android/push
##苹果推送服务器地址
push.ios.server.address http://localhost:8085/ios/push
#*********************************************************************
# Sensitive configuration
#*********************************************************************
##文本敏感词过滤处理方法,0 发送失败;1 发送成功但消息被服务器直接丢弃;2 命中的敏感词被替换成***
sensitive.filter.type 0
#*********************************************************************
# Message forward configuration
#*********************************************************************
##消息转发地址
#message.forward.url http://localhost:8087/message/forward
+229
View File
@@ -0,0 +1,229 @@
DROP TABLE IF EXISTS `t_messages`;
CREATE TABLE `t_messages` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_mid` bigint(20) NOT NULL,
`_from` varchar(64) NOT NULL,
`_type` tinyint NOT NULL DEFAULT 0,
`_target` varchar(64) NOT NULL,
`_line` int(11) NOT NULL DEFAULT 0,
`_data` BLOB NOT NULL,
`_searchable_key` TEXT DEFAULT NULL,
`_dt` DATETIME NOT NULL DEFAULT NOW(),
UNIQUE INDEX `message_uid_index` (`_mid` ASC)
);
DROP TABLE IF EXISTS `t_user_messages`;
CREATE TABLE `t_user_messages` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_mid` bigint(20) NOT NULL,
`_uid` varchar(64) NOT NULL,
`_seq` bigint(20) NOT NULL,
`_dt` DATETIME NOT NULL DEFAULT NOW(),
UNIQUE INDEX `message_seq_uid_index` (`_uid` DESC, `_seq` DESC)
);
DROP TABLE IF EXISTS `t_group`;
CREATE TABLE `t_group` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_gid` varchar(64) NOT NULL,
`_name` varchar(64) DEFAULT '',
`_portrait` varchar(1024) DEFAULT '',
`_owner` varchar(64) DEFAULT '',
`_type` tinyint NOT NULL DEFAULT 0,
`_extra` TEXT DEFAULT NULL,
`_dt` bigint(20) NOT NULL,
`_member_count` int(11) DEFAULT 0,
`_member_dt` bigint(20) NOT NULL,
UNIQUE INDEX `group_gid_index` (`_gid` ASC),
INDEX `group_name_index` (`_name` ASC)
);
DROP TABLE IF EXISTS `t_group_member`;
CREATE TABLE `t_group_member` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_gid` varchar(64) NOT NULL,
`_mid` varchar(64) DEFAULT '',
`_alias` varchar(64) DEFAULT '',
`_type` tinyint DEFAULT 0,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `user_gid_mid_index` (`_gid`, `_mid`)
);
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_name` varchar(64) DEFAULT '',
`_display_name` varchar(64) DEFAULT '',
`_gender` int(11) NOT NULL DEFAULT 0,
`_portrait` varchar(1024) DEFAULT '',
`_mobile` varchar(64) DEFAULT '',
`_email` varchar(64) DEFAULT '',
`_address` varchar(64) DEFAULT '',
`_company` varchar(64) DEFAULT '',
`_social` varchar(64) DEFAULT '',
`_passwd_md5` varchar(64) DEFAULT '',
`_salt` varchar(64) DEFAULT '',
`_extra` TEXT DEFAULT NULL,
`_type` tinyint DEFAULT 0,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `user_uid_index` (`_uid` ASC),
UNIQUE INDEX `user_name_index` (`_name` ASC),
INDEX `user_display_name_index` (`_display_name` ASC),
INDEX `user_mobile_index` (`_mobile` ASC),
INDEX `user_email_index` (`_email` ASC)
);
DROP TABLE IF EXISTS `t_user_status`;
CREATE TABLE `t_user_status` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_status` int(11) NOT NULL DEFAULT 0,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `user_status_uid_index` (`_uid` ASC),
);
DROP TABLE IF EXISTS `t_friend_request`;
CREATE TABLE `t_friend_request` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_friend_uid` varchar(64) NOT NULL,
`_reason` TEXT DEFAULT NULL,
`_status` tinyint NOT NULL DEFAULT 0,
`_dt` bigint(20) NOT NULL,
`_from_read_status` tinyint DEFAULT 0,
`_to_read_status` tinyint DEFAULT 0,
UNIQUE INDEX `fr_user_target_index` (`_uid` ASC, `_friend_uid` ASC)
);
DROP TABLE IF EXISTS `t_friend`;
CREATE TABLE `t_friend` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_friend_uid` varchar(64) NOT NULL,
`_state` tinyint DEFAULT 0,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `f_user_target_index` (`_uid` ASC, `_friend_uid` ASC)
);
DROP TABLE IF EXISTS `t_user_setting`;
CREATE TABLE `t_user_setting` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_scope` int NOT NULL,
`_key` varchar(64) NOT NULL,
`_value` varchar(4096) NOT NULL,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `user_setting_index` (`_uid` ASC, `_scope` ASC, `_key` ASC)
);
DROP TABLE IF EXISTS `t_id_generator`;
CREATE TABLE `t_id_generator` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT
)
AUTO_INCREMENT = 3;
DROP TABLE IF EXISTS `t_channel`;
CREATE TABLE `t_channel` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_cid` varchar(64) NOT NULL,
`_name` varchar(64) DEFAULT '',
`_portrait` varchar(1024) DEFAULT '',
`_owner` varchar(64) DEFAULT '',
`_status` tinyint NOT NULL DEFAULT 0,
`_desc` TEXT DEFAULT NULL,
`_secret` varchar(64) DEFAULT '',
`_callback` varchar(1024) DEFAULT '',
`_automatic` tinyint NOT NULL DEFAULT 0,
`_extra` TEXT DEFAULT NULL,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `channel_cid_index` (`_cid` DESC),
INDEX `channel_name_index` (`_name` DESC)
);
DROP TABLE IF EXISTS `t_channel_listener`;
CREATE TABLE `t_channel_listener` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_cid` varchar(64) NOT NULL,
`_mid` varchar(64) DEFAULT '',
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `channel_cid_mid_index` (`_cid`, `_mid`)
);
DROP TABLE IF EXISTS `t_user_session`;
CREATE TABLE `t_user_session` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_cid` varchar(64) NOT NULL,
`_token` varchar(64) DEFAULT '',
`_voip_token` varchar(64) DEFAULT '',
`_secret` varchar(64) NOT NULL,
`_db_secret` varchar(64) NOT NULL,
`_platform` tinyint DEFAULT 0,
`_push_type` tinyint DEFAULT 0,
`_package_name` varchar(64) DEFAULT '',
`_device_name` varchar(64) DEFAULT '',
`_device_version` varchar(64) DEFAULT '',
`_phone_name` varchar(64) DEFAULT '',
`_language` varchar(64) DEFAULT '',
`_carrier_name` varchar(64) DEFAULT '',
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `session_uid_cid_index` (`_cid`, `_uid`)
);
DROP TABLE IF EXISTS `t_robot`;
CREATE TABLE `t_robot` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_owner` varchar(64) DEFAULT '',
`_secret` varchar(64) DEFAULT '',
`_callback` varchar(1024) DEFAULT '',
`_extra` TEXT DEFAULT NULL,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `robot_uid_index` (`_uid` ASC),
INDEX `robot_owner_index` (`_owner` ASC)
);
DROP TABLE IF EXISTS `t_thing`;
CREATE TABLE `t_thing` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_owner` varchar(64) DEFAULT '',
`_token` varchar(64) DEFAULT '',
`_extra` TEXT DEFAULT NULL,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `thing_uid_index` (`_uid` ASC),
INDEX `thing_owner_index` (`_owner` ASC)
);
DROP TABLE IF EXISTS `t_chatroom`;
CREATE TABLE `t_chatroom` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_cid` varchar(64) NOT NULL,
`_title` varchar(64) DEFAULT '',
`_portrait` varchar(1024) DEFAULT '',
`_state` tinyint NOT NULL DEFAULT 0,
`_desc` TEXT DEFAULT NULL,
`_extra` TEXT DEFAULT NULL,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `chatroom_cid_index` (`_cid` DESC)
);
DROP TABLE IF EXISTS `t_sensitiveword`;
CREATE TABLE `t_sensitiveword` (
`_word` TEXT DEFAULT NULL
);
insert into t_chatroom (`_cid`, `_title`, `_desc`,`_dt`) values ('chatroom1','火信聊天室1','火信测试聊天室1,用来演示聊天室功能', 1);
insert into t_chatroom (`_cid`, `_title`, `_desc`,`_dt`) values ('chatroom2','火信聊天室2','火信测试聊天室2,用来演示聊天室功能', 1);
insert into t_chatroom (`_cid`, `_title`, `_desc`,`_dt`) values ('chatroom3','火信聊天室3','火信测试聊天室3,用来演示聊天室功能', 1);
+27
View File
@@ -0,0 +1,27 @@
upstream imserver {
server 127.0.0.1:8080; #Apache
}
server {
listen 80;
server_name www.wildfirechat.cn;
root html;
index index.html index.htm index.php;
## send request back to im server ##
location /im {
proxy_pass http://imserver;
}
location /route {
proxy_pass http://imserver;
}
location /api {
proxy_pass http://imserver;
}
location / {
proxy_pass https://wildfirechat.github.io/docs/;
}
}
Executable
+365
View File
@@ -0,0 +1,365 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<relativePath>../</relativePath>
<artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId>
<version>0.10</version>
</parent>
<artifactId>moquette-broker</artifactId>
<packaging>jar</packaging>
<name>Moquette - broker</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<bintray.package>io.moquette.moquette-broker</bintray.package>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>cn.wildfirechat</groupId>
<artifactId>client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.wildfirechat</groupId>
<artifactId>wildfirechat-server</artifactId>
<version>${project.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.1</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>${netty.version}</version>
<classifier>linux-x86_64</classifier>
<!--
<classifier>${os.detected.name}-${os.detected.arch}</classifier>
-->
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-jvm</artifactId>
<version>3.2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>com.librato.metrics</groupId>
<artifactId>metrics-librato</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>com.bugsnag</groupId>
<artifactId>bugsnag</artifactId>
<version>[3.0,4.0)</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>3.9.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.xiaoleilu/hutool -->
<dependency>
<groupId>com.xiaoleilu</groupId>
<artifactId>hutool</artifactId>
<version>2.15.11</version>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- needs extra dependencies: objenesis & hamcrest -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.fusesource.mqtt-client</groupId>
<artifactId>mqtt-client</artifactId>
<version>1.12</version>
</dependency>
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-client</artifactId>
<version>9.2.0.M1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<!-- use 2.6.0 for Java 7 projects -->
<version>2.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.191</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.1.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.197</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
</configuration>
</plugin>
<!-- Generate the test-jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Includes to generate OSGi headers -->
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>2.1.5</version>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<!--日期格式;默认值:dd.MM.yyyy '@' HH:mm:ss z;-->
<dateFormat>yyyyMMddHHmmss</dateFormat>
<!--,构建过程中,是否打印详细信息;默认值:false;-->
<verbose>true</verbose>
<!-- ".git"文件路径;默认值:${project.basedir}/.git; -->
<dotGitDirectory>${project.basedir}/.git</dotGitDirectory>
<!--若项目打包类型为pom,是否取消构建;默认值:true;-->
<skipPoms>false</skipPoms>
<!--是否生成"git.properties"文件;默认值:false;-->
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<!--指定"git.properties"文件的存放路径(相对于${project.basedir}的一个路径);-->
<generateGitPropertiesFilename>config/git.properties</generateGitPropertiesFilename>
<!--".git"文件夹未找到时,构建是否失败;若设置true,则构建失败;若设置false,则跳过执行该目标;默认值:true;-->
<failOnNoGitDirectory>true</failOnNoGitDirectory>
<!--git描述配置,可选;由JGit提供实现;-->
<gitDescribe>
<!--是否生成描述属性-->
<skip>false</skip>
<!--提交操作未发现tag时,仅打印提交操作ID,-->
<always>false</always>
<!--提交操作ID显式字符长度,最大值为:40;默认值:7;
0代表特殊意义;后面有解释;
-->
<abbrev>7</abbrev>
<!--构建触发时,代码有修改时(即"dirty state"),添加指定后缀;默认值:"";-->
<dirty>-dirty</dirty>
<!--always print using the "tag-commits_from_tag-g_commit_id-maybe_dirty" format, even if "on" a tag.
The distance will always be 0 if you're "on" the tag.
-->
<forceLongFormat>false</forceLongFormat>
</gitDescribe>
</configuration>
</plugin>
</plugins>
</build>
</project>
+1
View File
@@ -0,0 +1 @@
CREATE DATABASE IF NOT EXISTS wfchat CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+274
View File
@@ -0,0 +1,274 @@
DROP TABLE IF EXISTS `t_messages`;
CREATE TABLE `t_messages` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_mid` bigint(20) NOT NULL,
`_from` varchar(64) NOT NULL,
`_type` tinyint NOT NULL DEFAULT 0,
`_target` varchar(64) NOT NULL,
`_line` int(11) NOT NULL DEFAULT 0,
`_data` BLOB NOT NULL,
`_searchable_key` TEXT DEFAULT NULL,
`_dt` DATETIME NOT NULL,
UNIQUE INDEX `message_uid_index` (`_mid` DESC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_user_messages`;
CREATE TABLE `t_user_messages` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_mid` bigint(20) NOT NULL,
`_uid` varchar(64) NOT NULL,
`_seq` bigint(20) NOT NULL,
`_dt` DATETIME NOT NULL DEFAULT NOW(),
INDEX `message_seq_uid_index` (`_uid` DESC, `_seq` DESC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_group`;
CREATE TABLE `t_group` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_gid` varchar(64) NOT NULL,
`_name` varchar(64) DEFAULT '',
`_portrait` varchar(1024) DEFAULT '',
`_owner` varchar(64) DEFAULT '',
`_type` tinyint NOT NULL DEFAULT 0,
`_extra` TEXT DEFAULT NULL,
`_dt` bigint(20) NOT NULL,
`_member_count` int(11) DEFAULT 0,
`_member_dt` bigint(20) NOT NULL,
UNIQUE INDEX `group_gid_index` (`_gid` DESC),
INDEX `group_name_index` (`_name` DESC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_group_member`;
CREATE TABLE `t_group_member` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_gid` varchar(64) NOT NULL,
`_mid` varchar(64) DEFAULT '',
`_alias` varchar(64) DEFAULT '',
`_type` tinyint DEFAULT 0 COMMENT "0普通成员;1,管理员;2,群主,与Owner相同",
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `user_gid_mid_index` (`_gid`, `_mid`)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_name` varchar(64) DEFAULT '',
`_display_name` varchar(64) DEFAULT '',
`_gender` int(11) NOT NULL DEFAULT 0,
`_portrait` varchar(1024) DEFAULT '',
`_mobile` varchar(64) DEFAULT '',
`_email` varchar(64) DEFAULT '',
`_address` varchar(64) DEFAULT '',
`_company` varchar(64) DEFAULT '',
`_social` varchar(64) DEFAULT '',
`_passwd_md5` varchar(64) DEFAULT '',
`_salt` varchar(64) DEFAULT '',
`_extra` TEXT DEFAULT NULL,
`_type` tinyint DEFAULT 0,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `user_uid_index` (`_uid` ASC),
UNIQUE INDEX `user_name_index` (`_name` ASC),
INDEX `user_display_name_index` (`_display_name` ASC),
INDEX `user_mobile_index` (`_mobile` ASC),
INDEX `user_email_index` (`_email` ASC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_user_status`;
CREATE TABLE `t_user_status` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_status` int(11) NOT NULL DEFAULT 0,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `user_status_uid_index` (`_uid` ASC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_friend_request`;
CREATE TABLE `t_friend_request` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_friend_uid` varchar(64) NOT NULL,
`_reason` TEXT DEFAULT NULL,
`_status` tinyint NOT NULL DEFAULT 0,
`_dt` bigint(20) NOT NULL,
`_from_read_status` tinyint DEFAULT 0,
`_to_read_status` tinyint DEFAULT 0,
UNIQUE INDEX `fr_user_target_index` (`_uid` ASC, `_friend_uid` ASC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_friend`;
CREATE TABLE `t_friend` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_friend_uid` varchar(64) NOT NULL,
`_state` tinyint DEFAULT 0 COMMENT "0, normal; 1, deleted; 2, blacked",
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `f_user_target_index` (`_uid` ASC, `_friend_uid` ASC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_user_setting`;
CREATE TABLE `t_user_setting` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_scope` int NOT NULL,
`_key` varchar(64) NOT NULL,
`_value` varchar(4096) NOT NULL,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `user_setting_index` (`_uid` ASC, `_scope` ASC, `_key` ASC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_id_generator`;
CREATE TABLE `t_id_generator` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci
AUTO_INCREMENT = 3;
DROP TABLE IF EXISTS `t_channel`;
CREATE TABLE `t_channel` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_cid` varchar(64) NOT NULL,
`_name` varchar(64) DEFAULT '',
`_portrait` varchar(1024) DEFAULT '',
`_owner` varchar(64) DEFAULT '',
`_status` tinyint NOT NULL DEFAULT 0,
`_desc` TEXT DEFAULT NULL,
`_secret` varchar(64) DEFAULT '',
`_callback` varchar(1024) DEFAULT '',
`_extra` TEXT DEFAULT NULL,
`_automatic` tinyint NOT NULL DEFAULT 0,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `channel_cid_index` (`_cid` DESC),
INDEX `channel_name_index` (`_name` DESC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_channel_listener`;
CREATE TABLE `t_channel_listener` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_cid` varchar(64) NOT NULL,
`_mid` varchar(64) DEFAULT '',
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `channel_cid_mid_index` (`_cid`, `_mid`)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_user_session`;
CREATE TABLE `t_user_session` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_cid` varchar(64) NOT NULL,
`_token` varchar(64) DEFAULT '',
`_voip_token` varchar(64) DEFAULT '',
`_secret` varchar(64) NOT NULL,
`_db_secret` varchar(64) NOT NULL,
`_platform` tinyint DEFAULT 0,
`_push_type` tinyint DEFAULT 0,
`_package_name` varchar(64) DEFAULT '',
`_device_name` varchar(64) DEFAULT '',
`_device_version` varchar(64) DEFAULT '',
`_phone_name` varchar(64) DEFAULT '',
`_language` varchar(64) DEFAULT '',
`_carrier_name` varchar(64) DEFAULT '',
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `session_uid_cid_index` (`_cid`, `_uid`)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_robot`;
CREATE TABLE `t_robot` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_owner` varchar(64) DEFAULT '',
`_secret` varchar(64) DEFAULT '',
`_callback` varchar(1024) DEFAULT '',
`_state` tinyint DEFAULT 0,
`_extra` TEXT DEFAULT NULL,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `robot_uid_index` (`_uid` ASC),
INDEX `robot_owner_index` (`_owner` ASC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_thing`;
CREATE TABLE `t_thing` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_uid` varchar(64) NOT NULL,
`_owner` varchar(64) DEFAULT '',
`_token` varchar(64) DEFAULT '',
`_state` tinyint DEFAULT 0,
`_extra` TEXT DEFAULT NULL,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `thing_uid_index` (`_uid` ASC),
INDEX `thing_owner_index` (`_owner` ASC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_chatroom`;
CREATE TABLE `t_chatroom` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`_cid` varchar(64) NOT NULL,
`_title` varchar(64) DEFAULT '',
`_portrait` varchar(1024) DEFAULT '',
`_state` tinyint NOT NULL DEFAULT 0,
`_desc` TEXT DEFAULT NULL,
`_extra` TEXT DEFAULT NULL,
`_dt` bigint(20) NOT NULL,
UNIQUE INDEX `chatroom_cid_index` (`_cid` DESC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `t_sensitiveword`;
CREATE TABLE `t_sensitiveword` (
`_word` TEXT DEFAULT NULL
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
+5
View File
@@ -0,0 +1,5 @@
insert into t_chatroom (`_cid`, `_title`, `_desc`,`_dt`) values ('chatroom1','火信聊天室1','火信测试聊天室1,用来演示聊天室功能', 1);
insert into t_chatroom (`_cid`, `_title`, `_desc`,`_dt`) values ('chatroom2','火信聊天室2','火信测试聊天室2,用来演示聊天室功能', 1);
insert into t_chatroom (`_cid`, `_title`, `_desc`,`_dt`) values ('chatroom3','火信聊天室3','火信测试聊天室3,用来演示聊天室功能', 1);
+83
View File
@@ -0,0 +1,83 @@
#!/bin/bash
show_help_info(){
echo "### Usage: ./initial_db.sh -u{user} -p{password}"
echo "### 初始化数据库。"
echo "###"
}
for i in "$@"
do
PFLAG=`echo $i|cut -b1-2`
PPARAM=`echo $i|cut -b3-`
if [ $PFLAG = "-u" ]; then
USER=${PPARAM}
elif [ $PFLAG = "-p" ]; then
PASSWORD=${PPARAM}
elif [ $PFLAG = "-h" ]; then
show_help_info
exit 1
fi
done
echo
if [ "${USER}" = "" ]; then
show_help_info
exit 1
elif [ "${PASSWORD}" = "" ]; then
show_help_info
exit 1
fi
mysql -u${USER} -p${PASSWORD} -e"source 1_create_database.sql;"
mysql -u${USER} -p${PASSWORD} -e"use wfchat; source 2_create_table.sql; source 3_create_default_chatroom.sql;"
for((i=0;i<36;i++));
do
create_message_tbl_sql="USE wfchat;
DROP TABLE IF EXISTS \`t_messages_${i}\`;
CREATE TABLE \`t_messages_${i}\` (
\`id\` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
\`_mid\` bigint(20) NOT NULL,
\`_from\` varchar(64) NOT NULL,
\`_type\` tinyint NOT NULL DEFAULT 0,
\`_target\` varchar(64) NOT NULL,
\`_line\` int(11) NOT NULL DEFAULT 0,
\`_data\` BLOB NOT NULL,
\`_searchable_key\` TEXT DEFAULT NULL,
\`_dt\` DATETIME NOT NULL,
INDEX \`message_uid_index\` (\`_mid\` DESC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
"
echo "create table t_messages_$i"
mysql -u${USER} -p${PASSWORD} -e"${create_message_tbl_sql}"
done
for((i=0;i<128;i++));
do
create_user_message_tbl_sql="USE wfchat;
DROP TABLE IF EXISTS \`t_user_messages_${i}\`;
CREATE TABLE \`t_user_messages_${i}\` (
\`id\` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
\`_mid\` bigint(20) NOT NULL,
\`_uid\` varchar(64) NOT NULL,
\`_seq\` bigint(20) NOT NULL,
\`_dt\` DATETIME NOT NULL DEFAULT NOW(),
INDEX \`message_seq_uid_index\` (\`_uid\` DESC, \`_seq\` DESC)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
"
echo "create table t_user_messages_$i"
mysql -u${USER} -p${PASSWORD} -e"${create_user_message_tbl_sql}"
done
@@ -0,0 +1,49 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package cn.wildfirechat.push;
public class PushMessage {
public String sender;
public String senderName;
public int convType;
public String target;
public String targetName;
public int line;
public int cntType;
public long serverTime;
//消息的类型,普通消息通知栏;voip要透传。
public int pushMessageType;
//推送类型,android推送分为小米/华为/魅族等。ios分别为开发和发布。
public int pushType;
public String pushContent;
public int unReceivedMsg;
public int mentionedType;
public String packageName;
public String deviceToken;
public String voipDeviceToken;
public boolean isHiddenDetail;
public String language;
public PushMessage(String sender, int conversationType, String target, int line, int messageContentType, long serverTime, String senderName, String targetName, int unReceivedMsg, int mentionedType, boolean isHiddenDetail, String language) {
this.sender = sender;
this.convType = conversationType;
this.target = target;
this.senderName = senderName;
this.targetName = targetName;
this.line = line;
this.cntType = messageContentType;
this.serverTime = serverTime;
this.unReceivedMsg = unReceivedMsg;
this.pushMessageType = (cntType == 400 ? PushServer.PushMessageType.PUSH_MESSAGE_TYPE_VOIP_INVITE : (cntType == 402 ? PushServer.PushMessageType.PUSH_MESSAGE_TYPE_VOIP_BYE : PushServer.PushMessageType.PUSH_MESSAGE_TYPE_NORMAL));
this.mentionedType = mentionedType;
this.isHiddenDetail = isHiddenDetail;
this.language = language;
}
}
@@ -0,0 +1,103 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package cn.wildfirechat.push;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import io.moquette.persistence.MemorySessionStore;
import io.moquette.server.config.IConfig;
import io.moquette.spi.ClientSession;
import io.moquette.spi.ISessionsStore;
import io.netty.util.internal.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import win.liyufan.im.HttpUtils;
import win.liyufan.im.Utility;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static io.moquette.BrokerConstants.PUSH_ANDROID_SERVER_ADDRESS;
import static io.moquette.BrokerConstants.PUSH_IOS_SERVER_ADDRESS;
public class PushServer {
private static final Logger LOG = LoggerFactory.getLogger(PushServer.class);
public interface PushMessageType {
int PUSH_MESSAGE_TYPE_NORMAL = 0;
int PUSH_MESSAGE_TYPE_VOIP_INVITE = 1;
int PUSH_MESSAGE_TYPE_VOIP_BYE = 2;
}
private static PushServer INSTANCE = new PushServer();
private ISessionsStore sessionsStore;
private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*5);
private String androidPushServerUrl;
private String iOSPushServerUrl;
private PushServer() {
}
public static PushServer getServer() {
return INSTANCE;
}
public void init(IConfig config, ISessionsStore sessionsStore) {
this.sessionsStore = sessionsStore;
this.androidPushServerUrl = config.getProperty(PUSH_ANDROID_SERVER_ADDRESS);
this.iOSPushServerUrl = config.getProperty(PUSH_IOS_SERVER_ADDRESS);
}
public void pushMessage(PushMessage pushMessage, String deviceId, String pushContent) {
LOG.info("try to delivery push diviceId = {}, pushContent", deviceId, pushContent);
executorService.execute(() ->{
try {
pushMessageInternel(pushMessage, deviceId, pushContent);
} catch (Exception e) {
e.printStackTrace();
Utility.printExecption(LOG, e);
}
});
}
private void pushMessageInternel(PushMessage pushMessage, String deviceId, String pushContent) {
if (pushMessage.pushMessageType == PushMessageType.PUSH_MESSAGE_TYPE_NORMAL && StringUtil.isNullOrEmpty(pushContent)) {
LOG.info("push content empty, deviceId {}", deviceId);
return;
}
MemorySessionStore.Session session = sessionsStore.getSession(deviceId);
int badge = session.getUnReceivedMsgs();
if (badge <= 0) {
badge = 1;
}
if (StringUtil.isNullOrEmpty(session.getDeviceToken())) {
LOG.warn("Device token is empty for device {}", deviceId);
return;
}
pushMessage.packageName = session.getAppName();
pushMessage.pushType = session.getPushType();
pushMessage.pushContent = pushContent;
pushMessage.deviceToken = session.getDeviceToken();
pushMessage.unReceivedMsg = badge;
if (session.getPlatform() == ProtoConstants.Platform.Platform_iOS || session.getPlatform() == ProtoConstants.Platform.Platform_Android) {
String url = androidPushServerUrl;
if (session.getPlatform() == ProtoConstants.Platform.Platform_iOS) {
url = iOSPushServerUrl;
pushMessage.voipDeviceToken = session.getVoipDeviceToken();
}
HttpUtils.httpJsonPost(url, new Gson().toJson(pushMessage, pushMessage.getClass()));
} else {
LOG.info("Not mobile platform {}", session.getPlatform());
}
}
}
@@ -0,0 +1,34 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package cn.wildfirechat.server;
import win.liyufan.im.Utility;
import java.util.Base64;
public class MechineCode {
public static void main(String[] args) throws Exception {
for (String arg: args
) {
System.out.println(arg);
}
if (args.length != 2) {
System.out.println("Usage: ip port");
System.exit(-1);
}
String mac = Utility.getMacAddress();
mac += "|";
mac += args[0];
mac += "|";
mac += args[1];
mac = Base64.getEncoder().encodeToString(mac.getBytes());
System.out.println(mac);
}
}
@@ -0,0 +1,17 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package cn.wildfirechat.server;
import java.io.IOException;
public class Server {
public static void main(String[] args) throws IOException {
io.moquette.server.Server.start(args);
}
}
@@ -0,0 +1,48 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package cn.wildfirechat.server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadPoolExecutorWrapper {
private static final Logger LOG = LoggerFactory.getLogger(ThreadPoolExecutorWrapper.class);
private final ScheduledExecutorService executor;
private final int count;
private final AtomicInteger runCounter;
private final String name;
public ThreadPoolExecutorWrapper(ScheduledExecutorService executor, int count, String name) {
this.executor = executor;
this.count = count;
this.runCounter = new AtomicInteger();
this.name = name;
}
public void execute(Runnable task) {
int startCount = runCounter.incrementAndGet();
LOG.info("Submit task and current taskcount {}", startCount);
final long startTime = System.currentTimeMillis();
executor.execute(() -> {
try {
task.run();
} finally {
int endCount = runCounter.decrementAndGet();
LOG.info("Finish task and current taskcount {} use time {}", endCount, System.currentTimeMillis()-startTime);
}
});
}
public void shutdown() {
executor.shutdown();
}
}
@@ -0,0 +1,97 @@
package com.xiaoleilu.loServer;
import com.xiaoleilu.hutool.log.Log;
import com.xiaoleilu.hutool.log.StaticLog;
import com.xiaoleilu.hutool.util.DateUtil;
import com.xiaoleilu.loServer.action.Action;
import com.xiaoleilu.loServer.action.ClassUtil;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.ActionHandler;
import com.xiaoleilu.loServer.handler.HttpFileServerHandler;
import io.moquette.spi.IMessagesStore;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* LoServer starter<br>
* 用于启动服务器的主对象<br>
* 使用LoServer.start()启动服务器<br>
* 服务的Action类和端口等设置在ServerSetting中设置
* @author Looly
*
*/
public class LoFileServer {
private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(LoFileServer.class);
private int port;
private IMessagesStore messagesStore;
private Channel channel;
public LoFileServer(int port, IMessagesStore messagesStore) {
this.port = port;
this.messagesStore = messagesStore;
}
/**
* 启动服务
* @throws InterruptedException
*/
public void start() throws InterruptedException {
long start = System.currentTimeMillis();
// Configure the server.
final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
final EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
final ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024) // 服务端可连接队列大小
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.SO_REUSEADDR, true)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_SNDBUF, 1024*1024*10)
.option(ChannelOption.SO_RCVBUF, 1024*1024*10)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new HttpRequestDecoder());
socketChannel.pipeline().addLast(new HttpResponseEncoder());
socketChannel.pipeline().addLast(new ChunkedWriteHandler());
socketChannel.pipeline().addLast(new HttpObjectAggregator(100 * 1024 * 1024));
socketChannel.pipeline().addLast(new HttpFileServerHandler());
}
});
channel = b.bind(port).sync().channel();
Logger.info("***** Welcome To LoServer on port [{}], startting spend {}ms *****", port, DateUtil.spendMs(start));
} finally {
}
}
public void shutdown() {
if (this.channel != null) {
this.channel.close();
try {
this.channel.closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
+148
View File
@@ -0,0 +1,148 @@
package com.xiaoleilu.loServer;
import com.xiaoleilu.hutool.util.DateUtil;
import com.xiaoleilu.loServer.action.Action;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.AdminActionHandler;
import com.xiaoleilu.loServer.handler.IMActionHandler;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import com.xiaoleilu.loServer.action.ClassUtil;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* LoServer starter<br>
* 用于启动服务器的主对象<br>
* 使用LoServer.start()启动服务器<br>
* 服务的Action类和端口等设置在ServerSetting中设置
* @author Looly
*
*/
public class LoServer {
private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(LoServer.class);
private int port;
private int adminPort;
private IMessagesStore messagesStore;
private ISessionsStore sessionsStore;
private Channel channel;
private Channel adminChannel;
public LoServer(int port, int adminPort, IMessagesStore messagesStore, ISessionsStore sessionsStore) {
this.port = port;
this.adminPort = adminPort;
this.messagesStore = messagesStore;
this.sessionsStore = sessionsStore;
}
/**
* 启动服务
* @throws InterruptedException
*/
public void start() throws InterruptedException {
long start = System.currentTimeMillis();
// Configure the server.
final EventLoopGroup bossGroup = new NioEventLoopGroup(2);
final EventLoopGroup workerGroup = new NioEventLoopGroup();
registerAllAction();
try {
final ServerBootstrap b = new ServerBootstrap();
final ServerBootstrap adminB = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 10240) // 服务端可连接队列大小
.childOption(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.SO_REUSEADDR, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_SNDBUF, 1024*64)
.childOption(ChannelOption.SO_RCVBUF, 1024*64)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new HttpRequestDecoder());
socketChannel.pipeline().addLast(new HttpResponseEncoder());
socketChannel.pipeline().addLast(new ChunkedWriteHandler());
socketChannel.pipeline().addLast(new HttpObjectAggregator(100 * 1024 * 1024));
socketChannel.pipeline().addLast(new IMActionHandler(messagesStore, sessionsStore));
}
});
channel = b.bind(port).sync().channel();
adminB.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 10240) // 服务端可连接队列大小
.childOption(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.SO_REUSEADDR, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_SNDBUF, 1024*64)
.childOption(ChannelOption.SO_RCVBUF, 1024*64)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new HttpRequestDecoder());
socketChannel.pipeline().addLast(new HttpResponseEncoder());
socketChannel.pipeline().addLast(new ChunkedWriteHandler());
socketChannel.pipeline().addLast(new HttpObjectAggregator(100 * 1024 * 1024));
socketChannel.pipeline().addLast(new AdminActionHandler(messagesStore, sessionsStore));
}
});
adminChannel = adminB.bind(adminPort).sync().channel();
Logger.info("***** Welcome To LoServer on port [{},{}], startting spend {}ms *****", port, adminPort, DateUtil.spendMs(start));
} finally {
}
}
public void shutdown() {
if (this.channel!= null) {
this.channel.close();
try {
this.channel.closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (this.adminChannel != null) {
this.adminChannel.close();
try {
this.adminChannel.closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void registerAllAction() {
try {
for (Class cls:ClassUtil.getAllAssignedClass(Action.class)
) {
if(cls.getAnnotation(Route.class) != null) {
ServerSetting.setAction((Class<? extends Action>)cls);
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,70 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer;
import win.liyufan.im.ErrorCode;
public class RestResult {
int code;
String msg;
Object result;
public static RestResult ok(Object object) {
return resultOf(ErrorCode.ERROR_CODE_SUCCESS, ErrorCode.ERROR_CODE_SUCCESS.getMsg(), object);
}
public static RestResult ok() {
return resultOf(ErrorCode.ERROR_CODE_SUCCESS, ErrorCode.ERROR_CODE_SUCCESS.getMsg(), null);
}
public static RestResult resultOf(ErrorCode errorCode) {
return resultOf(errorCode, errorCode.msg, null);
}
public static RestResult resultOf(ErrorCode errorCode, String msg) {
return resultOf(errorCode, msg, null);
}
public static RestResult resultOf(ErrorCode errorCode, String msg, Object object) {
RestResult result = new RestResult();
result.code = errorCode.code;
result.msg = msg;
result.result = object;
return result;
}
public void setErrorCode(ErrorCode errorCode) {
setCode(errorCode.code);
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}
@@ -0,0 +1,302 @@
package com.xiaoleilu.loServer;
import java.io.File;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.xiaoleilu.hutool.lang.Singleton;
import com.xiaoleilu.hutool.log.Log;
import com.xiaoleilu.hutool.log.StaticLog;
import com.xiaoleilu.hutool.util.FileUtil;
import com.xiaoleilu.hutool.util.StrUtil;
import com.xiaoleilu.loServer.action.Action;
import com.xiaoleilu.loServer.action.DefaultIndexAction;
import com.xiaoleilu.loServer.action.UnknownErrorAction;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.exception.ServerSettingException;
import com.xiaoleilu.loServer.filter.Filter;
import org.slf4j.LoggerFactory;
/**
* 全局设定文件
* @author xiaoleilu
*
*/
public class ServerSetting {
private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(ServerSetting.class);
//-------------------------------------------------------- Default value start
/** 默认的字符集编码 */
public final static String DEFAULT_CHARSET = "utf-8";
public final static String MAPPING_ALL = "/*";
public final static String MAPPING_ERROR = "/_error";
//-------------------------------------------------------- Default value end
/** 字符编码 */
private static String charset = DEFAULT_CHARSET;
/** 端口 */
private static int port = 8090;
/** 根目录 */
private static File root;
/** Filter映射表 */
private static Map<String, Filter> filterMap;
/** Action映射表 */
private static Map<String, Action> getActionMap;
private static Map<String, Action> postActionMap;
private static Map<String, Action> putActionMap;
private static Map<String, Action> deleteActionMap;
private static Map<String, Action> errorActionMap;
static{
filterMap = new ConcurrentHashMap<String, Filter>();
getActionMap = new ConcurrentHashMap<String, Action>();
postActionMap = new ConcurrentHashMap<String, Action>();
putActionMap = new ConcurrentHashMap<String, Action>();
deleteActionMap = new ConcurrentHashMap<String, Action>();
errorActionMap = new ConcurrentHashMap<String, Action>();
errorActionMap.put(StrUtil.SLASH, new DefaultIndexAction());
errorActionMap.put(MAPPING_ERROR, new UnknownErrorAction());
}
/**
* @return 获取编码
*/
public static String getCharset() {
return charset;
}
/**
* @return 字符集
*/
public static Charset charset() {
return Charset.forName(getCharset());
}
/**
* 设置编码
* @param charset 编码
*/
public static void setCharset(String charset) {
ServerSetting.charset = charset;
}
/**
* @return 监听端口
*/
public static int getPort() {
return port;
}
/**
* 设置监听端口
* @param port 端口
*/
public static void setPort(int port) {
ServerSetting.port = port;
}
//----------------------------------------------------------------------------------------------- Root start
/**
* @return 根目录
*/
public static File getRoot() {
return root;
}
/**
* @return 根目录
*/
public static boolean isRootAvailable() {
if(root != null && root.isDirectory() && root.isHidden() == false && root.canRead()){
return true;
}
return false;
}
/**
* @return 根目录
*/
public static String getRootPath() {
return FileUtil.getAbsolutePath(root);
}
/**
* 根目录
* @param root 根目录绝对路径
*/
public static void setRoot(String root) {
ServerSetting.root = FileUtil.mkdir(root);
Logger.debug("Set root to [{}]", ServerSetting.root.getAbsolutePath());
}
/**
* 根目录
* @param root 根目录绝对路径
*/
public static void setRoot(File root) {
if(root.exists() == false){
root.mkdirs();
}else if(root.isDirectory() == false){
throw new ServerSettingException(StrUtil.format("{} is not a directory!", root.getPath()));
}
ServerSetting.root = root;
}
//----------------------------------------------------------------------------------------------- Root end
//----------------------------------------------------------------------------------------------- Filter start
/**
* @return 获取FilterMap
*/
public static Map<String, Filter> getFilterMap() {
return filterMap;
}
/**
* 获得路径对应的Filter
* @param path 路径,为空时将获得 根目录对应的Action
* @return Filter
*/
public static Filter getFilter(String path){
if(StrUtil.isBlank(path)){
path = StrUtil.SLASH;
}
return getFilterMap().get(path.trim());
}
/**
* 设置FilterMap
* @param filterMap FilterMap
*/
public static void setFilterMap(Map<String, Filter> filterMap) {
ServerSetting.filterMap = filterMap;
}
/**
* 设置Filter类,已有的Filter类将被覆盖
* @param path 拦截路径(必须以"/"开头)
* @param filter Action类
*/
public static void setFilter(String path, Filter filter) {
if(StrUtil.isBlank(path)){
path = StrUtil.SLASH;
}
if(null == filter) {
Logger.warn("Added blank action, pass it.");
return;
}
//所有路径必须以 "/" 开头,如果没有则补全之
if(false == path.startsWith(StrUtil.SLASH)) {
path = StrUtil.SLASH + path;
}
ServerSetting.filterMap.put(path, filter);
}
/**
* 设置Filter类,已有的Filter类将被覆盖
* @param path 拦截路径(必须以"/"开头)
* @param filterClass Filter类
*/
public static void setFilter(String path, Class<? extends Filter> filterClass) {
setFilter(path, (Filter)Singleton.get(filterClass));
}
//----------------------------------------------------------------------------------------------- Filter end
//----------------------------------------------------------------------------------------------- Action start
/**
* @return 获取ActionMap
*/
public static Map<String, Action> getActionMap(String method) {
if (method.equals("GET")) {
return getActionMap;
} else if(method.equals("POST")) {
return postActionMap;
} else if(method.equals("PUT")) {
return putActionMap;
} else if(method.equals("DELETE")) {
return deleteActionMap;
}
return getActionMap;
}
public static Action getErrorAction(String path) {
return errorActionMap.get(path);
}
/**
* 获得路径对应的Action
* @param path 路径,为空时将获得 根目录对应的Action
* @return Action
*/
public static Action getAction(String path, String method){
if(StrUtil.isBlank(path)){
path = StrUtil.SLASH;
}
return getActionMap(method).get(path.trim());
}
/**
* 设置Action类,已有的Action类将被覆盖
* @param path 拦截路径(必须以"/"开头)
* @param action Action类
*/
public static void setAction(String path, Action action) {
if(StrUtil.isBlank(path)){
path = StrUtil.SLASH;
}
if(null == action) {
Logger.warn("Added blank action, pass it.");
return;
}
//所有路径必须以 "/" 开头,如果没有则补全之
if(false == path.startsWith(StrUtil.SLASH)) {
path = StrUtil.SLASH + path;
}
String method = "GET";
HttpMethod methodAnnotation = action.getClass().getAnnotation(HttpMethod.class);
if (methodAnnotation != null) {
method = methodAnnotation.value();
}
ServerSetting.getActionMap(method).put(path, action);
}
/**
* 增加Action类,已有的Action类将被覆盖<br>
* 所有Action都是以单例模式存在的!
* @param path 拦截路径(必须以"/"开头)
* @param actionClass Action类
*/
public static void setAction(String path, Class<? extends Action> actionClass) {
setAction(path, (Action)Singleton.get(actionClass));
}
/**
* 增加Action类,已有的Action类将被覆盖<br>
* 自动读取Route的注解来获得Path路径
* @param action 带注解的Action对象
*/
public static void setAction(Action action) {
final Route route = action.getClass().getAnnotation(Route.class);
if(route != null){
final String path = route.value();
if(StrUtil.isNotBlank(path)){
setAction(path, action);
return;
}
}
throw new ServerSettingException("Can not find Route annotation,please add annotation to Action class!");
}
/**
* 增加Action类,已有的Action类将被覆盖<br>
* 所有Action都是以单例模式存在的!
* @param actionClass 带注解的Action类
*/
public static void setAction(Class<? extends Action> actionClass) {
setAction((Action)Singleton.get(actionClass));
}
//----------------------------------------------------------------------------------------------- Action start
}
@@ -0,0 +1,105 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.action.admin.GetIMTokenAction;
import com.xiaoleilu.loServer.annotation.RequireAuthentication;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.impl.Utils;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import win.liyufan.im.DBUtil;
import win.liyufan.im.ErrorCode;
import static win.liyufan.im.ErrorCode.ERROR_CODE_SUCCESS;
/**
* 请求处理接口<br>
* 当用户请求某个Path,则调用相应Action的doAction方法
* @author Looly
*
*/
abstract public class Action {
public static IMessagesStore messagesStore = null;
public static ISessionsStore sessionsStore = null;
public ChannelHandlerContext ctx;
protected boolean isSync = true;
public ErrorCode preAction(Request request, Response response) {
if (getClass().getAnnotation(RequireAuthentication.class) != null) {
//do authentication
}
return ERROR_CODE_SUCCESS;
}
public boolean doAction(Request request, Response response) {
ErrorCode errorCode = preAction(request, response);
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
//事务逻辑有缺陷,先注释掉
// if (isTransactionAction() && !(this instanceof IMAction)) {
// DBUtil.beginTransaction();
// try {
// action(request, response);
// DBUtil.commit();
// } catch (Exception e) {
// e.printStackTrace();
// DBUtil.roolback();
// throw e;
// }
// } else {
action(request, response);
// }
afterAction(request, response);
} else {
response.setStatus(HttpResponseStatus.OK);
if (errorCode == null) {
errorCode = ErrorCode.ERROR_CODE_SUCCESS;
}
RestResult result = RestResult.resultOf(errorCode, errorCode.getMsg(), RestResult.resultOf(errorCode));
response.setContent(new Gson().toJson(result));
response.send();
}
if(this instanceof IMAction || this instanceof RouteAction || this instanceof GetIMTokenAction) {
isSync = false;
}
return isSync;
}
public boolean isTransactionAction() {
return false;
}
abstract public void action(Request request, Response response);
public void afterAction(Request request, Response response) {
}
protected <T> T getRequestBody(HttpRequest request, Class<T> cls) {
if (request instanceof FullHttpRequest) {
FullHttpRequest fullHttpRequest = (FullHttpRequest) request;
byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content());
String content = new String(bytes);
Gson gson = new Gson();
T t = gson.fromJson(content, cls);
return t;
}
return null;
}
}
@@ -0,0 +1,234 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class ClassUtil {
/**
* 获取同一路径下所有子类或接口实现类
*
* @param cls
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public static List<Class<?>> getAllAssignedClass(Class<?> cls) throws IOException,
ClassNotFoundException {
List<Class<?>> classes = new ArrayList<Class<?>>();
for (Class<?> c : getClasses(cls)) {
if (cls.isAssignableFrom(c) && !cls.equals(c)) {
classes.add(c);
}
}
return classes;
}
/**
* 取得当前类路径下的所有类
*
* @param cls
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public static List<Class<?>> getClasses(Class<?> cls) throws IOException,
ClassNotFoundException {
String pk = cls.getPackage().getName();
// String path = pk.replace('.', '/');
// ClassLoader classloader = Thread.currentThread().getContextClassLoader();
// URL url = classloader.getResource(path);
// String type = url.getProtocol();
//
// System.out.println("the type is " + type);
// System.out.println(url.getPath());
//
// return getClasses(new File(url.getFile()), pk);
List<String> classNames = getClassName(pk, true);
List<Class<?>> classes = new ArrayList<>();
for (String className :
classNames) {
if (className.endsWith(".class")) {
className = className.substring(0, className.length() - 6);
className = className.substring(className.lastIndexOf("/")+1, className.length());
}
classes.add(Class.forName(className));
}
return classes;
}
/**
* 迭代查找类
*
* @param dir
* @param pk
* @return
* @throws ClassNotFoundException
*/
private static List<Class<?>> getClasses(File dir, String pk) throws ClassNotFoundException {
System.out.println(dir.getAbsolutePath());
List<Class<?>> classes = new ArrayList<Class<?>>();
if (!dir.exists()) {
return classes;
}
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
classes.addAll(getClasses(f, pk + "." + f.getName()));
}
String name = f.getName();
if (name.endsWith(".class")) {
classes.add(Class.forName(pk + "." + name.substring(0, name.length() - 6)));
}
}
return classes;
}
/**
* 获取某包下所有类
* @param packageName 包名
* @param childPackage 是否遍历子包
* @return 类的完整名称
*/
public static List<String> getClassName(String packageName, boolean childPackage) {
List<String> fileNames = null;
ClassLoader loader = Thread.currentThread().getContextClassLoader();
String packagePath = packageName.replace(".", "/");
URL url = loader.getResource(packagePath);
if (url != null) {
String type = url.getProtocol();
if (type.equals("file")) {
fileNames = getClassNameByFile(url.getPath(), null, childPackage);
} else if (type.equals("jar")) {
fileNames = getClassNameByJar(url.getPath(), childPackage);
}
} else {
fileNames = getClassNameByJars(((URLClassLoader) loader).getURLs(), packagePath, childPackage);
}
return fileNames;
}
/**
* 从项目文件获取某包下所有类
* @param filePath 文件路径
* @param className 类名集合
* @param childPackage 是否遍历子包
* @return 类的完整名称
*/
private static List<String> getClassNameByFile(String filePath, List<String> className, boolean childPackage) {
List<String> myClassName = new ArrayList<String>();
File file = new File(filePath);
File[] childFiles = file.listFiles();
for (File childFile : childFiles) {
if (childFile.isDirectory()) {
if (childPackage) {
myClassName.addAll(getClassNameByFile(childFile.getPath(), myClassName, childPackage));
}
} else {
String childFilePath = childFile.getPath();
if (childFilePath.endsWith(".class")) {
if (childFilePath.indexOf("\\classes") >= 0 || childFilePath.indexOf("/classes") >= 0) {
int start = childFilePath.indexOf("\\classes");
if (start == -1) {
start = childFilePath.indexOf("/classes");
}
childFilePath = childFilePath.substring(start + 9, childFilePath.lastIndexOf("."));
}
childFilePath = childFilePath.replace("\\", ".");
childFilePath = childFilePath.replace("/", ".");
myClassName.add(childFilePath);
}
}
}
return myClassName;
}
/**
* 从jar获取某包下所有类
* @param jarPath jar文件路径
* @param childPackage 是否遍历子包
* @return 类的完整名称
*/
private static List<String> getClassNameByJar(String jarPath, boolean childPackage) {
List<String> myClassName = new ArrayList<String>();
String[] jarInfo = jarPath.split("!");
String jarFilePath = jarInfo[0].substring(jarInfo[0].indexOf("/"));
String packagePath = jarInfo[1].substring(1);
try {
JarFile jarFile = new JarFile(jarFilePath);
Enumeration<JarEntry> entrys = jarFile.entries();
while (entrys.hasMoreElements()) {
JarEntry jarEntry = entrys.nextElement();
String entryName = jarEntry.getName();
if (entryName.endsWith(".class")) {
if (childPackage) {
if (entryName.startsWith(packagePath)) {
entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
myClassName.add(entryName);
}
} else {
int index = entryName.lastIndexOf("/");
String myPackagePath;
if (index != -1) {
myPackagePath = entryName.substring(0, index);
} else {
myPackagePath = entryName;
}
if (myPackagePath.equals(packagePath)) {
entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
myClassName.add(entryName);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return myClassName;
}
/**
* 从所有jar中搜索该包,并获取该包下所有类
* @param urls URL集合
* @param packagePath 包路径
* @param childPackage 是否遍历子包
* @return 类的完整名称
*/
private static List<String> getClassNameByJars(URL[] urls, String packagePath, boolean childPackage) {
List<String> myClassName = new ArrayList<String>();
if (urls != null) {
for (int i = 0; i < urls.length; i++) {
URL url = urls[i];
String urlPath = url.getPath();
// 不必搜索classes文件夹
if (urlPath.endsWith("classes/")) {
continue;
}
String jarPath = urlPath + "!/" + packagePath;
myClassName.addAll(getClassNameByJar(jarPath, childPackage));
}
}
return myClassName;
}
}
@@ -0,0 +1,18 @@
package com.xiaoleilu.loServer.action;
import com.hazelcast.core.HazelcastInstance;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.spi.IMessagesStore;
/**
* 默认的主页Action,当访问主页且没有定义主页Action时,调用此Action
* @author Looly
*
*/
public class DefaultIndexAction extends Action{
@Override
public void action(Request request, Response response) {
response.setContent("Welcome to LoServer.");
}
}
@@ -0,0 +1,117 @@
package com.xiaoleilu.loServer.action;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.regex.Pattern;
import com.hazelcast.core.HazelcastInstance;
import com.xiaoleilu.hutool.log.Log;
import com.xiaoleilu.hutool.log.StaticLog;
import com.xiaoleilu.hutool.util.DateUtil;
import com.xiaoleilu.hutool.util.FileUtil;
import com.xiaoleilu.hutool.util.ReUtil;
import com.xiaoleilu.hutool.util.StrUtil;
import com.xiaoleilu.loServer.ServerSetting;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.spi.IMessagesStore;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.slf4j.LoggerFactory;
/**
* 默认的主页Action,当访问主页且没有定义主页Action时,调用此Action
*
* @author Looly
*
*/
public class FileAction extends Action {
private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(FileAction.class);
@Override
public void action(Request request, Response response) {
if (false == Request.METHOD_GET.equalsIgnoreCase(request.getMethod())) {
response.sendError(HttpResponseStatus.METHOD_NOT_ALLOWED, "Please use GET method to request file!");
return;
}
if(ServerSetting.isRootAvailable() == false){
response.sendError(HttpResponseStatus.NOT_FOUND, "404 Root dir not avaliable!");
return;
}
final File file = getFileByPath(request.getPath());
Logger.debug("Client [{}] get file [{}]", request.getIp(), file.getPath());
// 隐藏文件,跳过
if (file.isHidden() || !file.exists()) {
response.sendError(HttpResponseStatus.NOT_FOUND, "404 File not found!");
return;
}
// 非文件,跳过
if (false == file.isFile()) {
response.sendError(HttpResponseStatus.FORBIDDEN, "403 Forbidden!");
return;
}
// Cache Validation
String ifModifiedSince = request.getHeader(HttpHeaderNames.IF_MODIFIED_SINCE.toString());
if (StrUtil.isNotBlank(ifModifiedSince)) {
Date ifModifiedSinceDate = null;
try {
ifModifiedSinceDate = DateUtil.parse(ifModifiedSince, HTTP_DATE_FORMATER);
} catch (Exception e) {
Logger.warn("If-Modified-Since header parse error: {}", e.getMessage());
}
if(ifModifiedSinceDate != null) {
// 只对比到秒一级别
long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000;
long fileLastModifiedSeconds = file.lastModified() / 1000;
if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) {
Logger.debug("File {} not modified.", file.getPath());
response.sendNotModified();
return;
}
}
}
response.setContent(file);
}
private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");
private static final SimpleDateFormat HTTP_DATE_FORMATER = new SimpleDateFormat(DateUtil.HTTP_DATETIME_PATTERN, Locale.US);
/**
* 通过URL中的path获得文件的绝对路径
*
* @param httpPath Http请求的Path
* @return 文件绝对路径
*/
public static File getFileByPath(String httpPath) {
// Decode the path.
try {
httpPath = URLDecoder.decode(httpPath, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new Error(e);
}
if (httpPath.isEmpty() || httpPath.charAt(0) != '/') {
return null;
}
// 路径安全检查
if (httpPath.contains("/.") || httpPath.contains("./") || httpPath.charAt(0) == '.' || httpPath.charAt(httpPath.length() - 1) == '.' || ReUtil.isMatch(INSECURE_URI, httpPath)) {
return null;
}
// 转换为绝对路径
return FileUtil.file(ServerSetting.getRoot(), httpPath);
}
}
@@ -0,0 +1,124 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action;
import cn.wildfirechat.proto.WFCMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.MemorySessionStore;
import io.moquette.persistence.TargetEntry;
import io.moquette.spi.impl.Utils;
import io.moquette.spi.impl.security.AES;
import io.moquette.spi.security.Tokenor;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import win.liyufan.im.ErrorCode;
import java.util.Base64;
import java.util.concurrent.Executor;
@Route("/im")
@HttpMethod("POST")
public class IMAction extends Action {
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
FullHttpRequest fullHttpRequest = (FullHttpRequest)request.getNettyRequest();
byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content());
String str = new String(bytes);
try {
bytes = Base64.getDecoder().decode(str);
} catch (IllegalArgumentException e) {
sendResponse(response, ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH, null);
return;
}
String cid = fullHttpRequest.headers().get("cid");
byte[] cbytes = Base64.getDecoder().decode(cid);
cbytes = AES.AESDecrypt(cbytes, null, true);
if (cbytes == null) {
sendResponse(response, ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH, null);
return;
}
cid = new String(cbytes);
MemorySessionStore.Session session = sessionsStore.getSession(cid);
if (session != null) {
bytes = AES.AESDecrypt(bytes, session.getSecret(), true);
} else {
sendResponse(response, ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH, null);
return;
}
if (bytes == null) {
sendResponse(response, ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH, null);
return;
}
try {
WFCMessage.IMHttpWrapper wrapper = WFCMessage.IMHttpWrapper.parseFrom(bytes);
String token = wrapper.getToken();
String userId = Tokenor.getUserId(token.getBytes());
if (userId == null) {
sendResponse(response, ErrorCode.ERROR_CODE_TOKEN_ERROR, null);
} else {
RPCCenter.getInstance().sendRequest(userId, wrapper.getClientId(), wrapper.getRequest(), wrapper.getData().toByteArray(), userId, TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
sendResponse(response, null, result);
}
@Override
public void onError(ErrorCode errorCode) {
sendResponse(response, errorCode, null);
}
@Override
public void onTimeout() {
sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null);
}
@Override
public Executor getResponseExecutor() {
return command -> {
ctx.executor().execute(command);
};
}
});
}
} catch (InvalidProtocolBufferException e) {
sendResponse(response, ErrorCode.ERROR_CODE_INVALID_DATA, null);
}
}
}
private void sendResponse(Response response, ErrorCode errorCode, byte[] contents) {
response.setStatus(HttpResponseStatus.OK);
if(contents == null) {
ByteBuf ackPayload = Unpooled.buffer();
ackPayload.ensureWritable(1).writeByte(errorCode.getCode());
response.setContent(ackPayload);
} else {
response.setContent(contents);
}
response.send();
}
}
@@ -0,0 +1,33 @@
package com.xiaoleilu.loServer.action;
import com.hazelcast.core.HazelcastInstance;
import com.xiaoleilu.hutool.log.Log;
import com.xiaoleilu.hutool.log.StaticLog;
import com.xiaoleilu.hutool.util.StrUtil;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.spi.IMessagesStore;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.slf4j.LoggerFactory;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* 错误堆栈Action类
* @author Looly
*
*/
public class NotFoundAction extends Action{
private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(NotFoundAction.class);
public final static String ERROR_PARAM_NAME = "_e";
private final static String TEMPLATE_ERROR = "<!DOCTYPE html><html><head><title>LoServer - Error report</title><style>h1,h3 {color:white; background-color: gray;}</style></head><body><h1>HTTP Status {} - {}</h1><hr size=\"1\" noshade=\"noshade\" /><p>{}</p><hr size=\"1\" noshade=\"noshade\" /><h3>LoServer</h3></body></html>";
@Override
public void action(Request request, Response response) {
response.sendError(HttpResponseStatus.NOT_FOUND, "404 Not found!");
}
}
@@ -0,0 +1,154 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action;
import cn.wildfirechat.proto.WFCMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.persistence.MemorySessionStore;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.moquette.spi.impl.Utils;
import io.moquette.spi.impl.security.AES;
import io.moquette.spi.security.Tokenor;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import win.liyufan.im.ErrorCode;
import java.util.Base64;
import java.util.concurrent.Executor;
@Route("/route")
@HttpMethod("POST")
public class RouteAction extends Action {
private static final Logger LOG = LoggerFactory.getLogger(RouteAction.class);
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
response.setContentType("application/octet-stream");
FullHttpRequest fullHttpRequest = (FullHttpRequest)request.getNettyRequest();
boolean b = false;
if ("web".equalsIgnoreCase(fullHttpRequest.headers().get("p"))) {
b = true;
}
final boolean base64Response = b;
byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content());
String str = new String(bytes);
try {
bytes = Base64.getDecoder().decode(str);
} catch (IllegalArgumentException e) {
sendResponse(response, ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH, null, base64Response);
return;
}
String cid = fullHttpRequest.headers().get("cid");
byte[] cbytes = Base64.getDecoder().decode(cid);
cbytes = AES.AESDecrypt(cbytes, null, true);
if (cbytes == null) {
sendResponse(response, ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH, null, base64Response);
return;
}
cid = new String(cbytes);
String uid = fullHttpRequest.headers().get("uid");
byte[] ubytes = Base64.getDecoder().decode(uid);
ubytes = AES.AESDecrypt(ubytes, null, true);
if (ubytes == null) {
sendResponse(response, ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH, null, base64Response);
return;
}
uid = new String(ubytes);
MemorySessionStore.Session session = sessionsStore.sessionForClientAndUser(uid, cid);
if (session == null) {
ErrorCode errorCode = sessionsStore.createNewSession(uid, cid, true);
if (errorCode != ErrorCode.ERROR_CODE_SUCCESS) {
sendResponse(response, errorCode, null, base64Response);
return;
}
session = sessionsStore.sessionForClientAndUser(uid, cid);
}
if (session != null) {
bytes = AES.AESDecrypt(bytes, session.getSecret(), true);
} else {
sendResponse(response, ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH, null, base64Response);
return;
}
if (bytes == null) {
sendResponse(response, ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH, null, base64Response);
return;
}
try {
WFCMessage.IMHttpWrapper wrapper = WFCMessage.IMHttpWrapper.parseFrom(bytes);
String token = wrapper.getToken();
String userId = Tokenor.getUserId(token.getBytes());
LOG.info("RouteAction token={}, userId={}", token, userId);
if (userId == null) {
sendResponse(response, ErrorCode.ERROR_CODE_TOKEN_ERROR, null, base64Response);
} else {
RPCCenter.getInstance().sendRequest(userId, wrapper.getClientId(), wrapper.getRequest(), wrapper.getData().toByteArray(), userId, TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
sendResponse(response, null, result, base64Response);
}
@Override
public void onError(ErrorCode errorCode) {
sendResponse(response, errorCode, null, base64Response);
}
@Override
public void onTimeout() {
sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null, base64Response);
}
@Override
public Executor getResponseExecutor() {
return command -> {
ctx.executor().execute(command);
};
}
});
}
} catch (InvalidProtocolBufferException e) {
sendResponse(response, ErrorCode.ERROR_CODE_INVALID_DATA, null, false);
}
}
}
private void sendResponse(Response response, ErrorCode errorCode, byte[] contents, boolean base64Response) {
response.setStatus(HttpResponseStatus.OK);
if(contents == null) {
ByteBuf ackPayload = Unpooled.buffer();
ackPayload.ensureWritable(1).writeByte(errorCode.getCode());
response.setContent(ackPayload);
} else {
response.setContent(contents);
}
response.send();
}
}
@@ -0,0 +1,33 @@
package com.xiaoleilu.loServer.action;
import java.io.PrintWriter;
import java.io.StringWriter;
import com.hazelcast.core.HazelcastInstance;
import com.xiaoleilu.hutool.log.Log;
import com.xiaoleilu.hutool.log.StaticLog;
import com.xiaoleilu.hutool.util.StrUtil;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.spi.IMessagesStore;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.slf4j.LoggerFactory;
/**
* 错误堆栈Action类
* @author Looly
*
*/
public class UnknownErrorAction extends Action{
private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(UnknownErrorAction.class);
public final static String ERROR_PARAM_NAME = "_e";
private final static String TEMPLATE_ERROR = "<!DOCTYPE html><html><head><title>LoServer - Error report</title><style>h1,h3 {color:white; background-color: gray;}</style></head><body><h1>HTTP Status {} - {}</h1><hr size=\"1\" noshade=\"noshade\" /><p>{}</p><hr size=\"1\" noshade=\"noshade\" /><h3>LoServer</h3></body></html>";
@Override
public void action(Request request, Response response) {
response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
}
}
@@ -0,0 +1,344 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action;
import com.hazelcast.util.StringUtil;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.RequireAuthentication;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.*;
import io.moquette.server.config.MediaServerConfig;
import io.moquette.spi.impl.security.AES;
import io.moquette.spi.security.DES;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.RandomAccessFile;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import static com.xiaoleilu.loServer.handler.HttpResponseHelper.getFileExt;
@Route("/fs")
@HttpMethod("POST")
@RequireAuthentication
public class UploadFileAction extends Action {
private static final String KEY = "imfile";
private static final Logger logger = LoggerFactory.getLogger(UploadFileAction.class);
private static final HttpDataFactory factory = new DefaultHttpDataFactory(false);
public static class InvalidateTokenExecption extends Exception {
}
public static String getToken(int type) {
String signKey = KEY + "|" + (System.currentTimeMillis()) + "|" + type;
try {
return DES.encryptDES(signKey);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static int validateToken(String token) throws InvalidateTokenExecption {
try {
String signKey = DES.decryptDES(token);
String[] parts = signKey.split("\\|");
if(parts.length == 3) {
if(parts[0].equals(KEY)) {
long timestamp = Long.parseLong(parts[1]);
if(Math.abs(System.currentTimeMillis() - timestamp) < 2 * 60 * 60 * 1000) {
return Integer.parseInt(parts[2]);
}
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
throw new InvalidateTokenExecption();
}
@Override
public void action(Request r, Response response) {
if (r.getNettyRequest() instanceof FullHttpRequest) {
FullHttpRequest request = (FullHttpRequest) r.getNettyRequest();
String requestId = UUID.randomUUID().toString().replace("-", "");
logger.info("HttpFileServerHandler received a request: method=" + request.getMethod() + ", uri=" + request.getUri() + ", requestId=" + requestId);
if (!request.getDecoderResult().isSuccess()) {
logger.warn("http decode failed!");
response.setStatus(HttpResponseStatus.BAD_REQUEST);
response.setContent("http decode failed");
return;
}
multipartUpload(request, requestId, response);
}
}
/**
* multipart上传
*/
private void multipartUpload(FullHttpRequest request, String requestId, Response response) {
HttpPostRequestDecoder decoder = null;
try {
decoder = new HttpPostRequestDecoder(factory, request);
} catch (HttpPostRequestDecoder.ErrorDataDecoderException e1) {
logger.error("Failed to decode file data!", e1);
response.setStatus(HttpResponseStatus.BAD_REQUEST);
response.setContent("Failed to decode file data!");
return;
}
if (decoder != null) {
if (request instanceof HttpContent) {
HttpContent chunk = (HttpContent) request;
try {
decoder.offer(chunk);
} catch (HttpPostRequestDecoder.ErrorDataDecoderException e1) {
logger.warn("BAD_REQUEST, Failed to decode file data");
response.setStatus(HttpResponseStatus.BAD_REQUEST);
response.setContent("Failed to decode file data!");
return;
}
long fileTotalSize = 0;
if (request.headers().contains("X-File-Total-Size")) {
try {
fileTotalSize = Integer.parseInt(request.headers().get("X-File-Total-Size"));
} catch (Exception e) {
logger.warn("invalid X-File-Total-Size value!");
}
}
readHttpDataChunkByChunk(response, decoder, requestId, HttpHeaders.isKeepAlive(request));
if (chunk instanceof LastHttpContent) {
}
} else {
logger.warn("BAD_REQUEST, Not a http request");
response.setStatus(HttpResponseStatus.BAD_REQUEST);
response.setContent("Not a http request");
}
}
}
/**
* readHttpDataChunkByChunk
*/
private void readHttpDataChunkByChunk(Response response, HttpPostRequestDecoder decoder, String requestId, boolean isKeepAlive) {
try {
int[] bucket = new int[1];
bucket[0] = -1;
while (decoder.hasNext()) {
InterfaceHttpData data = decoder.next();
if (data != null) {
try {
if(!writeFileUploadData(data, response, requestId, isKeepAlive, bucket)) {
break;
}
} finally {
data.release();
}
}
}
} catch (Exception e) {
logger.info("chunk end");
}
}
/**
* writeFileUploadData
*/
private boolean writeFileUploadData(InterfaceHttpData data, Response response, String requestId, boolean isKeepAlive, int[] bucket) {
try {
if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
FileUpload fileUpload = (FileUpload) data;
String remoteFileName = fileUpload.getFilename();
long remoteFileSize = fileUpload.length();
if(bucket[0] == -1) {
logger.warn("Not authenticated!");
response.setStatus(HttpResponseStatus.BAD_REQUEST);
response.setContent("Not authenticated!");
return false;
}
if (StringUtil.isNullOrEmpty(remoteFileName)) {
logger.warn("remoteFileName is empty!");
response.setStatus(HttpResponseStatus.BAD_REQUEST);
response.setContent("file name is empty");
return false;
}
if (StringUtil.isNullOrEmpty(requestId)) {
logger.warn("requestId is empty!");
response.setStatus(HttpResponseStatus.BAD_REQUEST);
response.setContent("requestId is empty!");
return false;
}
if (remoteFileSize > 50 * 1024 * 1024) {
logger.warn("file over limite!(" + remoteFileSize + ")");
response.setStatus(HttpResponseStatus.BAD_REQUEST);
response.setContent("file over limite!");
return false;
}
String remoteFileExt = "";
if (remoteFileName.lastIndexOf(".") == -1) {
remoteFileExt = "octetstream";
remoteFileName = remoteFileName + "." + remoteFileExt;
} else {
remoteFileExt = getFileExt(remoteFileName);
}
if (StringUtil.isNullOrEmpty(remoteFileExt) || remoteFileExt.equals("ing")) {
logger.warn("Invalid file extention name");
response.setStatus(HttpResponseStatus.BAD_REQUEST);
response.setContent("Invalid file extention name");
return false;
}
int remoteFileTotalSize = (int) remoteFileSize;
ByteBuf byteBuf = null;
int savedThunkSize = 0; // 分片接收保存的大小
int offset = 0; // 断点续传开始位置
Date nowTime=new Date();
SimpleDateFormat time=new SimpleDateFormat("yyyy/MM/dd/HH");
String datePath = time.format(nowTime);
datePath = "fs/" + bucket[0] + "/" + datePath; //add bucket
String dir = "./" + MediaServerConfig.FILE_STROAGE_ROOT + "/" + datePath;
File dirFile = new File(dir);
boolean bFile = dirFile.exists();
if(!bFile) {
bFile = dirFile.mkdirs();
if (!bFile) {
response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
response.setContent("服务器错误:无法创建文件");
return false;
}
}
String filePath = dir + "/" + requestId;
logger.info("the file path is " + filePath);
File tmpFile = new File(filePath);
logger.info("before write the file");
boolean isError = false;
while (true) {
byte[] thunkData;
try {
byteBuf = fileUpload.getChunk(128 * 1024);
int readableBytesSize = byteBuf.readableBytes();
thunkData = new byte[readableBytesSize];
byteBuf.readBytes(thunkData);
put(tmpFile, offset, thunkData);
savedThunkSize += readableBytesSize;
offset += readableBytesSize;
if (savedThunkSize >= remoteFileSize) {
byteBuf.release();
fileUpload.release();
response.setStatus(HttpResponseStatus.OK);
String relativePath = datePath + "/" + requestId;
response.setContent("{\"key\":\"" + relativePath + "\"}");
break;
}
} catch (Exception e) {
logger.error("save thunckData error!", e);
if (fileUpload != null)
fileUpload.release();
if (byteBuf != null)
byteBuf.release();
response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
response.setContent("服务器错误:" + e.getMessage());
isError = true;
return false;
} finally {
thunkData = null;
if (isError) {
tmpFile.delete();
}
}
}
} else if(data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
Attribute attribute = (Attribute)data;
if(attribute.getName().equals("token")) {
String token = attribute.getValue();
try {
bucket[0] = validateToken(token);
} catch (InvalidateTokenExecption e) {
logger.error("无效的token!", e);
response.setStatus(HttpResponseStatus.BAD_REQUEST);
response.setContent("无效的token:" + e.getMessage());
return false;
}
}
}
} catch (Exception e) {
logger.error("writeHttpData error!", e);
response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
response.setContent("服务器错误:" + e.getMessage());
return false;
}
return true;
}
public static void put(File file, long pos, byte[] data) throws Exception {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(file, "rwd");
raf.seek(pos);
raf.write(data);
} finally {
try {
if (raf != null)
raf.close();
} catch (Exception e) {
logger.warn("release error!", e);
}
}
}
}
@@ -0,0 +1,48 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputUserLogin;
import com.xiaoleilu.loServer.pojos.OutputLoginData;
import io.moquette.spi.impl.Utils;
import io.moquette.spi.impl.security.TokenAuthenticator;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import win.liyufan.im.ErrorCode;
import win.liyufan.im.GitRepositoryState;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Route("/api/version")
@HttpMethod("GET")
public class VersionAction extends Action {
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
response.setStatus(HttpResponseStatus.OK);
try {
response.setContent(new Gson().toJson(GitRepositoryState.getGitRepositoryState()));
} catch (IOException e) {
e.printStackTrace();
response.setContent("No version info!");
}
}
}
}
@@ -0,0 +1,73 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.action.Action;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputCreateUser;
import io.moquette.persistence.RPCCenter;
import io.moquette.spi.impl.Utils;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import org.apache.commons.codec.digest.DigestUtils;
import win.liyufan.im.ErrorCode;
import win.liyufan.im.RateLimiter;
abstract public class AdminAction extends Action {
private static String SECRET_KEY = "123456";
private final RateLimiter mLimitCounter = new RateLimiter(10, 500);
public static void setSecretKey(String secretKey) {
SECRET_KEY = secretKey;
}
@Override
public ErrorCode preAction(Request request, Response response) {
if (!mLimitCounter.isGranted("admin")) {
return ErrorCode.ERROR_CODE_OVER_FREQUENCY;
}
String nonce = request.getHeader("nonce");
String timestamp = request.getHeader("timestamp");
String sign = request.getHeader("sign");
if (StringUtil.isNullOrEmpty(nonce) || StringUtil.isNullOrEmpty(timestamp) || StringUtil.isNullOrEmpty(sign)) {
return ErrorCode.INVALID_PARAMETER;
}
Long ts;
try {
ts = Long.parseLong(timestamp);
} catch (NumberFormatException e) {
e.printStackTrace();
return ErrorCode.INVALID_PARAMETER;
}
if (System.currentTimeMillis() - ts > 2 * 60 * 60 * 1000) {
// return ErrorCode.ERROR_CODE_SIGN_EXPIRED;
}
String str = nonce + "|" + SECRET_KEY + "|" + timestamp;
String localSign = DigestUtils.sha1Hex(str);
return localSign.equals(sign) ? ErrorCode.ERROR_CODE_SUCCESS : ErrorCode.ERROR_CODE_AUTH_FAILURE;
}
protected void sendResponse(Response response, ErrorCode errorCode, Object data) {
response.setStatus(HttpResponseStatus.OK);
if (errorCode == null) {
errorCode = ErrorCode.ERROR_CODE_SUCCESS;
}
RestResult result = RestResult.resultOf(errorCode, errorCode.getMsg(), data);
response.setContent(new Gson().toJson(result));
response.send();
}
}
@@ -0,0 +1,58 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputOutputUserBlockStatus;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.ErrorCode;
@Route("admin/user/status")
@HttpMethod("POST")
public class BlockUserAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputOutputUserBlockStatus inputUserBlock = getRequestBody(request.getNettyRequest(), InputOutputUserBlockStatus.class);
if (inputUserBlock != null
&& !StringUtil.isNullOrEmpty(inputUserBlock.getUserId())) {
ErrorCode errorCode = messagesStore.modifyUserStatus(inputUserBlock.getUserId(), inputUserBlock.getStatus());
response.setStatus(HttpResponseStatus.OK);
RestResult result;
result = RestResult.resultOf(errorCode);
response.setContent(new Gson().toJson(result));
RPCCenter.getInstance().sendRequest(null, null, RPCCenter.KICKOFF_USER_REQUEST, inputUserBlock.getUserId().getBytes(), inputUserBlock.getUserId(), TargetEntry.Type.TARGET_TYPE_USER, null);
sendResponse(response, ErrorCode.ERROR_CODE_SUCCESS, null);
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,45 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputGetUserInfo;
import com.xiaoleilu.loServer.pojos.OutputCreateUser;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.ErrorCode;
@Route("/admin/user/onlinestatus")
@HttpMethod("POST")
public class CheckUserOnlineAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return false;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputGetUserInfo inputUserId = getRequestBody(request.getNettyRequest(), InputGetUserInfo.class);
if (inputUserId == null || !StringUtil.isNullOrEmpty(inputUserId.getUserId())) {
RPCCenter.getInstance().sendRequest(null, null, RPCCenter.CHECK_USER_ONLINE_REQUEST, inputUserId.getUserId().getBytes(), inputUserId.getUserId(), TargetEntry.Type.TARGET_TYPE_USER, null);
return;
} else {
sendResponse(response, ErrorCode.INVALID_PARAMETER, null);
}
}
}
}
@@ -0,0 +1,71 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputCreateChatroom;
import com.xiaoleilu.loServer.pojos.OutputCreateChatroom;
import io.moquette.spi.impl.Utils;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.ErrorCode;
import win.liyufan.im.UUIDGenerator;
@Route("/admin/chatroom")
@HttpMethod("PUT")
public class CreateChatroomAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
FullHttpRequest fullHttpRequest = (FullHttpRequest)request.getNettyRequest();
byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content());
String content = new String(bytes);
Gson gson = new Gson();
InputCreateChatroom inputCreateChatroom = gson.fromJson(content, InputCreateChatroom.class);
if (inputCreateChatroom != null
&& !StringUtil.isNullOrEmpty(inputCreateChatroom.getTitle())) {
if (StringUtil.isNullOrEmpty(inputCreateChatroom.getChatroomId())) {
inputCreateChatroom.setChatroomId(messagesStore.getShortUUID());
}
if (inputCreateChatroom.getPortrait() == null || inputCreateChatroom.getPortrait().length() == 0) {
inputCreateChatroom.setPortrait("https://avatars.io/gravatar/" + inputCreateChatroom.getChatroomId());
}
WFCMessage.ChatroomInfo info = inputCreateChatroom.toChatroomInfo();
messagesStore.createChatroom(inputCreateChatroom.getChatroomId(), info);
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.ok(new OutputCreateChatroom(inputCreateChatroom.getChatroomId()));
response.setContent(new Gson().toJson(result));
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,85 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputCreateGroup;
import com.xiaoleilu.loServer.pojos.OutputCreateGroupResult;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import win.liyufan.im.ErrorCode;
import win.liyufan.im.IMTopic;
import java.util.concurrent.Executor;
@Route("admin/group")
@HttpMethod("PUT")
public class CreateGroupAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputCreateGroup inputCreateGroup = getRequestBody(request.getNettyRequest(), InputCreateGroup.class);
if (inputCreateGroup.isValide()) {
RPCCenter.getInstance().sendRequest(inputCreateGroup.getOperator(), null, IMTopic.CreateGroupTopic, inputCreateGroup.toProtoGroupRequest().toByteArray(), inputCreateGroup.getOperator(), TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(result);
ErrorCode errorCode = ErrorCode.fromCode(byteBuf.readByte());
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
byte[] data = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(data);
String groupId = new String(data);
sendResponse(response, null, new OutputCreateGroupResult(groupId));
} else {
sendResponse(response, errorCode, null);
}
}
@Override
public void onError(ErrorCode errorCode) {
sendResponse(response, errorCode, null);
}
@Override
public void onTimeout() {
sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null);
}
@Override
public Executor getResponseExecutor() {
return command -> {
ctx.executor().execute(command);
};
}
});
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,80 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputCreateRobot;
import com.xiaoleilu.loServer.pojos.OutputCreateRobot;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.ErrorCode;
import win.liyufan.im.UUIDGenerator;
@Route("admin/robot/create")
@HttpMethod("POST")
public class CreateRobotAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputCreateRobot inputCreateRobot = getRequestBody(request.getNettyRequest(), InputCreateRobot.class);
if (inputCreateRobot != null
&& !StringUtil.isNullOrEmpty(inputCreateRobot.getName())) {
if(StringUtil.isNullOrEmpty(inputCreateRobot.getPassword())) {
inputCreateRobot.setPassword(UUIDGenerator.getUUID());
}
if(StringUtil.isNullOrEmpty(inputCreateRobot.getUserId())) {
inputCreateRobot.setUserId(messagesStore.getShortUUID());
}
if (inputCreateRobot.getPortrait() == null || inputCreateRobot.getPortrait().length() == 0) {
inputCreateRobot.setPortrait("https://avatars.io/gravatar/" + inputCreateRobot.getUserId());
}
WFCMessage.User newUser = inputCreateRobot.toUser();
messagesStore.addUserInfo(newUser, inputCreateRobot.getPassword());
if (StringUtil.isNullOrEmpty(inputCreateRobot.getOwner())) {
inputCreateRobot.setOwner(inputCreateRobot.getUserId());
}
if (StringUtil.isNullOrEmpty(inputCreateRobot.getSecret())) {
inputCreateRobot.setSecret(UUIDGenerator.getUUID());
}
messagesStore.addRobot(inputCreateRobot.toRobot());
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.ok(new OutputCreateRobot(inputCreateRobot.getUserId(), inputCreateRobot.getSecret()));
response.setContent(new Gson().toJson(result));
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,94 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputCreateUser;
import com.xiaoleilu.loServer.pojos.OutputCreateUser;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.ErrorCode;
import win.liyufan.im.UUIDGenerator;
@Route("admin/user/create")
@HttpMethod("POST")
public class CreateUserAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputCreateUser inputCreateUser = getRequestBody(request.getNettyRequest(), InputCreateUser.class);
if (inputCreateUser != null
&& !StringUtil.isNullOrEmpty(inputCreateUser.getName())) {
if(StringUtil.isNullOrEmpty(inputCreateUser.getPassword())) {
inputCreateUser.setPassword(UUIDGenerator.getUUID());
}
if(StringUtil.isNullOrEmpty(inputCreateUser.getUserId())) {
inputCreateUser.setUserId(messagesStore.getShortUUID());
}
if (inputCreateUser.getPortrait() == null || inputCreateUser.getPortrait().length() == 0) {
inputCreateUser.setPortrait("https://avatars.io/gravatar/" + inputCreateUser.getUserId());
}
WFCMessage.User.Builder newUserBuilder = WFCMessage.User.newBuilder()
.setUid(StringUtil.isNullOrEmpty(inputCreateUser.getUserId()) ? "" : inputCreateUser.getUserId());
if (inputCreateUser.getName() != null)
newUserBuilder.setName(inputCreateUser.getName());
if (inputCreateUser.getDisplayName() != null)
newUserBuilder.setDisplayName(StringUtil.isNullOrEmpty(inputCreateUser.getDisplayName()) ? inputCreateUser.getName() : inputCreateUser.getDisplayName());
if (inputCreateUser.getPortrait() != null)
newUserBuilder.setPortrait(inputCreateUser.getPortrait());
if (inputCreateUser.getEmail() != null)
newUserBuilder.setEmail(inputCreateUser.getEmail());
if (inputCreateUser.getAddress() != null)
newUserBuilder.setAddress(inputCreateUser.getAddress());
if (inputCreateUser.getCompany() != null)
newUserBuilder.setCompany(inputCreateUser.getCompany());
if (inputCreateUser.getSocial() != null)
newUserBuilder.setSocial(inputCreateUser.getSocial());
if (inputCreateUser.getMobile() != null)
newUserBuilder.setMobile(inputCreateUser.getMobile());
newUserBuilder.setGender(inputCreateUser.getGender());
if (inputCreateUser.getExtra() != null)
newUserBuilder.setExtra(inputCreateUser.getExtra());
newUserBuilder.setUpdateDt(System.currentTimeMillis());
messagesStore.addUserInfo(newUserBuilder.build(), inputCreateUser.getPassword());
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.ok(new OutputCreateUser(inputCreateUser.getUserId(), inputCreateUser.getName()));
response.setContent(new Gson().toJson(result));
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,58 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputDestoryChatroom;
import io.moquette.spi.impl.Utils;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.ErrorCode;
@Route("/admin/chatroom")
@HttpMethod("DELETE")
public class DestoryChatroomAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
FullHttpRequest fullHttpRequest = (FullHttpRequest)request.getNettyRequest();
byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content());
String content = new String(bytes);
Gson gson = new Gson();
InputDestoryChatroom inputDestoryChatroom = gson.fromJson(content, InputDestoryChatroom.class);
if (inputDestoryChatroom != null
&& !StringUtil.isNullOrEmpty(inputDestoryChatroom.getChatroomId())) {
messagesStore.destoryChatroom(inputDestoryChatroom.getChatroomId());
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.ok();
response.setContent(new Gson().toJson(result));
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,60 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.OutputGetChatroomInfo;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.ErrorCode;
@Route("/admin/chatroom")
@HttpMethod("GET")
public class GetChatroomInfoAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
String chatroomid = request.getParam("id");
if (!StringUtil.isNullOrEmpty(chatroomid)) {
WFCMessage.ChatroomInfo info = messagesStore.getChatroomInfo(chatroomid);
RestResult result;
if (info == null) {
result = RestResult.ok(new OutputGetChatroomInfo(chatroomid, messagesStore.getChatroomMemberCount(chatroomid), info));
} else {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_SUCCESS);
}
response.setStatus(HttpResponseStatus.OK);
response.setContent(new Gson().toJson(result));
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,81 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.proto.WFCMessage;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputGetToken;
import com.xiaoleilu.loServer.pojos.OutputGetIMTokenData;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.handler.codec.http.FullHttpRequest;
import win.liyufan.im.ErrorCode;
import win.liyufan.im.IMTopic;
import java.util.Base64;
import java.util.concurrent.Executor;
@Route("/admin/user/token")
@HttpMethod("POST")
public class GetIMTokenAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return false;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputGetToken input = getRequestBody(request.getNettyRequest(), InputGetToken.class);
String userId = input.getUserId();
WFCMessage.GetTokenRequest getTokenRequest = WFCMessage.GetTokenRequest.newBuilder().setUserId(userId).setClientId(input.getClientId()).build();
RPCCenter.getInstance().sendRequest(userId, input.getClientId(), IMTopic.GetTokenTopic, getTokenRequest.toByteArray(), userId, TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
ErrorCode errorCode1 = ErrorCode.fromCode(result[0]);
if (errorCode1 == ErrorCode.ERROR_CODE_SUCCESS) {
//ba errorcode qudiao
byte[] data = new byte[result.length -1];
for (int i = 0; i < data.length; i++) {
data[i] = result[i+1];
}
String token = Base64.getEncoder().encodeToString(data);
sendResponse(response, null, new OutputGetIMTokenData(userId, token));
} else {
sendResponse(response, errorCode1, null);
}
}
@Override
public void onError(ErrorCode errorCode) {
sendResponse(response, errorCode, null);
}
@Override
public void onTimeout() {
sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null);
}
@Override
public Executor getResponseExecutor() {
return command -> {
ctx.executor().execute(command);
};
}
});
}
}
}
@@ -0,0 +1,68 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputCreateUser;
import com.xiaoleilu.loServer.pojos.InputGetUserInfo;
import com.xiaoleilu.loServer.pojos.OutputCreateUser;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.ErrorCode;
@Route("admin/user/info")
@HttpMethod("POST")
public class GetUserAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputGetUserInfo inputUserId = getRequestBody(request.getNettyRequest(), InputGetUserInfo.class);
if (inputUserId != null
&& (!StringUtil.isNullOrEmpty(inputUserId.getUserId()) || !StringUtil.isNullOrEmpty(inputUserId.getName()))) {
WFCMessage.User user = null;
if(!StringUtil.isNullOrEmpty(inputUserId.getUserId())) {
user = messagesStore.getUserInfo(inputUserId.getUserId());
} else if(!StringUtil.isNullOrEmpty(inputUserId.getName())) {
user = messagesStore.getUserInfoByName(inputUserId.getName());
} else if(!StringUtil.isNullOrEmpty(inputUserId.getMobile())) {
user = messagesStore.getUserInfoByMobile(inputUserId.getMobile());
}
response.setStatus(HttpResponseStatus.OK);
RestResult result;
if (user == null) {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST);
} else {
result = RestResult.ok(InputCreateUser.fromPbUser(user));
}
response.setContent(new Gson().toJson(result));
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,37 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
@Route("admin/user/statuslist")
@HttpMethod("POST")
public class GetUserBlockListAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.ok(messagesStore.getUserStatusList());
response.setContent(new Gson().toJson(result));
}
}
}
@@ -0,0 +1,53 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputGetUserInfo;
import com.xiaoleilu.loServer.pojos.OutputCreateUser;
import com.xiaoleilu.loServer.pojos.OutputUserStatus;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.ErrorCode;
@Route("admin/user/checkstatus")
@HttpMethod("POST")
public class GetUserBlockStatusAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputGetUserInfo inputUserId = getRequestBody(request.getNettyRequest(), InputGetUserInfo.class);
if (inputUserId != null
&& !StringUtil.isNullOrEmpty(inputUserId.getUserId())) {
int status = messagesStore.getUserStatus(inputUserId.getUserId());
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.ok(new OutputUserStatus(status));
response.setContent(new Gson().toJson(result));
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,80 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.RecallMessageData;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.ErrorCode;
import win.liyufan.im.IMTopic;
import java.util.concurrent.Executor;
@Route("admin/message/recall")
@HttpMethod("POST")
public class RecallMessageAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
RecallMessageData recallMessageData = getRequestBody(request.getNettyRequest(), RecallMessageData.class);
if (recallMessageData != null && !StringUtil.isNullOrEmpty(recallMessageData.getOperator())) {
WFCMessage.INT64Buf idBuf = WFCMessage.INT64Buf.newBuilder().setId(recallMessageData.getMessageUid()).build();
RPCCenter.getInstance().sendRequest(recallMessageData.getOperator(), null, IMTopic.RecallMessageTopic, idBuf.toByteArray(), recallMessageData.getOperator(), TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(result);
ErrorCode errorCode = ErrorCode.fromCode(byteBuf.readByte());
sendResponse(response, errorCode, null);
}
@Override
public void onError(ErrorCode errorCode) {
sendResponse(response, errorCode, null);
}
@Override
public void onTimeout() {
sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null);
}
@Override
public Executor getResponseExecutor() {
return command -> {
ctx.executor().execute(command);
};
}
});
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,84 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.SendMessageData;
import com.xiaoleilu.loServer.pojos.SendMessageResult;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import win.liyufan.im.ErrorCode;
import win.liyufan.im.IMTopic;
import java.util.concurrent.Executor;
@Route("admin/message/send")
@HttpMethod("POST")
public class SendMessageAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
SendMessageData sendMessageData = getRequestBody(request.getNettyRequest(), SendMessageData.class);
if (SendMessageData.isValide(sendMessageData)) {
RPCCenter.getInstance().sendRequest(sendMessageData.getSender(), null, IMTopic.SendMessageTopic, sendMessageData.toProtoMessage().toByteArray(), sendMessageData.getSender(), TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(result);
ErrorCode errorCode = ErrorCode.fromCode(byteBuf.readByte());
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
long messageId = byteBuf.readLong();
long timestamp = byteBuf.readLong();
sendResponse(response, null, new SendMessageResult(messageId, timestamp));
} else {
sendResponse(response, errorCode, null);
}
}
@Override
public void onError(ErrorCode errorCode) {
sendResponse(response, errorCode, null);
}
@Override
public void onTimeout() {
sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null);
}
@Override
public Executor getResponseExecutor() {
return command -> {
ctx.executor().execute(command);
};
}
});
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,51 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputOutputSensitiveWords;
import com.xiaoleilu.loServer.pojos.InputOutputUserBlockStatus;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.ErrorCode;
@Route("admin/sensitive/add")
@HttpMethod("POST")
public class SensitiveWordAddAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputOutputSensitiveWords input = getRequestBody(request.getNettyRequest(), InputOutputSensitiveWords.class);
if (input != null && input.getWords() != null && !input.getWords().isEmpty()) {
ErrorCode errorCode = messagesStore.addSensitiveWords(input.getWords()) ? ErrorCode.ERROR_CODE_SUCCESS : ErrorCode.ERROR_CODE_SERVER_ERROR;
response.setStatus(HttpResponseStatus.OK);
sendResponse(response, errorCode, null);
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,48 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputOutputSensitiveWords;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import win.liyufan.im.ErrorCode;
@Route("admin/sensitive/del")
@HttpMethod("POST")
public class SensitiveWordDeleteAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputOutputSensitiveWords input = getRequestBody(request.getNettyRequest(), InputOutputSensitiveWords.class);
if (input != null && input.getWords() != null && !input.getWords().isEmpty()) {
ErrorCode errorCode = messagesStore.removeSensitiveWords(input.getWords()) ? ErrorCode.ERROR_CODE_SUCCESS : ErrorCode.ERROR_CODE_SERVER_ERROR;
response.setStatus(HttpResponseStatus.OK);
sendResponse(response, errorCode, null);
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,42 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputOutputSensitiveWords;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import win.liyufan.im.ErrorCode;
import java.util.List;
@Route("admin/sensitive/query")
@HttpMethod("POST")
public class SensitiveWordQueryAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
List<String> words = messagesStore.getAllSensitiveWords();
InputOutputSensitiveWords out = new InputOutputSensitiveWords();
out.setWords(words);
sendResponse(response, ErrorCode.ERROR_CODE_SUCCESS, out);
}
}
}
@@ -0,0 +1,90 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.channel;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.action.Action;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.persistence.RPCCenter;
import io.moquette.spi.impl.Utils;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import org.apache.commons.codec.digest.DigestUtils;
import win.liyufan.im.ErrorCode;
import win.liyufan.im.RateLimiter;
abstract public class ChannelAction extends Action {
private final RateLimiter mLimitCounter = new RateLimiter(10, 200);
protected WFCMessage.ChannelInfo channelInfo;
@Override
public ErrorCode preAction(Request request, Response response) {
String nonce = request.getHeader("nonce");
String timestamp = request.getHeader("timestamp");
String sign = request.getHeader("sign");
String cid = request.getHeader("cid");
if (StringUtil.isNullOrEmpty(nonce) || StringUtil.isNullOrEmpty(timestamp) || StringUtil.isNullOrEmpty(sign) || StringUtil.isNullOrEmpty(cid)) {
return ErrorCode.INVALID_PARAMETER;
}
if (!mLimitCounter.isGranted(cid)) {
return ErrorCode.ERROR_CODE_OVER_FREQUENCY;
}
Long ts;
try {
ts = Long.parseLong(timestamp);
} catch (NumberFormatException e) {
e.printStackTrace();
return ErrorCode.INVALID_PARAMETER;
}
if (System.currentTimeMillis() - ts > 2 * 60 * 60 * 1000) {
return ErrorCode.ERROR_CODE_SIGN_EXPIRED;
}
channelInfo = messagesStore.getChannelInfo(cid);
if (channelInfo == null) {
return ErrorCode.INVALID_PARAMETER;
}
String str = nonce + "|" + channelInfo.getSecret() + "|" + timestamp;
String localSign = DigestUtils.sha1Hex(str);
return localSign.equals(sign) ? ErrorCode.ERROR_CODE_SUCCESS : ErrorCode.ERROR_CODE_AUTH_FAILURE;
}
protected void sendResponse(Response response, ErrorCode errorCode, Object data) {
response.setStatus(HttpResponseStatus.OK);
if (errorCode == null) {
errorCode = ErrorCode.ERROR_CODE_SUCCESS;
}
RestResult result = RestResult.resultOf(errorCode, errorCode.getMsg(), data);
response.setContent(new Gson().toJson(result));
response.send();
}
protected <T> T getRequestBody(HttpRequest request, Class<T> cls) {
if (request instanceof FullHttpRequest) {
FullHttpRequest fullHttpRequest = (FullHttpRequest) request;
byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content());
String content = new String(bytes);
Gson gson = new Gson();
T t = gson.fromJson(content, cls);
return t;
}
return null;
}
}
@@ -0,0 +1,92 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.channel;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.action.admin.AdminAction;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.Conversation;
import com.xiaoleilu.loServer.pojos.SendMessageData;
import com.xiaoleilu.loServer.pojos.SendMessageResult;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import win.liyufan.im.ErrorCode;
import win.liyufan.im.IMTopic;
import java.util.concurrent.Executor;
import static cn.wildfirechat.proto.ProtoConstants.ConversationType.ConversationType_Channel;
@Route("channel/message/send")
@HttpMethod("POST")
public class SendMessageAction extends ChannelAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
SendMessageData sendMessageData = getRequestBody(request.getNettyRequest(), SendMessageData.class);
sendMessageData.setConv(new Conversation());
sendMessageData.getConv().setType(ConversationType_Channel);
sendMessageData.getConv().setTarget(channelInfo.getTargetId());
sendMessageData.setSender(channelInfo.getOwner());
if (SendMessageData.isValide(sendMessageData)) {
RPCCenter.getInstance().sendRequest(sendMessageData.getSender(), null, IMTopic.SendMessageTopic, sendMessageData.toProtoMessage().toByteArray(), sendMessageData.getSender(), TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(result);
ErrorCode errorCode = ErrorCode.fromCode(byteBuf.readByte());
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
long messageId = byteBuf.readLong();
long timestamp = byteBuf.readLong();
sendResponse(response, null, new SendMessageResult(messageId, timestamp));
} else {
sendResponse(response, errorCode, null);
}
}
@Override
public void onError(ErrorCode errorCode) {
sendResponse(response, errorCode, null);
}
@Override
public void onTimeout() {
sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null);
}
@Override
public Executor getResponseExecutor() {
return command -> {
ctx.executor().execute(command);
};
}
});
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,63 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.robot;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.InputCreateUser;
import com.xiaoleilu.loServer.pojos.InputGetUserInfo;
import com.xiaoleilu.loServer.pojos.OutputCreateUser;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.ErrorCode;
@Route("admin/robot/user_info")
@HttpMethod("POST")
public class GetUserAction extends RobotAction {
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputGetUserInfo inputUserId = getRequestBody(request.getNettyRequest(), InputGetUserInfo.class);
if (inputUserId != null
&& (!StringUtil.isNullOrEmpty(inputUserId.getUserId()) || !StringUtil.isNullOrEmpty(inputUserId.getName()))) {
WFCMessage.User user = null;
if(!StringUtil.isNullOrEmpty(inputUserId.getUserId())) {
user = messagesStore.getUserInfo(inputUserId.getUserId());
} else if(!StringUtil.isNullOrEmpty(inputUserId.getName())) {
user = messagesStore.getUserInfoByName(inputUserId.getName());
} else if(!StringUtil.isNullOrEmpty(inputUserId.getMobile())) {
user = messagesStore.getUserInfoByMobile(inputUserId.getMobile());
}
response.setStatus(HttpResponseStatus.OK);
RestResult result;
if (user == null) {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST);
} else {
result = RestResult.ok(InputCreateUser.fromPbUser(user));
}
response.setContent(new Gson().toJson(result));
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,90 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.robot;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.action.Action;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.persistence.RPCCenter;
import io.moquette.spi.impl.Utils;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import org.apache.commons.codec.digest.DigestUtils;
import win.liyufan.im.ErrorCode;
import win.liyufan.im.RateLimiter;
abstract public class RobotAction extends Action {
private final RateLimiter mLimitCounter = new RateLimiter(10, 1000);
protected WFCMessage.Robot robot;
@Override
public ErrorCode preAction(Request request, Response response) {
String nonce = request.getHeader("nonce");
String timestamp = request.getHeader("timestamp");
String sign = request.getHeader("sign");
String rid = request.getHeader("rid");
if (StringUtil.isNullOrEmpty(nonce) || StringUtil.isNullOrEmpty(timestamp) || StringUtil.isNullOrEmpty(sign) || StringUtil.isNullOrEmpty(rid)) {
return ErrorCode.INVALID_PARAMETER;
}
if (mLimitCounter.isGranted(rid)) {
return ErrorCode.ERROR_CODE_OVER_FREQUENCY;
}
Long ts;
try {
ts = Long.parseLong(timestamp);
} catch (NumberFormatException e) {
e.printStackTrace();
return ErrorCode.INVALID_PARAMETER;
}
if (System.currentTimeMillis() - ts > 2 * 60 * 60 * 1000) {
return ErrorCode.ERROR_CODE_SIGN_EXPIRED;
}
robot = messagesStore.getRobot(rid);
if (robot == null) {
return ErrorCode.ERROR_CODE_NOT_EXIST;
}
String str = nonce + "|" + robot.getSecret() + "|" + timestamp;
String localSign = DigestUtils.sha1Hex(str);
return localSign.equals(sign) ? ErrorCode.ERROR_CODE_SUCCESS : ErrorCode.ERROR_CODE_AUTH_FAILURE;
}
protected void sendResponse(Response response, ErrorCode errorCode, Object data) {
response.setStatus(HttpResponseStatus.OK);
if (errorCode == null) {
errorCode = ErrorCode.ERROR_CODE_SUCCESS;
}
RestResult result = RestResult.resultOf(errorCode, errorCode.getMsg(), data);
response.setContent(new Gson().toJson(result));
response.send();
}
protected <T> T getRequestBody(HttpRequest request, Class<T> cls) {
if (request instanceof FullHttpRequest) {
FullHttpRequest fullHttpRequest = (FullHttpRequest) request;
byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content());
String content = new String(bytes);
Gson gson = new Gson();
T t = gson.fromJson(content, cls);
return t;
}
return null;
}
}
@@ -0,0 +1,87 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.robot;
import cn.wildfirechat.proto.ProtoConstants;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.action.admin.AdminAction;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.pojos.SendMessageData;
import com.xiaoleilu.loServer.pojos.SendMessageResult;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import win.liyufan.im.ErrorCode;
import win.liyufan.im.IMTopic;
import java.util.concurrent.Executor;
@Route("admin/robot/send")
@HttpMethod("POST")
public class SendMessageAction extends RobotAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public void action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
SendMessageData sendMessageData = getRequestBody(request.getNettyRequest(), SendMessageData.class);
sendMessageData.setSender(robot.getUid());
if (SendMessageData.isValide(sendMessageData)) {
RPCCenter.getInstance().sendRequest(sendMessageData.getSender(), null, IMTopic.SendMessageTopic, sendMessageData.toProtoMessage().toByteArray(), sendMessageData.getSender(), TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(result);
ErrorCode errorCode = ErrorCode.fromCode(byteBuf.readByte());
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
long messageId = byteBuf.readLong();
long timestamp = byteBuf.readLong();
sendResponse(response, null, new SendMessageResult(messageId, timestamp));
} else {
sendResponse(response, errorCode, null);
}
}
@Override
public void onError(ErrorCode errorCode) {
sendResponse(response, errorCode, null);
}
@Override
public void onTimeout() {
sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null);
}
@Override
public Executor getResponseExecutor() {
return command -> {
ctx.executor().execute(command);
};
}
});
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
}
}
@@ -0,0 +1,20 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.annotation;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)/*保留的时间长短*/
@Inherited/*只用于class,可被子类继承*/
public @interface HttpMethod {
String value() default "GET";
}
@@ -0,0 +1,18 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.annotation;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)/*保留的时间长短*/
@Inherited/*只用于class,可被子类继承*/
public @interface RequireAuthentication {
}
@@ -0,0 +1,25 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.annotation;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 注解,用于自定义访问的URL路径<br>
* 值可以是一个请求路径,如果需要指定HTTP方法,在前面加方法名用":"分隔既可<br>
* @author loolly
*
*/
@Retention(RetentionPolicy.RUNTIME)/*保留的时间长短*/
@Inherited/*只用于class,可被子类继承*/
public @interface Route {
String value();
}
@@ -0,0 +1,31 @@
package com.xiaoleilu.loServer.exception;
import com.xiaoleilu.hutool.util.StrUtil;
/**
* 设置异常
* @author xiaoleilu
*/
public class ServerSettingException extends RuntimeException{
private static final long serialVersionUID = -4134588858314744501L;
public ServerSettingException(Throwable e) {
super(e);
}
public ServerSettingException(String message) {
super(message);
}
public ServerSettingException(String messageTemplate, Object... params) {
super(StrUtil.format(messageTemplate, params));
}
public ServerSettingException(String message, Throwable throwable) {
super(message, throwable);
}
public ServerSettingException(Throwable throwable, String messageTemplate, Object... params) {
super(StrUtil.format(messageTemplate, params), throwable);
}
}
@@ -0,0 +1,20 @@
package com.xiaoleilu.loServer.filter;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
/**
* 过滤器接口<br>
* @author Looly
*
*/
public interface Filter {
/**
* 执行过滤
* @param request 请求对象
* @param response 响应对象
* @return 如果返回true,则继续执行下一步内容,否则中断
*/
public boolean doFilter(Request request, Response response);
}
@@ -0,0 +1,144 @@
package com.xiaoleilu.loServer.handler;
import java.io.IOException;
import com.hazelcast.core.HazelcastInstance;
import com.xiaoleilu.hutool.lang.Singleton;
import com.xiaoleilu.hutool.log.Log;
import com.xiaoleilu.hutool.log.StaticLog;
import com.xiaoleilu.loServer.ServerSetting;
import com.xiaoleilu.loServer.action.Action;
import com.xiaoleilu.loServer.action.UnknownErrorAction;
import com.xiaoleilu.loServer.action.FileAction;
import com.xiaoleilu.loServer.filter.Filter;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Action处理单元
*
* @author Looly
*/
abstract public class ActionHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private static final Logger Logger = LoggerFactory.getLogger(ActionHandler.class);
public ActionHandler(IMessagesStore messagesStore, ISessionsStore sessionsStore) {
Action.messagesStore = messagesStore;
Action.sessionsStore = sessionsStore;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
Logger.info("Http request whit url {}", fullHttpRequest.uri());
final Request request = Request.build(ctx, fullHttpRequest);
final Response response = Response.build(ctx, request);
response.setContentType("application/json");
try {
//do filter
boolean isPass = this.doFilter(request, response);
if(isPass){
//do action
this.doAction(ctx, request, response);
}
} catch (Exception e) {
Action errorAction = ServerSetting.getErrorAction(ServerSetting.MAPPING_ERROR);
request.putParam(UnknownErrorAction.ERROR_PARAM_NAME, e);
response.setContent(e.toString());
errorAction.doAction(request, response);
//如果发送请求未被触发,则触发之,否则跳过。
if(!response.isSent()){
response.send();
}
ctx.fireExceptionCaught(e);
ctx.close();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
Logger.warn("{}", cause.getMessage());
Channel channel = ctx.channel();
if(channel.isActive())ctx.close();
}
//---------------------------------------------------------------------------------------- Private method start
/**
* 执行过滤
* @param request 请求
* @param response 响应
* @@return 是否继续
*/
private boolean doFilter(Request request, Response response) {
//全局过滤器
Filter filter = ServerSetting.getFilter(ServerSetting.MAPPING_ALL);
if(null != filter){
if(false == filter.doFilter(request, response)){
return false;
}
}
//自定义Path过滤器
filter = ServerSetting.getFilter(request.getPath());
if(null != filter){
if(false == filter.doFilter(request, response)){
return false;
}
}
return true;
}
abstract boolean isValidePath(String path);
/**
* 执行Action
* @param request 请求对象
* @param response 响应对象
*/
private void doAction(ChannelHandlerContext ctx, Request request, Response response){
Action action;
if (isValidePath(request.getPath())) {
action = ServerSetting.getAction(request.getPath(), request.getMethod().toUpperCase());
} else {
action = ServerSetting.getErrorAction(ServerSetting.MAPPING_ERROR);
}
if (action == null && (request.getPath().startsWith("/api") || request.getPath().startsWith("/admin"))) {
action = ServerSetting.getErrorAction(ServerSetting.MAPPING_ERROR);
}
if (null == action) {
//查找匹配所有路径的Action
action = ServerSetting.getAction(ServerSetting.MAPPING_ALL, request.getMethod());
if(null == action){
// 非Action方法,调用静态文件读取
action = Singleton.get(FileAction.class);
}
}
action.ctx = ctx;
boolean isSync = action.doAction(request, response);
if(isSync) {
//如果发送请求未被触发,则触发之,否则跳过。
if(!response.isSent()){
response.send();
}
}
}
//---------------------------------------------------------------------------------------- Private method start
}
@@ -0,0 +1,45 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.handler;
import com.xiaoleilu.hutool.lang.Singleton;
import com.xiaoleilu.loServer.ServerSetting;
import com.xiaoleilu.loServer.action.Action;
import com.xiaoleilu.loServer.action.FileAction;
import com.xiaoleilu.loServer.action.UnknownErrorAction;
import com.xiaoleilu.loServer.filter.Filter;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Action处理单元
*
* @author Looly
*/
public class AdminActionHandler extends ActionHandler {
private static final Logger Logger = LoggerFactory.getLogger(AdminActionHandler.class);
public AdminActionHandler(IMessagesStore messagesStore, ISessionsStore sessionsStore) {
super(messagesStore, sessionsStore);
}
@Override
boolean isValidePath(String path) {
if (path.startsWith("/admin")) {
return true;
}
return false;
}
}
@@ -0,0 +1,28 @@
package com.xiaoleilu.loServer.handler;
import com.xiaoleilu.hutool.log.StaticLog;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.channel.FileRegion;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpContentCompressor;
/**
* 解决大文件传输与Gzip压缩冲突问题
* @author Looly
*
*/
public class HttpChunkContentCompressor extends HttpContentCompressor {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
StaticLog.debug("Write object [{}]", msg);
if (msg instanceof FileRegion || msg instanceof DefaultHttpResponse) {
//文件传输不经过Gzip压缩
ctx.write(msg, promise);
}else{
super.write(ctx, msg, promise);
}
}
}
@@ -0,0 +1,75 @@
package com.xiaoleilu.loServer.handler;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class HttpFileServerController {
private static final Logger logger = LoggerFactory.getLogger(HttpFileServerController.class);
private Map<String, ChannelHandlerContext> contextMap = null; //
private static class BrokenUploadInfoHandlerHolder {
private static HttpFileServerController singleton = new HttpFileServerController();
}
/**
* constructor
*/
private HttpFileServerController() {
contextMap = new ConcurrentHashMap<String, ChannelHandlerContext>();
}
/**
* getInstance
*/
public static HttpFileServerController getInstance() {
return BrokenUploadInfoHandlerHolder.singleton;
}
/**
* mapChannelHandlerContext
*
* @param requestId
* @param ctx
*/
public synchronized void mapChannelHandlerContext(String requestId, ChannelHandlerContext ctx) {
if (contextMap.containsKey(requestId)) {
logger.warn("contextMap has already contained the key:" + requestId);
}
contextMap.put(requestId, ctx);
}
/**
* getContext
*
* @param requestId
* @return
*/
public ChannelHandlerContext getChannelHandlerContext(String requestId) {
if (!contextMap.containsKey(requestId)) {
logger.warn("contextMap not contains the key:" + requestId);
return null;
}
return contextMap.get(requestId);
}
/**
* removeContext
*
* @param requestId
*/
public void removeChannelHandlerContext(String requestId) {
if (!contextMap.containsKey(requestId)) {
logger.warn("contextMap not contains the key:" + requestId);
return;
}
contextMap.remove(requestId);
// logger.info("contextMap remove done.(" + requestId + ")");
}
}
@@ -0,0 +1,420 @@
package com.xiaoleilu.loServer.handler;
import io.netty.util.internal.StringUtil;
import io.moquette.server.config.MediaServerConfig;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.RandomAccessFile;
import java.util.UUID;
import static com.xiaoleilu.loServer.handler.HttpResponseHelper.getFileExt;
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
import static io.netty.handler.codec.http.HttpHeaders.Names.ACCESS_CONTROL_ALLOW_HEADERS;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
public class HttpFileServerHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(HttpFileServerHandler.class);
private static final HttpDataFactory factory = new DefaultHttpDataFactory(false);
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
if (msg instanceof FullHttpRequest) {
FullHttpRequest request = (FullHttpRequest) msg;
String requestId = UUID.randomUUID().toString().replace("-", "");
logger.info("HttpFileServerHandler received a request: method=" + request.getMethod() + ", uri=" + request.getUri() + ", requestId=" + requestId);
if (!request.getDecoderResult().isSuccess()) {
logger.warn("http decode failed!");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST);
return;
}
if (request.getMethod() == HttpMethod.GET) {
download(ctx, request, requestId);
} else if (request.getMethod() == HttpMethod.POST) {
multipartUpload(ctx, request, requestId);
} else if (request.getMethod() == HttpMethod.DELETE) {
delete(ctx, request, requestId);
} else if (request.getMethod() == HttpMethod.OPTIONS) {
doOptions(ctx, request);
} else {
logger.warn("METHOD_NOT_ALLOWED!(methodName=" + request.getMethod().name() + ")");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.METHOD_NOT_ALLOWED);
}
} else {
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "Not a http request");
}
} catch (Exception e) {
logger.error("HttpFileServerHandler.channelRead0() process error!", e);
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
try {
if (ctx.channel().isActive()) {
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
logger.warn("exceptionCaught:" + cause.getMessage());
}
} catch (Exception e) {
logger.warn("exceptionCaught error!" + e.getMessage());
}
}
/**
* multipart上传
*/
private void multipartUpload(ChannelHandlerContext ctx, FullHttpRequest request, String requestId) {
HttpPostRequestDecoder decoder = null;
try {
decoder = new HttpPostRequestDecoder(factory, request);
} catch (HttpPostRequestDecoder.ErrorDataDecoderException e1) {
logger.error("Failed to decode file data!", e1);
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "Failed to decode file data!");
return;
}
if (decoder != null) {
if (request instanceof HttpContent) {
HttpContent chunk = (HttpContent) request;
try {
decoder.offer(chunk);
} catch (HttpPostRequestDecoder.ErrorDataDecoderException e1) {
logger.warn("BAD_REQUEST, Failed to decode file data");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "Failed to decode file data");
return;
}
long fileTotalSize = 0;
if (request.headers().contains("X-File-Total-Size")) {
try {
fileTotalSize = Integer.parseInt(request.headers().get("X-File-Total-Size"));
} catch (Exception e) {
logger.warn("invalid X-File-Total-Size value!");
}
}
readHttpDataChunkByChunk(ctx, decoder, requestId, HttpHeaders.isKeepAlive(request));
if (chunk instanceof LastHttpContent) {
releaseRequest(request, decoder);
}
} else {
logger.warn("BAD_REQUEST, Not a http request");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "Not a http request");
}
}
}
/**
* readHttpDataChunkByChunk
*/
private void readHttpDataChunkByChunk(ChannelHandlerContext ctx, HttpPostRequestDecoder decoder, String requestId, boolean isKeepAlive) {
try {
while (decoder.hasNext()) {
InterfaceHttpData data = decoder.next();
if (data != null) {
try {
writeFileUploadData(data, ctx, requestId, isKeepAlive);
} finally {
data.release();
}
}
}
} catch (Exception e) {
logger.info("chunk end");
}
}
/**
* writeFileUploadData
*/
private void writeFileUploadData(InterfaceHttpData data, ChannelHandlerContext ctx, String requestId, boolean isKeepAlive) {
try {
if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
FileUpload fileUpload = (FileUpload) data;
String remoteFileName = fileUpload.getFilename();
long remoteFileSize = fileUpload.length();
if (StringUtil.isNullOrEmpty(remoteFileName)) {
logger.warn("remoteFileName is empty!");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "file name is empty");
return;
}
if (StringUtil.isNullOrEmpty(requestId)) {
logger.warn("requestId is empty!");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "requestId is empty!");
return;
}
if (remoteFileSize > 10 * 1024 * 1024) {
logger.warn("file over limite!(" + remoteFileSize + ")");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "file over limite!");
return;
}
String remoteFileExt = "";
if (remoteFileName.lastIndexOf(".") == -1) {
remoteFileExt = "octetstream";
remoteFileName = remoteFileName + "." + remoteFileExt;
} else {
remoteFileExt = getFileExt(remoteFileName);
}
if (StringUtil.isNullOrEmpty(remoteFileExt) || remoteFileExt.equals("ing")) {
logger.warn("Invalid file extention name");
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "Invalid file extention name");
return;
}
int remoteFileTotalSize = (int) remoteFileSize;
HttpFileServerController.getInstance().mapChannelHandlerContext(requestId, ctx);
ByteBuf byteBuf = null;
int savedThunkSize = 0; // 分片接收保存的大小
int offset = 0; // 断点续传开始位置
File tmpFile = new File("./" + MediaServerConfig.FILE_STROAGE_ROOT + "/" + requestId);
boolean isError = false;
while (true) {
byte[] thunkData;
try {
byteBuf = fileUpload.getChunk(128 * 1024);
int readableBytesSize = byteBuf.readableBytes();
thunkData = new byte[readableBytesSize];
byteBuf.readBytes(thunkData);
put(tmpFile, offset, thunkData);
savedThunkSize += readableBytesSize;
offset += readableBytesSize;
if (savedThunkSize >= remoteFileSize) {
byteBuf.release();
fileUpload.release();
HttpResponseHelper.sendResponse(requestId, ctx, HttpResponseStatus.OK, HttpHeaders.Values.APPLICATION_JSON, "{\"key\":\"" + requestId + "\"}", true, false);
break;
}
} catch (Exception e) {
logger.error("save thunckData error!", e);
if (fileUpload != null)
fileUpload.release();
if (byteBuf != null)
byteBuf.release();
HttpResponseHelper.sendResponse(requestId, ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
isError = true;
return;
} finally {
thunkData = null;
if (isError) {
tmpFile.delete();
}
}
}
}
} catch (Exception e) {
logger.error("writeHttpData error!", e);
HttpResponseHelper.sendResponse(requestId, ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
}
}
public static void put(File file, long pos, byte[] data) throws Exception {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(file, "rwd");
raf.seek(pos);
raf.write(data);
} finally {
try {
if (raf != null)
raf.close();
} catch (Exception e) {
logger.warn("release error!", e);
}
}
}
/**
* 下载
*/
private void download(ChannelHandlerContext ctx, FullHttpRequest request, String requestId) {
// try {
// final String httpUri = request.getUri();
// if (httpUri.toLowerCase().equals("/favicon.ico")) {
// logger.warn("invalid httpUri(" + httpUri + ", requestId=" + requestId + ")");
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "invalid httpUri");
// return;
// }
//
// String typedDownloadInfo = MetaDataIndexHandler.getTypedDownloadInfo(httpUri);
// if (StringUtil.isNullOrEmpty(typedDownloadInfo)) {
// logger.warn("invalid httpUri(" + httpUri + ", requestId=" + requestId + ")");
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "invalid httpUri");
// return;
// }
//
// MetaDataIndex metadataIndex = MetaDataIndexHandler.parseTypedDownloadInfo(typedDownloadInfo);
// if (metadataIndex == null) {
// logger.warn("invalid typedDownloadInfo!(" + typedDownloadInfo + ", requestId=" + requestId + ")");
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "invalid downloadFileName");
// return;
// }
//
// int fileSize = MetaDataHandler.getDataFieldLength(metadataIndex);
// int chunkSize = FileStorageConst.Stroage_Remote_Invoke_Thunk_Size;
// int chunkCount = fileSize % chunkSize == 0 ? (int) (fileSize / chunkSize) : (int) (fileSize / chunkSize + 1);
//
// ClusterNode toNode = StorageClusterRouterManager.getInstance().getDownloadNode(metadataIndex.getClusterNode());
// if(toNode == null) {
// logger.warn("toNode is null!(" + typedDownloadInfo + ", requestId=" + requestId + ")");
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.NOT_FOUND, "File Not Existed");
// return;
// }
//
// ClusterNode fromNode = StorageClusterRouterManager.getInstance().getSelfNode();
// ActorSystem actorSystem = StorageClusterRouterManager.getInstance().getStorageActorSystem();
// ActorRef toActorRef = actorSystem.actorOf(toNode.getIp(), toNode.getRpcPort(), ActorType.DownloadActor.getName());
// ActorRef fromActorRef = actorSystem.actorOf(fromNode.getIp(),fromNode.getRpcPort(), ActorType.HttpFileServerActor.getName());
//
// HttpFileServerController.getInstance().mapChannelHandlerContext(requestId, ctx);
//
// DownloadMsg downloadMsg = new DownloadMsg();
// downloadMsg.setRequestId(requestId);
// downloadMsg.setIsKeepAlive(HttpHeaders.isKeepAlive(request));
// downloadMsg.setClusterNode(metadataIndex.getClusterNode());
// downloadMsg.setFileSize(fileSize);
// downloadMsg.setChunkIndex(0);
// downloadMsg.setChunkSize(chunkSize);
// downloadMsg.setChunkCount(chunkCount);
// downloadMsg.setTime(metadataIndex.getTime());
// downloadMsg.setBigFileIndex(metadataIndex.getBigFileIndex());
// downloadMsg.setOffset(metadataIndex.getOffset());
// downloadMsg.setMetaDataTotalLength(metadataIndex.getMetaDataTotalLength());
// downloadMsg.setFileType(metadataIndex.getFileType());
// downloadMsg.setBigFilePath(metadataIndex.getBigFilePath());
// downloadMsg.setStorageEngineVersion(metadataIndex.getStorageEngineVersion());
// toActorRef.tell(downloadMsg, fromActorRef);
// } catch (Exception e) {
// logger.error("download error!", e);
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
// } finally {
// releaseRequest(request, null);
// }
}
/**
* 删除
*/
private void delete(ChannelHandlerContext ctx, FullHttpRequest request, String requestId) {
// try {
// final String httpUri = request.getUri();
// String typedDownloadInfo = MetaDataIndexHandler.getTypedDownloadInfo(httpUri);
// if (StringUtil.isNullOrEmpty(typedDownloadInfo)) {
// logger.warn("invalid httpUri(" + httpUri + ")");
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "invalid httpUri");
// return;
// }
//
// MetaDataIndex metadataIndex = MetaDataIndexHandler.parseTypedDownloadInfo(typedDownloadInfo);
// if (metadataIndex == null) {
// logger.warn("invalid typedDownloadInfo!(" + typedDownloadInfo + ")");
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.BAD_REQUEST, "invalid downloadFileName");
// }
//
// ClusterNode toNode = StorageClusterRouterManager.getInstance().getClusterNode(metadataIndex.getClusterNode());
// ClusterNode fromNode = StorageClusterRouterManager.getInstance().getSelfNode();
// ActorSystem actorSystem = StorageClusterRouterManager.getInstance().getStorageActorSystem();
// ActorRef toActorRef = actorSystem.actorOf(toNode.getIp(), toNode.getRpcPort(), ActorType.DeleteActor.getName());
// ActorRef fromActorRef = actorSystem.actorOf(fromNode.getIp(),fromNode.getRpcPort(), ActorType.HttpFileServerActor.getName());
//
// HttpFileServerController.getInstance().mapChannelHandlerContext(requestId, ctx);
//
// DeleteMsg downloadMsg = new DeleteMsg();
// downloadMsg.setRequestId(requestId);
// downloadMsg.setIsKeepAlive(HttpHeaders.isKeepAlive(request));
// downloadMsg.setClusterNode(metadataIndex.getClusterNode());
// downloadMsg.setTime(metadataIndex.getTime());
// downloadMsg.setBigFileIndex(metadataIndex.getBigFileIndex());
// downloadMsg.setOffset(metadataIndex.getOffset());
// downloadMsg.setMetaDataTotalLength(metadataIndex.getMetaDataTotalLength());
// downloadMsg.setFileType(metadataIndex.getFileType());
// downloadMsg.setBigFilePath(metadataIndex.getBigFilePath());
// downloadMsg.setStorageEngineVersion(metadataIndex.getStorageEngineVersion());
// toActorRef.tell(downloadMsg, fromActorRef);
// } catch (Exception e) {
// logger.error("delete error!", e);
// HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
// } finally {
// releaseRequest(request, null);
// }
}
/**
* HTTP能力探测
*/
private void doOptions(ChannelHandlerContext ctx, FullHttpRequest request) {
try {
boolean isKeepAlive = HttpHeaders.isKeepAlive(request);
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.NO_CONTENT);
response.headers().set(CONTENT_TYPE, HttpHeaders.Values.APPLICATION_JSON);
response.headers().set(TRANSFER_ENCODING, "chunked");
if (isKeepAlive)
response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
response.headers().set(ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, DELETE, OPTIONS");
response.headers().set(ACCESS_CONTROL_ALLOW_HEADERS, "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization");
ChannelFuture future = ctx.channel().writeAndFlush(response);
if (!isKeepAlive) {
future.addListener(ChannelFutureListener.CLOSE);
}
} catch (Exception e) {
logger.error("doOptions error!", e);
HttpResponseHelper.sendResponse("", ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
} finally {
releaseRequest(request, null);
}
}
/**
* releaseRequest
*/
private void releaseRequest(FullHttpRequest request, HttpPostRequestDecoder decoder) {
try {
if (request != null)
request.release();
request = null;
if (decoder != null)
decoder.destroy();
decoder = null;
} catch (Exception e) {
logger.error("reset error!", e);
}
}
}
@@ -0,0 +1,179 @@
package com.xiaoleilu.loServer.handler;
import io.netty.util.internal.StringUtil;
import com.xiaoleilu.hutool.util.FileUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.activation.MimetypesFileTypeMap;
import static io.netty.handler.codec.http.HttpHeaders.Names.ACCESS_CONTROL_ALLOW_ORIGIN;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_RANGE;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
public class HttpResponseHelper {
private static final Logger logger = LoggerFactory.getLogger(HttpResponseHelper.class);
private static final MimetypesFileTypeMap mimetypesFileTypeMap = new MimetypesFileTypeMap();
/**
* sendResponse
*
* @param ctx
* @param status
*/
public static void sendResponse(String requestId, ChannelHandlerContext ctx, HttpResponseStatus status) {
sendResponse(requestId, ctx, status, "status: " + status.toString() + "\r\n");
}
/**
* sendResponse
*
* @param ctx
* @param status
* @param msg
*/
public static void sendResponse(String requestId, ChannelHandlerContext ctx, HttpResponseStatus status, String msg) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
removeChannelHandlerContext(requestId, ctx);
}
/**
* sendResponse(成功应答需要注意isKeepAlive和isCloseChannel的值)
*
* @param requestId
* @param ctx
* @param status
* @param contentType
* @param respBodyString
* @param isKeepAlive
* @param isCloseChannel
*/
public static void sendResponse(String requestId, ChannelHandlerContext ctx, HttpResponseStatus status, String contentType, String respBodyString, boolean isKeepAlive, boolean isCloseChannel) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(respBodyString, CharsetUtil.UTF_8));
if (!StringUtil.isNullOrEmpty(contentType)) {
response.headers().set(CONTENT_TYPE, contentType);
} else {
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
}
if (isKeepAlive) {
response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
}
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
ChannelFuture future = ctx.channel().writeAndFlush(response);
if (!isKeepAlive) {
future.addListener(ChannelFutureListener.CLOSE);
}
removeChannelHandlerContext(requestId, ctx, isCloseChannel);
}
/**
* sendResponse
*
* @param ctx
* @param statusCode
* @param resultDesc
*/
public static void sendResponse(String requestId, ChannelHandlerContext ctx, int statusCode, String resultDesc) {
HttpResponseStatus httpResponseStatus = new HttpResponseStatus(statusCode, resultDesc);
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, httpResponseStatus, Unpooled.copiedBuffer(resultDesc, CharsetUtil.UTF_8));
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
removeChannelHandlerContext(requestId, ctx);
}
/**
* 断点上传204应答
*
* @param ctx
* @param contentRange
*/
public static void sendBrokenUpload204Resonse(String requestId, ChannelHandlerContext ctx, String contentRange, boolean isKeepAlive, boolean isCloseChannel) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.NO_CONTENT, Unpooled.copiedBuffer("", CharsetUtil.UTF_8));
response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
response.headers().set(CONTENT_RANGE, contentRange);
ChannelFuture future = ctx.channel().writeAndFlush(response);
if (!isKeepAlive) {
future.addListener(ChannelFutureListener.CLOSE);
}
removeChannelHandlerContext(requestId, ctx, isCloseChannel);
}
public static String getMultipartUploadResponseBody(String remoteFileName, String fileUrl) throws Exception {
return "{\"rc_url\":{\"path\":\"" + fileUrl + "\",\"type\":0}}";
}
/**
* getContentType
*
* @param fileName
* @return
*/
public static String getFileExt(String fileName) {
int index = fileName.lastIndexOf(".");
if (index == -1) {
return "";
}
return fileName.substring(index + 1).toLowerCase();
}
public static String getContentType(String fileName) {
String fileExtName = getFileExt(fileName);
if (fileExtName.equals("png")) { // MimetypesFileTypeMap目前缺少png类型
return "image/png";
} else {
return mimetypesFileTypeMap.getContentType(fileName);
}
}
/**
* removeChannelHandlerContext
*
* @param requestId
* @param ctx
*/
private static void removeChannelHandlerContext(String requestId, ChannelHandlerContext ctx) {
removeChannelHandlerContext(requestId, ctx, true);
}
private static void removeChannelHandlerContext(String requestId, ChannelHandlerContext ctx, boolean isCloseChannel) {
try {
if (!StringUtil.isNullOrEmpty(requestId)) {
// HttpFileServerController.getInstance().removeChannelHandlerContext(requestId);
}
if (isCloseChannel)
ctx.channel().close();
} catch (Exception e) {
logger.error("close error!", e);
}
}
}
@@ -0,0 +1,45 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.handler;
import com.xiaoleilu.hutool.lang.Singleton;
import com.xiaoleilu.loServer.ServerSetting;
import com.xiaoleilu.loServer.action.Action;
import com.xiaoleilu.loServer.action.FileAction;
import com.xiaoleilu.loServer.action.UnknownErrorAction;
import com.xiaoleilu.loServer.filter.Filter;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Action处理单元
*
* @author Looly
*/
public class IMActionHandler extends ActionHandler {
private static final Logger Logger = LoggerFactory.getLogger(IMActionHandler.class);
public IMActionHandler(IMessagesStore messagesStore, ISessionsStore sessionsStore) {
super(messagesStore, sessionsStore);
}
@Override
boolean isValidePath(String path) {
if (!path.startsWith("/admin")) {
return true;
}
return false;
}
}
@@ -0,0 +1,531 @@
package com.xiaoleilu.loServer.handler;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.xiaoleilu.hutool.http.HttpUtil;
import com.xiaoleilu.hutool.lang.Conver;
import com.xiaoleilu.hutool.log.Log;
import com.xiaoleilu.hutool.log.StaticLog;
import com.xiaoleilu.hutool.util.CharsetUtil;
import com.xiaoleilu.hutool.util.DateUtil;
import com.xiaoleilu.hutool.util.StrUtil;
import com.xiaoleilu.hutool.util.URLUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import org.slf4j.LoggerFactory;
/**
* Http请求对象
*
* @author Looly
*
*/
public class Request {
private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(Request.class);
public static final String METHOD_DELETE = HttpMethod.DELETE.name();
public static final String METHOD_HEAD = HttpMethod.HEAD.name();
public static final String METHOD_GET = HttpMethod.GET.name();
public static final String METHOD_OPTIONS = HttpMethod.OPTIONS.name();
public static final String METHOD_POST = HttpMethod.POST.name();
public static final String METHOD_PUT = HttpMethod.PUT.name();
public static final String METHOD_TRACE = HttpMethod.TRACE.name();
private static final HttpDataFactory HTTP_DATA_FACTORY = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
private FullHttpRequest nettyRequest;
private String path;
private String ip;
private Map<String, String> headers = new HashMap<String, String>();
private Map<String, Object> params = new HashMap<String, Object>();
private Map<String, Cookie> cookies = new HashMap<String, Cookie>();
/**
* 构造
*
* @param ctx ChannelHandlerContext
* @param nettyRequest HttpRequest
*/
private Request(ChannelHandlerContext ctx, FullHttpRequest nettyRequest) {
this.nettyRequest = nettyRequest;
final String uri = nettyRequest.uri();
this.path = URLUtil.getPath(getUri());
this.putHeadersAndCookies(nettyRequest.headers());
// request URI parameters
this.putParams(new QueryStringDecoder(uri));
if(nettyRequest.method() != HttpMethod.GET){
HttpPostRequestDecoder decoder = null;
try {
decoder = new HttpPostRequestDecoder(HTTP_DATA_FACTORY, nettyRequest);
this.putParams(decoder);
} finally {
if(null != decoder){
decoder.destroy();
decoder = null;
}
}
}
// IP
this.putIp(ctx);
}
/**
* @return Netty的HttpRequest
*/
public HttpRequest getNettyRequest() {
return this.nettyRequest;
}
/**
* 获得版本信息
*
* @return 版本
*/
public String getProtocolVersion() {
return nettyRequest.protocolVersion().text();
}
/**
* 获得URI(带参数的路径)
*
* @return URI
*/
public String getUri() {
return nettyRequest.uri();
}
/**
* @return 获得path(不带参数的路径)
*/
public String getPath() {
return path;
}
/**
* 获得Http方法
*
* @return Http method
*/
public String getMethod() {
return nettyRequest.method().name();
}
/**
* 获得IP地址
*
* @return IP地址
*/
public String getIp() {
return ip;
}
/**
* 获得所有头信息
*
* @return 头信息Map
*/
public Map<String, String> getHeaders() {
return headers;
}
/**
* 使用ISO8859_1字符集获得Header内容<br>
* 由于Header中很少有中文,故一般情况下无需转码
*
* @param headerKey 头信息的KEY
* @return 值
*/
public String getHeader(String headerKey) {
return headers.get(headerKey);
}
/**
* @return 是否为普通表单(application/x-www-form-urlencoded)
*/
public boolean isXWwwFormUrlencoded() {
return "application/x-www-form-urlencoded".equals(getHeader("Content-Type"));
}
/**
* 获得指定的Cookie
*
* @param name cookie名
* @return Cookie对象
*/
public Cookie getCookie(String name) {
return cookies.get(name);
}
/**
* @return 获得所有Cookie信息
*/
public Map<String, Cookie> getCookies() {
return this.cookies;
}
/**
* @return 客户浏览器是否为IE
*/
public boolean isIE() {
String userAgent = getHeader("User-Agent");
if (StrUtil.isNotBlank(userAgent)) {
userAgent = userAgent.toUpperCase();
if (userAgent.contains("MSIE") || userAgent.contains("TRIDENT")) {
return true;
}
}
return false;
}
/**
* @param name 参数名
* @return 获得请求参数
*/
public String getParam(String name) {
final Object value = params.get(name);
if(null == value){
return null;
}
if(value instanceof String){
return (String)value;
}
return value.toString();
}
/**
* @param name 参数名
* @return 获得请求参数
*/
public Object getObjParam(String name) {
return params.get(name);
}
/**
* 获得GET请求参数<br>
* 会根据浏览器类型自动识别GET请求的编码方式从而解码<br>
* charsetOfServlet为null则默认的ISO_8859_1
*
* @param name 参数名
* @param charset 字符集
* @return 获得请求参数
*/
public String getParam(String name, Charset charset) {
if (null == charset) {
charset = Charset.forName(CharsetUtil.ISO_8859_1);
}
String destCharset = CharsetUtil.UTF_8;
if (isIE()) {
// IE浏览器GET请求使用GBK编码
destCharset = CharsetUtil.GBK;
}
String value = getParam(name);
if (METHOD_GET.equalsIgnoreCase(getMethod())) {
value = CharsetUtil.convert(value, charset.toString(), destCharset);
}
return value;
}
/**
* @param name 参数名
* @param defaultValue 当客户端未传参的默认值
* @return 获得请求参数
*/
public String getParam(String name, String defaultValue) {
String param = getParam(name);
return StrUtil.isBlank(param) ? defaultValue : param;
}
/**
* @param name 参数名
* @param defaultValue 当客户端未传参的默认值
* @return 获得Integer类型请求参数
*/
public Integer getIntParam(String name, Integer defaultValue) {
return Conver.toInt(getParam(name), defaultValue);
}
/**
* @param name 参数名
* @param defaultValue 当客户端未传参的默认值
* @return 获得long类型请求参数
*/
public Long getLongParam(String name, Long defaultValue) {
return Conver.toLong(getParam(name), defaultValue);
}
/**
* @param name 参数名
* @param defaultValue 当客户端未传参的默认值
* @return 获得Double类型请求参数
*/
public Double getDoubleParam(String name, Double defaultValue) {
return Conver.toDouble(getParam(name), defaultValue);
}
/**
* @param name 参数名
* @param defaultValue 当客户端未传参的默认值
* @return 获得Float类型请求参数
*/
public Float getFloatParam(String name, Float defaultValue) {
return Conver.toFloat(getParam(name), defaultValue);
}
/**
* @param name 参数名
* @param defaultValue 当客户端未传参的默认值
* @return 获得Boolean类型请求参数
*/
public Boolean getBoolParam(String name, Boolean defaultValue) {
return Conver.toBool(getParam(name), defaultValue);
}
/**
* 格式:<br>
* 1、yyyy-MM-dd HH:mm:ss <br>
* 2、yyyy-MM-dd <br>
* 3、HH:mm:ss <br>
*
* @param name 参数名
* @param defaultValue 当客户端未传参的默认值
* @return 获得Date类型请求参数,默认格式:
*/
public Date getDateParam(String name, Date defaultValue) {
String param = getParam(name);
return StrUtil.isBlank(param) ? defaultValue : DateUtil.parse(param);
}
/**
* @param name 参数名
* @param format 格式
* @param defaultValue 当客户端未传参的默认值
* @return 获得Date类型请求参数
*/
public Date getDateParam(String name, String format, Date defaultValue) {
String param = getParam(name);
return StrUtil.isBlank(param) ? defaultValue : DateUtil.parse(param, format);
}
/**
* 获得请求参数<br>
* 列表类型值,常用于表单中的多选框
*
* @param name 参数名
* @return 数组
*/
@SuppressWarnings("unchecked")
public List<String> getArrayParam(String name) {
Object value = params.get(name);
if(null == value){
return null;
}
if(value instanceof List){
return (List<String>) value;
}else if(value instanceof String){
return StrUtil.split((String)value, ',');
}else{
throw new RuntimeException("Value is not a List type!");
}
}
/**
* 获得所有请求参数
*
* @return Map
*/
public Map<String, Object> getParams() {
return params;
}
/**
* @return 是否为长连接
*/
public boolean isKeepAlive() {
final String connectionHeader = getHeader(HttpHeaderNames.CONNECTION.toString());
// 无论任何版本Connection为close时都关闭连接
if (HttpHeaderValues.CLOSE.toString().equalsIgnoreCase(connectionHeader)) {
return false;
}
// HTTP/1.0只有Connection为Keep-Alive时才会保持连接
if (HttpVersion.HTTP_1_0.text().equals(getProtocolVersion())) {
if (false == HttpHeaderValues.KEEP_ALIVE.toString().equalsIgnoreCase(connectionHeader)) {
return false;
}
}
// HTTP/1.1默认打开Keep-Alive
return true;
}
// --------------------------------------------------------- Protected method start
/**
* 填充参数(GET请求的参数)
*
* @param decoder QueryStringDecoder
*/
protected void putParams(QueryStringDecoder decoder) {
if (null != decoder) {
List<String> valueList;
for (Entry<String, List<String>> entry : decoder.parameters().entrySet()) {
valueList = entry.getValue();
if(null != valueList){
this.putParam(entry.getKey(), 1 == valueList.size() ? valueList.get(0) : valueList);
}
}
}
}
/**
* 填充参数(POST请求的参数)
*
* @param decoder QueryStringDecoder
*/
protected void putParams(HttpPostRequestDecoder decoder) {
if (null == decoder) {
return;
}
for (InterfaceHttpData data : decoder.getBodyHttpDatas()) {
putParam(data);
}
}
/**
* 填充参数
*
* @param data InterfaceHttpData
*/
protected void putParam(InterfaceHttpData data) {
final HttpDataType dataType = data.getHttpDataType();
if (dataType == HttpDataType.Attribute) {
//普通参数
Attribute attribute = (Attribute) data;
try {
this.putParam(attribute.getName(), attribute.getValue());
} catch (IOException e) {
Logger.error(e.toString());
}
}else if(dataType == HttpDataType.FileUpload){
//文件
FileUpload fileUpload = (FileUpload) data;
if(fileUpload.isCompleted()){
try {
this.putParam(data.getName(), fileUpload.getFile());
} catch (IOException e) {
Logger.error(e.toString(), "Get file param [{}] error!", data.getName());
}
}
}
}
/**
* 填充参数
*
* @param key 参数名
* @param value 参数值
*/
protected void putParam(String key, Object value) {
this.params.put(key, value);
}
/**
* 填充头部信息和Cookie信息
*
* @param headers HttpHeaders
*/
protected void putHeadersAndCookies(HttpHeaders headers) {
for (Entry<String, String> entry : headers) {
this.headers.put(entry.getKey(), entry.getValue());
}
// Cookie
final String cookieString = this.headers.get(HttpHeaderNames.COOKIE);
if (StrUtil.isNotBlank(cookieString)) {
final Set<Cookie> cookies = ServerCookieDecoder.LAX.decode(cookieString);
for (Cookie cookie : cookies) {
this.cookies.put(cookie.name(), cookie);
}
}
}
/**
* 设置客户端IP
*
* @param ctx ChannelHandlerContext
*/
protected void putIp(ChannelHandlerContext ctx) {
String ip = getHeader("X-Forwarded-For");
if (StrUtil.isNotBlank(ip)) {
ip = HttpUtil.getMultistageReverseProxyIp(ip);
} else {
final InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
ip = insocket.getAddress().getHostAddress();
}
this.ip = ip;
}
// --------------------------------------------------------- Protected method end
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("\r\nprotocolVersion: ").append(getProtocolVersion()).append("\r\n");
sb.append("uri: ").append(getUri()).append("\r\n");
sb.append("path: ").append(path).append("\r\n");
sb.append("method: ").append(getMethod()).append("\r\n");
sb.append("ip: ").append(ip).append("\r\n");
sb.append("headers:\r\n ");
for (Entry<String, String> entry : headers.entrySet()) {
sb.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
}
sb.append("params: \r\n");
for (Entry<String, Object> entry : params.entrySet()) {
sb.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
}
return sb.toString();
}
/**
* 构建Request对象
*
* @param ctx ChannelHandlerContext
* @param nettyRequest Netty的HttpRequest
* @return Request
*/
protected final static Request build(ChannelHandlerContext ctx, FullHttpRequest nettyRequest) {
return new Request(ctx, nettyRequest);
}
}
@@ -0,0 +1,592 @@
package com.xiaoleilu.loServer.handler;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import com.xiaoleilu.hutool.http.HttpUtil;
import com.xiaoleilu.hutool.log.Log;
import com.xiaoleilu.hutool.log.StaticLog;
import com.xiaoleilu.hutool.util.CharsetUtil;
import com.xiaoleilu.hutool.util.DateUtil;
import com.xiaoleilu.hutool.util.StrUtil;
import com.xiaoleilu.loServer.ServerSetting;
import com.xiaoleilu.loServer.listener.FileProgressiveFutureListener;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.DefaultCookie;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import org.slf4j.LoggerFactory;
/**
* 响应对象
*
* @author Looly
*
*/
public class Response {
private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(Response.class);
/** 返回内容类型:普通文本 */
public final static String CONTENT_TYPE_TEXT = "text/plain";
/** 返回内容类型:HTML */
public final static String CONTENT_TYPE_HTML = "text/html";
/** 返回内容类型:XML */
public final static String CONTENT_TYPE_XML = "text/xml";
/** 返回内容类型:JAVASCRIPT */
public final static String CONTENT_TYPE_JAVASCRIPT = "application/javascript";
/** 返回内容类型:JSON */
public final static String CONTENT_TYPE_JSON = "application/json";
public final static String CONTENT_TYPE_JSON_IE = "text/json";
private ChannelHandlerContext ctx;
private Request request;
private HttpVersion httpVersion = HttpVersion.HTTP_1_1;
private HttpResponseStatus status = HttpResponseStatus.OK;
private String contentType = CONTENT_TYPE_HTML;
private String charset = ServerSetting.getCharset();
private HttpHeaders headers = new DefaultHttpHeaders();
private Set<Cookie> cookies = new HashSet<Cookie>();
private Object content = Unpooled.EMPTY_BUFFER;
//发送完成标记
private boolean isSent;
public Response(ChannelHandlerContext ctx, Request request) {
this.ctx = ctx;
this.request = request;
}
/**
* 设置响应的Http版本号
*
* @param httpVersion http版本号对象
* @return 自己
*/
public Response setHttpVersion(HttpVersion httpVersion) {
this.httpVersion = httpVersion;
return this;
}
/**
* 响应状态码<br>
* 使用io.netty.handler.codec.http.HttpResponseStatus对象
*
* @param status 状态码
* @return 自己
*/
public Response setStatus(HttpResponseStatus status) {
this.status = status;
return this;
}
/**
* 响应状态码
*
* @param status 状态码
* @return 自己
*/
public Response setStatus(int status) {
return setStatus(HttpResponseStatus.valueOf(status));
}
/**
* 设置Content-Type
*
* @param contentType Content-Type
* @return 自己
*/
public Response setContentType(String contentType) {
this.contentType = contentType;
return this;
}
/**
* 设置返回内容的字符集编码
*
* @param charset 编码
* @return 自己
*/
public Response setCharset(String charset) {
this.charset = charset;
return this;
}
/**
* 增加响应的Header<br>
* 重复的Header将被叠加
*
* @param name 名
* @param value 值,可以是String,Date, int
* @return 自己
*/
public Response addHeader(String name, Object value) {
headers.add(name, value);
return this;
}
/**
* 设置响应的Header<br>
* 重复的Header将被替换
*
* @param name 名
* @param value 值,可以是String,Date, int
* @return 自己
*/
public Response setHeader(String name, Object value) {
headers.set(name, value);
return this;
}
/**
* 设置响应体长度
*
* @param contentLength 响应体长度
* @return 自己
*/
public Response setContentLength(long contentLength) {
setHeader(HttpHeaderNames.CONTENT_LENGTH.toString(), contentLength);
return this;
}
/**
* 设置是否长连接
*
* @return 自己
*/
public Response setKeepAlive() {
setHeader(HttpHeaderNames.CONNECTION.toString(), HttpHeaderValues.KEEP_ALIVE.toString());
return this;
}
// --------------------------------------------------------- Cookie start
/**
* 设定返回给客户端的Cookie
*
* @param cookie
* @return 自己
*/
public Response addCookie(Cookie cookie) {
cookies.add(cookie);
return this;
}
/**
* 设定返回给客户端的Cookie
*
* @param name Cookie名
* @param value Cookie值
* @return 自己
*/
public Response addCookie(String name, String value) {
return addCookie(new DefaultCookie(name, value));
}
/**
* 设定返回给客户端的Cookie
*
* @param name cookie名
* @param value cookie值
* @param maxAgeInSeconds -1: 关闭浏览器清除Cookie. 0: 立即清除Cookie. n>0 : Cookie存在的秒数.
* @param path Cookie的有效路径
* @param domain the Cookie可见的域,依据 RFC 2109 标准
* @return 自己
*/
public Response addCookie(String name, String value, int maxAgeInSeconds, String path, String domain) {
Cookie cookie = new DefaultCookie(name, value);
if (domain != null) {
cookie.setDomain(domain);
}
cookie.setMaxAge(maxAgeInSeconds);
cookie.setPath(path);
return addCookie(cookie);
}
/**
* 设定返回给客户端的Cookie<br>
* Path: "/"<br>
* No Domain
*
* @param name cookie名
* @param value cookie值
* @param maxAgeInSeconds -1: 关闭浏览器清除Cookie. 0: 立即清除Cookie. n>0 : Cookie存在的秒数.
* @return 自己
*/
public Response addCookie(String name, String value, int maxAgeInSeconds) {
return addCookie(name, value, maxAgeInSeconds, "/", null);
}
// --------------------------------------------------------- Cookie end
/**
* 设置响应HTML文本内容
*
* @param contentText 响应的文本
* @return 自己
*/
public Response setContent(String contentText) {
this.content = Unpooled.copiedBuffer(contentText, Charset.forName(charset));
return this;
}
/**
* 设置响应文本内容
*
* @param contentText 响应的文本
* @return 自己
*/
public Response setTextContent(String contentText) {
setContentType(CONTENT_TYPE_TEXT);
return setContent(contentText);
}
/**
* 设置响应JSON文本内容
*
* @param contentText 响应的JSON文本
* @return 自己
*/
public Response setJsonContent(String contentText) {
setContentType(request.isIE() ? CONTENT_TYPE_JSON : CONTENT_TYPE_JSON);
return setContent(contentText);
}
/**
* 设置响应XML文本内容
*
* @param contentText 响应的XML文本
* @return 自己
*/
public Response setXmlContent(String contentText) {
setContentType(CONTENT_TYPE_XML);
return setContent(contentText);
}
/**
* 设置响应文本内容
*
* @param contentBytes 响应的字节
* @return 自己
*/
public Response setContent(byte[] contentBytes) {
return setContent(Unpooled.copiedBuffer(contentBytes));
}
/**
* 设置响应文本内容
*
* @param byteBuf 响应的字节
* @return 自己
*/
public Response setContent(ByteBuf byteBuf) {
this.content = byteBuf;
return this;
}
/**
* 设置响应到客户端的文件
*
* @param file 文件
* @return 自己
*/
public Response setContent(File file) {
this.content = file;
return this;
}
/**
* Sets the Date and Cache headers for the HTTP Response
*
* @param response HTTP response
* @param fileToCache file to extract content type
*/
/**
* 设置日期和过期时间
*
* @param lastModify 上一次修改时间
* @param httpCacheSeconds 缓存时间,单位秒
*/
public void setDateAndCache(long lastModify, int httpCacheSeconds) {
SimpleDateFormat formatter = new SimpleDateFormat(DateUtil.HTTP_DATETIME_PATTERN, Locale.US);
formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
// Date header
Calendar time = new GregorianCalendar();
setHeader(HttpHeaderNames.DATE.toString(), formatter.format(time.getTime()));
// Add cache headers
time.add(Calendar.SECOND, httpCacheSeconds);
setHeader(HttpHeaderNames.EXPIRES.toString(), formatter.format(time.getTime()));
setHeader(HttpHeaderNames.CACHE_CONTROL.toString(), "private, max-age=" + httpCacheSeconds);
setHeader(HttpHeaderNames.LAST_MODIFIED.toString(), formatter.format(DateUtil.date(lastModify)));
}
// -------------------------------------------------------------------------------------- build HttpResponse start
/**
* 转换为Netty所用Response<br>
* 不包括content,一般用于返回文件类型的响应
*
* @return DefaultHttpResponse
*/
private DefaultHttpResponse toDefaultHttpResponse() {
final DefaultHttpResponse defaultHttpResponse = new DefaultHttpResponse(httpVersion, status);
// headers
final HttpHeaders httpHeaders = defaultHttpResponse.headers().add(headers);
// Cookies
for (Cookie cookie : cookies) {
httpHeaders.add(HttpHeaderNames.SET_COOKIE.toString(), ServerCookieEncoder.LAX.encode(cookie));
}
return defaultHttpResponse;
}
/**
* 转换为Netty所用Response<br>
* 用于返回一般类型响应(文本)
*
* @return FullHttpResponse
*/
private FullHttpResponse toFullHttpResponse() {
final ByteBuf byteBuf = (ByteBuf)content;
final FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(httpVersion, status, byteBuf);
// headers
final HttpHeaders httpHeaders = fullHttpResponse.headers().add(headers);
if ("application/octet-stream".equals(contentType)) {
httpHeaders.set(HttpHeaderNames.CONTENT_TYPE.toString(), contentType);
} else {
httpHeaders.set(HttpHeaderNames.CONTENT_TYPE.toString(), StrUtil.format("{};charset={}", contentType, charset));
httpHeaders.set(HttpHeaderNames.CONTENT_ENCODING.toString(), charset);
}
httpHeaders.set(HttpHeaderNames.CONTENT_LENGTH.toString(), byteBuf.readableBytes());
// Cookies
for (Cookie cookie : cookies) {
httpHeaders.add(HttpHeaderNames.SET_COOKIE.toString(), ServerCookieEncoder.LAX.encode(cookie));
}
return fullHttpResponse;
}
// -------------------------------------------------------------------------------------- build HttpResponse end
// -------------------------------------------------------------------------------------- send start
/**
* 发送响应到客户端<br>
*
* @return ChannelFuture
* @throws IOException
*/
public ChannelFuture send() {
ChannelFuture channelFuture;
if(content instanceof File){
//文件
File file = (File)content;
try {
channelFuture = sendFile(file);
} catch (IOException e) {
Logger.error(StrUtil.format("Send {} error!", file), e.toString());
channelFuture = sendError(HttpResponseStatus.FORBIDDEN, "");
}
}else{
//普通文本
channelFuture = sendFull();
}
this.isSent = true;
return channelFuture;
}
/**
* @return 是否已经出发发送请求,内部使用<br>
*/
protected boolean isSent(){
return this.isSent;
}
/**
* 发送响应到客户端
*
* @return ChannelFuture
*/
private ChannelFuture sendFull() {
if (request != null && request.isKeepAlive()) {
setKeepAlive();
return ctx.writeAndFlush(this.toFullHttpResponse());
} else {
return sendAndCloseFull();
}
}
/**
* 发送给到客户端并关闭ChannelHandlerContext
*
* @return ChannelFuture
*/
private ChannelFuture sendAndCloseFull() {
return ctx.writeAndFlush(this.toFullHttpResponse()).addListener(ChannelFutureListener.CLOSE);
}
/**
* 发送文件
*
* @param file 文件
* @return ChannelFuture
* @throws IOException
*/
private ChannelFuture sendFile(File file) throws IOException {
final RandomAccessFile raf = new RandomAccessFile(file, "r");
// 内容长度
long fileLength = raf.length();
this.setContentLength(fileLength);
//文件类型
String contentType = HttpUtil.getMimeType(file.getName());
if(StrUtil.isBlank(contentType)){
//无法识别默认使用数据流
contentType = "application/octet-stream";
}
this.setContentType(contentType);
ctx.write(this.toDefaultHttpResponse());
ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise())
.addListener(FileProgressiveFutureListener.build(raf));
return sendEmptyLast();
}
/**
* 发送结尾标记,表示发送结束
* @return ChannelFuture
*/
private ChannelFuture sendEmptyLast(){
final ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
if (false == request.isKeepAlive()) {
lastContentFuture.addListener(ChannelFutureListener.CLOSE);
}
return lastContentFuture;
}
// -------------------------------------------------------------------------------------- send end
// ---------------------------------------------------------------------------- special response start
/**
* 302 重定向
*
* @param uri 重定向到的URI
* @return ChannelFuture
*/
public ChannelFuture sendRedirect(String uri) {
return this.setStatus(HttpResponseStatus.FOUND).setHeader(HttpHeaderNames.LOCATION.toString(), uri).send();
}
/**
* 304 文件未修改
*
* @return ChannelFuture
*/
public ChannelFuture sendNotModified() {
return this.setStatus(HttpResponseStatus.NOT_MODIFIED).setHeader(HttpHeaderNames.DATE.toString(), DateUtil.formatHttpDate(DateUtil.date())).send();
}
/**
* 发送错误消息
*
* @param status 错误状态码
* @param msg 消息内容
* @return ChannelFuture
*/
public ChannelFuture sendError(HttpResponseStatus status, String msg) {
if (ctx.channel().isActive()) {
return this.setStatus(status).setContent(msg).send();
}
return null;
}
/**
* 发送404 Not Found
*
* @param msg 消息内容
* @return ChannelFuture
*/
public ChannelFuture sendNotFound(String msg) {
return sendError(HttpResponseStatus.NOT_FOUND, msg);
}
/**
* 发送500 Internal Server Error
*
* @param msg 消息内容
* @return ChannelFuture
*/
public ChannelFuture sendServerError(String msg) {
return sendError(HttpResponseStatus.INTERNAL_SERVER_ERROR, msg);
}
// ---------------------------------------------------------------------------- special response end
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("headers:\r\n ");
for ( Entry<String, String> entry : headers.entries()) {
sb.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
}
sb.append("content: ").append(StrUtil.str(content, CharsetUtil.UTF_8));
return sb.toString();
}
// ---------------------------------------------------------------------------- static method start
/**
* 构建Response对象
*
* @param ctx ChannelHandlerContext
* @param request 请求对象
* @return Response对象
*/
protected static Response build(ChannelHandlerContext ctx, Request request) {
return new Response(ctx, request);
}
/**
* 构建Response对象,Request对象为空,将无法获得某些信息<br>
* 1. 无法使用长连接
*
* @param ctx ChannelHandlerContext
* @return Response对象
*/
protected static Response build(ChannelHandlerContext ctx) {
return new Response(ctx, null);
}
// ---------------------------------------------------------------------------- static method end
}
@@ -0,0 +1,46 @@
package com.xiaoleilu.loServer.listener;
import java.io.RandomAccessFile;
import com.xiaoleilu.hutool.io.IoUtil;
import com.xiaoleilu.hutool.log.Log;
import com.xiaoleilu.hutool.log.StaticLog;
import io.netty.channel.ChannelProgressiveFuture;
import io.netty.channel.ChannelProgressiveFutureListener;
import org.slf4j.LoggerFactory;
/**
* 文件进度指示监听
* @author Looly
*
*/
public class FileProgressiveFutureListener implements ChannelProgressiveFutureListener{
private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(FileProgressiveFutureListener.class);
private RandomAccessFile raf;
public FileProgressiveFutureListener(RandomAccessFile raf) {
this.raf = raf;
}
@Override
public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
Logger.debug("Transfer progress: {} / {}", progress, total);
}
@Override
public void operationComplete(ChannelProgressiveFuture future) {
IoUtil.close(raf);
Logger.debug("Transfer complete.");
}
/**
* 构建文件进度指示监听
* @param raf RandomAccessFile
* @return 文件进度指示监听
*/
public static FileProgressiveFutureListener build(RandomAccessFile raf){
return new FileProgressiveFutureListener(raf);
}
}
@@ -0,0 +1,61 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.model;
import java.io.Serializable;
public class FriendData implements Serializable {
private String userId;
private String friendUid;
private int state;
private long timestamp;
public FriendData(String userId, String friendUid, int state, long timestamp) {
this.userId = userId;
this.friendUid = friendUid;
this.state = state;
this.timestamp = timestamp;
}
public FriendData() {
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getFriendUid() {
return friendUid;
}
public void setFriendUid(String friendUid) {
this.friendUid = friendUid;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
@@ -0,0 +1,39 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
public class Conversation {
private int type;
private String target;
private int line;
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getTarget() {
return target;
}
public void setTarget(String target) {
this.target = target;
}
public int getLine() {
return line;
}
public void setLine(int line) {
this.line = line;
}
}
@@ -0,0 +1,108 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.google.protobuf.ByteString;
import io.moquette.BrokerConstants;
import java.util.List;
public class GroupNotificationBinaryContent {
//operator
private String o;
//name
private String n;
//members
private List<String> ms;
//members
private String m;
public GroupNotificationBinaryContent(String o, String n, String m) {
this.o = o;
this.n = n;
this.m = m;
}
public GroupNotificationBinaryContent(String operator, String name, List<String> members) {
this.o = operator;
this.n = name;
this.ms = members;
}
public String getO() {
return o;
}
public void setO(String o) {
this.o = o;
}
public String getN() {
return n;
}
public void setN(String n) {
this.n = n;
}
public List<String> getMs() {
return ms;
}
public void setMs(List<String> ms) {
this.ms = ms;
}
public WFCMessage.MessageContent getGroupNotifyContent(int groupContentType) {
return WFCMessage.MessageContent.newBuilder().setType(groupContentType).setData(ByteString.copyFromUtf8(new Gson().toJson(this))).build();
}
public WFCMessage.MessageContent getAddGroupNotifyContent() {
return getGroupNotifyContent(BrokerConstants.MESSAGE_CONTENT_TYPE_ADD_GROUP_MEMBER);
}
public WFCMessage.MessageContent getCreateGroupNotifyContent() {
return getGroupNotifyContent(BrokerConstants.MESSAGE_CONTENT_TYPE_CREATE_GROUP);
}
public WFCMessage.MessageContent getDismissGroupNotifyContent() {
return getGroupNotifyContent(BrokerConstants.MESSAGE_CONTENT_TYPE_DISMISS_GROUP);
}
public WFCMessage.MessageContent getKickokfMemberGroupNotifyContent() {
return getGroupNotifyContent(BrokerConstants.MESSAGE_CONTENT_TYPE_KICKOF_GROUP_MEMBER);
}
public WFCMessage.MessageContent getQuitGroupNotifyContent() {
return getGroupNotifyContent(BrokerConstants.MESSAGE_CONTENT_TYPE_QUIT_GROUP);
}
public WFCMessage.MessageContent getTransferGroupNotifyContent() {
return getGroupNotifyContent(BrokerConstants.MESSAGE_CONTENT_TYPE_TRANSFER_GROUP_OWNER);
}
public WFCMessage.MessageContent getChangeGroupNameNotifyContent() {
return getGroupNotifyContent(BrokerConstants.MESSAGE_CONTENT_TYPE_CHANGE_GROUP_NAME);
}
public WFCMessage.MessageContent getChangeGroupPortraitNotifyContent() {
return getGroupNotifyContent(BrokerConstants.MESSAGE_CONTENT_TYPE_CHANGE_GROUP_PORTRAIT);
}
public WFCMessage.MessageContent getModifyGroupMemberAliasNotifyContent() {
return getGroupNotifyContent(BrokerConstants.MESSAGE_CONTENT_TYPE_MODIFY_GROUP_ALIAS);
}
}
@@ -0,0 +1,62 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
import cn.wildfirechat.proto.WFCMessage;
import io.netty.util.internal.StringUtil;
import java.util.List;
public class InputAddGroupMember extends InputGroupBase {
private String group_id;
private List<PojoGroupMember> members;
public String getGroup_id() {
return group_id;
}
public void setGroup_id(String group_id) {
this.group_id = group_id;
}
public List<PojoGroupMember> getMembers() {
return members;
}
public void setMembers(List<PojoGroupMember> members) {
this.members = members;
}
public boolean isValide() {
return true;
}
public WFCMessage.AddGroupMemberRequest toProtoGroupRequest() {
WFCMessage.AddGroupMemberRequest.Builder addGroupBuilder = WFCMessage.AddGroupMemberRequest.newBuilder();
addGroupBuilder.setGroupId(group_id);
for (PojoGroupMember pojoGroupMember : getMembers()) {
WFCMessage.GroupMember.Builder groupMemberBuilder = WFCMessage.GroupMember.newBuilder().setMemberId(pojoGroupMember.getMember_id());
if (!StringUtil.isNullOrEmpty(pojoGroupMember.getAlias())) {
groupMemberBuilder.setAlias(pojoGroupMember.getAlias());
}
groupMemberBuilder.setType(pojoGroupMember.getType());
addGroupBuilder.addAddedMember(groupMemberBuilder);
}
for (Integer line : to_lines
) {
addGroupBuilder.addToLine(line);
}
addGroupBuilder.setNotifyContent(notify_message.toProtoMessageContent());
return addGroupBuilder.build();
}
}
@@ -0,0 +1,101 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import io.netty.util.internal.StringUtil;
public class InputCreateChatroom {
private String chatroomId;
private String title;
private String desc;
private String portrait;
private String extra;
private Integer state;
public WFCMessage.ChatroomInfo toChatroomInfo() {
WFCMessage.ChatroomInfo.Builder builder = WFCMessage.ChatroomInfo.newBuilder().setTitle(title);
if (!StringUtil.isNullOrEmpty(desc)) {
builder.setDesc(desc);
}
if (!StringUtil.isNullOrEmpty(portrait)) {
builder.setPortrait(portrait);
}
long current = System.currentTimeMillis();
builder.setCreateDt(current).setUpdateDt(current).setMemberCount(0);
if (!StringUtil.isNullOrEmpty(extra)) {
builder.setExtra(extra);
}
if (state == null || state == 0) {
builder.setState(ProtoConstants.ChatroomState.Chatroom_State_Normal);
} else if(state == 1) {
builder.setState(ProtoConstants.ChatroomState.Chatroom_State_NotStart);
} else {
builder.setState(ProtoConstants.ChatroomState.Chatroom_State_End);
}
return builder.build();
}
public InputCreateChatroom() {
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
public String getChatroomId() {
return chatroomId;
}
public void setChatroomId(String chatroomId) {
this.chatroomId = chatroomId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getPortrait() {
return portrait;
}
public void setPortrait(String portrait) {
this.portrait = portrait;
}
public String getExtra() {
return extra;
}
public void setExtra(String extra) {
this.extra = extra;
}
}
@@ -0,0 +1,77 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
import cn.wildfirechat.proto.WFCMessage;
import io.netty.util.internal.StringUtil;
import java.util.List;
public class InputCreateGroup extends InputGroupBase {
private PojoGroup group;
public boolean isValide() {
return true;
}
public WFCMessage.CreateGroupRequest toProtoGroupRequest() {
WFCMessage.Group.Builder groupBuilder = WFCMessage.Group.newBuilder();
WFCMessage.GroupInfo.Builder groupInfoBuilder = WFCMessage.GroupInfo.newBuilder();
if (!StringUtil.isNullOrEmpty(group.getGroup_info().target_id)) {
groupInfoBuilder.setTargetId(group.getGroup_info().getTarget_id());
}
if (!StringUtil.isNullOrEmpty(group.getGroup_info().name)) {
groupInfoBuilder.setName(group.getGroup_info().getName());
}
if (!StringUtil.isNullOrEmpty(group.getGroup_info().portrait)) {
groupInfoBuilder.setPortrait(group.getGroup_info().getPortrait());
}
if (!StringUtil.isNullOrEmpty(group.getGroup_info().owner)) {
groupInfoBuilder.setOwner(group.getGroup_info().getOwner());
}
groupInfoBuilder.setType(group.getGroup_info().getType());
if (!StringUtil.isNullOrEmpty(group.getGroup_info().extra)) {
groupInfoBuilder.setExtra(group.getGroup_info().getExtra());
}
groupBuilder.setGroupInfo(groupInfoBuilder);
for (PojoGroupMember pojoGroupMember : group.getMembers()) {
WFCMessage.GroupMember.Builder groupMemberBuilder = WFCMessage.GroupMember.newBuilder().setMemberId(pojoGroupMember.getMember_id());
if (!StringUtil.isNullOrEmpty(pojoGroupMember.getAlias())) {
groupMemberBuilder.setAlias(pojoGroupMember.getAlias());
}
groupMemberBuilder.setType(pojoGroupMember.getType());
groupBuilder.addMembers(groupMemberBuilder);
}
WFCMessage.CreateGroupRequest.Builder createGroupReqBuilder = WFCMessage.CreateGroupRequest.newBuilder();
createGroupReqBuilder.setGroup(groupBuilder);
for (Integer line : to_lines
) {
createGroupReqBuilder.addToLine(line);
}
createGroupReqBuilder.setNotifyContent(notify_message.toProtoMessageContent());
return createGroupReqBuilder.build();
}
public PojoGroup getGroup() {
return group;
}
public void setGroup(PojoGroup group) {
this.group = group;
}
}
@@ -0,0 +1,217 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import io.netty.util.internal.StringUtil;
public class InputCreateRobot {
private String userId;
private String name;
private String password;
private String displayName;
private String portrait;
private int gender;
private String mobile;
private String email;
private String address;
private String company;
private String social;
private String extra;
private long updateDt;
private String owner;
private String secret;
private String callback;
private String robotExtra;
public String getSocial() {
return social;
}
public void setSocial(String social) {
this.social = social;
}
public WFCMessage.Robot toRobot() {
WFCMessage.Robot.Builder builder = WFCMessage.Robot.newBuilder();
if (!StringUtil.isNullOrEmpty(userId))
builder.setUid(userId);
if (!StringUtil.isNullOrEmpty(owner))
builder.setOwner(owner);
if (!StringUtil.isNullOrEmpty(secret))
builder.setSecret(secret);
if (!StringUtil.isNullOrEmpty(callback))
builder.setCallback(callback);
if (!StringUtil.isNullOrEmpty(robotExtra))
builder.setExtra(robotExtra);
builder.setState(0);
return builder.build();
}
public WFCMessage.User toUser() {
WFCMessage.User.Builder newUserBuilder = WFCMessage.User.newBuilder()
.setUid(userId);
if (name != null)
newUserBuilder.setName(name);
if (displayName != null)
newUserBuilder.setDisplayName(displayName);
if (getPortrait() != null)
newUserBuilder.setPortrait(getPortrait());
if (getEmail() != null)
newUserBuilder.setEmail(getEmail());
if (getAddress() != null)
newUserBuilder.setAddress(getAddress());
if (getCompany() != null)
newUserBuilder.setCompany(getCompany());
if (getSocial() != null)
newUserBuilder.setSocial(getSocial());
if (getMobile() != null)
newUserBuilder.setMobile(getMobile());
if (getExtra() != null)
newUserBuilder.setExtra(getExtra());
newUserBuilder.setType(ProtoConstants.UserType.UserType_Robot);
newUserBuilder.setGender(gender);
newUserBuilder.setUpdateDt(System.currentTimeMillis());
return newUserBuilder.build();
}
public long getUpdateDt() {
return updateDt;
}
public void setUpdateDt(long updateDt) {
this.updateDt = updateDt;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getPortrait() {
return portrait;
}
public void setPortrait(String portrait) {
this.portrait = portrait;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
public String getExtra() {
return extra;
}
public void setExtra(String extra) {
this.extra = extra;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public String getCallback() {
return callback;
}
public void setCallback(String callback) {
this.callback = callback;
}
public String getRobotExtra() {
return robotExtra;
}
public void setRobotExtra(String robotExtra) {
this.robotExtra = robotExtra;
}
}
@@ -0,0 +1,177 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
import cn.wildfirechat.proto.WFCMessage;
public class InputCreateUser {
private String userId;
private String name;
private String password;
private String displayName;
private String portrait;
private int gender;
private String mobile;
private String email;
private String address;
private String company;
private String social;
private String extra;
private long updateDt;
public static InputCreateUser fromPbUser(WFCMessage.User pbUser) {
InputCreateUser inputCreateUser = new InputCreateUser();
inputCreateUser.userId = pbUser.getUid();
inputCreateUser.name = pbUser.getName();
inputCreateUser.displayName = pbUser.getDisplayName();
inputCreateUser.portrait = pbUser.getPortrait();
inputCreateUser.gender = pbUser.getGender();
inputCreateUser.mobile = pbUser.getMobile();
inputCreateUser.email = pbUser.getEmail();
inputCreateUser.address = pbUser.getAddress();
inputCreateUser.company = pbUser.getCompany();
inputCreateUser.social = pbUser.getSocial();
inputCreateUser.extra = pbUser.getExtra();
inputCreateUser.updateDt = pbUser.getUpdateDt();
return inputCreateUser;
}
public String getSocial() {
return social;
}
public void setSocial(String social) {
this.social = social;
}
public WFCMessage.User toUser() {
WFCMessage.User.Builder newUserBuilder = WFCMessage.User.newBuilder()
.setUid(userId);
if (name != null)
newUserBuilder.setName(name);
if (displayName != null)
newUserBuilder.setDisplayName(displayName);
if (getPortrait() != null)
newUserBuilder.setPortrait(getPortrait());
if (getEmail() != null)
newUserBuilder.setEmail(getEmail());
if (getAddress() != null)
newUserBuilder.setAddress(getAddress());
if (getCompany() != null)
newUserBuilder.setCompany(getCompany());
if (getSocial() != null)
newUserBuilder.setSocial(getSocial());
if (getMobile() != null)
newUserBuilder.setMobile(getMobile());
if (getExtra() != null)
newUserBuilder.setExtra(getExtra());
newUserBuilder.setGender(gender);
newUserBuilder.setUpdateDt(System.currentTimeMillis());
return newUserBuilder.build();
}
public long getUpdateDt() {
return updateDt;
}
public void setUpdateDt(long updateDt) {
this.updateDt = updateDt;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getPortrait() {
return portrait;
}
public void setPortrait(String portrait) {
this.portrait = portrait;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
public String getExtra() {
return extra;
}
public void setExtra(String extra) {
this.extra = extra;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
}
@@ -0,0 +1,25 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
public class InputDestoryChatroom {
private String chatroomId;
public InputDestoryChatroom() {
}
public String getChatroomId() {
return chatroomId;
}
public void setChatroomId(String chatroomId) {
this.chatroomId = chatroomId;
}
}
@@ -0,0 +1,46 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
import cn.wildfirechat.proto.WFCMessage;
import io.netty.util.internal.StringUtil;
import java.util.List;
public class InputDismissGroupMember extends InputGroupBase {
private String group_id;
public String getGroup_id() {
return group_id;
}
public void setGroup_id(String group_id) {
this.group_id = group_id;
}
public boolean isValide() {
return true;
}
public WFCMessage.DismissGroupRequest toProtoGroupRequest() {
WFCMessage.DismissGroupRequest.Builder dismissGroupBuilder = WFCMessage.DismissGroupRequest.newBuilder();
dismissGroupBuilder.setGroupId(group_id);
for (Integer line : to_lines
) {
dismissGroupBuilder.addToLine(line);
}
dismissGroupBuilder.setNotifyContent(notify_message.toProtoMessageContent());
return dismissGroupBuilder.build();
}
}
@@ -0,0 +1,44 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
public class InputGetUserInfo {
private String userId;
private String name;
private String mobile;
public InputGetUserInfo(String userId, String name) {
this.userId = userId;
this.name = name;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@@ -0,0 +1,38 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
import java.util.List;
public class InputGroupBase {
public String operator;
public List<Integer> to_lines;
public MessagePayload notify_message;
public String getOperator() {
return operator;
}
public List<Integer> getTo_lines() {
return to_lines;
}
public void setTo_lines(List<Integer> to_lines) {
this.to_lines = to_lines;
}
public MessagePayload getNotify_message() {
return notify_message;
}
public void setNotify_message(MessagePayload notify_message) {
this.notify_message = notify_message;
}
}
@@ -0,0 +1,55 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
import cn.wildfirechat.proto.WFCMessage;
import io.netty.util.internal.StringUtil;
import java.util.List;
public class InputKickoffGroupMember extends InputGroupBase {
private String group_id;
private List<String> members;
public String getGroup_id() {
return group_id;
}
public void setGroup_id(String group_id) {
this.group_id = group_id;
}
public List<String> getMembers() {
return members;
}
public void setMembers(List<String> members) {
this.members = members;
}
public boolean isValide() {
return true;
}
public WFCMessage.RemoveGroupMemberRequest toProtoGroupRequest() {
WFCMessage.RemoveGroupMemberRequest.Builder removedGroupBuilder = WFCMessage.RemoveGroupMemberRequest.newBuilder();
removedGroupBuilder.setGroupId(group_id);
removedGroupBuilder.addAllRemovedMember(members);
for (Integer line : to_lines
) {
removedGroupBuilder.addToLine(line);
}
removedGroupBuilder.setNotifyContent(notify_message.toProtoMessageContent());
return removedGroupBuilder.build();
}
}
@@ -0,0 +1,24 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
import java.util.List;
public class InputOutputSensitiveWords {
private List<String> words;
public List<String> getWords() {
return words;
}
public void setWords(List<String> words) {
this.words = words;
}
}
@@ -0,0 +1,36 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
public class InputOutputUserBlockStatus {
private String userId;
private int status;
public InputOutputUserBlockStatus(String userId, int status) {
this.userId = userId;
this.status = status;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
}
@@ -0,0 +1,53 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
import cn.wildfirechat.proto.WFCMessage;
import io.netty.util.internal.StringUtil;
public class InputTransferGroup extends InputGroupBase {
private String group_id;
private String new_owner;
public boolean isValide() {
return true;
}
public WFCMessage.TransferGroupRequest toProtoGroupRequest() {
WFCMessage.TransferGroupRequest.Builder groupBuilder = WFCMessage.TransferGroupRequest.newBuilder();
groupBuilder.setGroupId(group_id);
groupBuilder.setNewOwner(new_owner);
for (Integer line : to_lines
) {
groupBuilder.addToLine(line);
}
groupBuilder.setNotifyContent(notify_message.toProtoMessageContent());
return groupBuilder.build();
}
public String getGroup_id() {
return group_id;
}
public void setGroup_id(String group_id) {
this.group_id = group_id;
}
public String getNew_owner() {
return new_owner;
}
public void setNew_owner(String new_owner) {
this.new_owner = new_owner;
}
}
@@ -0,0 +1,40 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
public class InputUserLogin {
private String name;
private String password;
private String clientId;
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
@@ -0,0 +1,164 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
import cn.wildfirechat.proto.WFCMessage;
import com.google.protobuf.ByteString;
import com.hazelcast.util.StringUtil;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
public class MessagePayload {
private int type;
private String searchableContent;
private String pushContent;
private String content;
private String base64edData;
private int mediaType;
private String remoteMediaUrl;
private int persistFlag;
private int expireDuration;
private int mentionedType;
private List<String> mentionedTarget;
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getSearchableContent() {
return searchableContent;
}
public void setSearchableContent(String searchableContent) {
this.searchableContent = searchableContent;
}
public String getPushContent() {
return pushContent;
}
public void setPushContent(String pushContent) {
this.pushContent = pushContent;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getBase64edData() {
return base64edData;
}
public void setBase64edData(String base64edData) {
this.base64edData = base64edData;
}
public int getMediaType() {
return mediaType;
}
public void setMediaType(int mediaType) {
this.mediaType = mediaType;
}
public String getRemoteMediaUrl() {
return remoteMediaUrl;
}
public void setRemoteMediaUrl(String remoteMediaUrl) {
this.remoteMediaUrl = remoteMediaUrl;
}
public int getPersistFlag() {
return persistFlag;
}
public void setPersistFlag(int persistFlag) {
this.persistFlag = persistFlag;
}
public int getExpireDuration() {
return expireDuration;
}
public void setExpireDuration(int expireDuration) {
this.expireDuration = expireDuration;
}
public int getMentionedType() {
return mentionedType;
}
public void setMentionedType(int mentionedType) {
this.mentionedType = mentionedType;
}
public List<String> getMentionedTarget() {
return mentionedTarget;
}
public void setMentionedTarget(List<String> mentionedTarget) {
this.mentionedTarget = mentionedTarget;
}
public WFCMessage.MessageContent toProtoMessageContent() {
WFCMessage.MessageContent.Builder builder = WFCMessage.MessageContent.newBuilder()
.setType(type)
.setMediaType(mediaType)
.setPersistFlag(persistFlag)
.setExpireDuration(expireDuration)
.setMentionedType(mentionedType);
if (!StringUtil.isNullOrEmpty(searchableContent))
builder.setSearchableContent(searchableContent);
if (!StringUtil.isNullOrEmpty(pushContent))
builder.setPushContent(pushContent);
if (!StringUtil.isNullOrEmpty(searchableContent))
builder.setContent(searchableContent);
if (!StringUtil.isNullOrEmpty(base64edData))
builder.setData(ByteString.copyFrom(Base64.getDecoder().decode(base64edData)));
if (!StringUtil.isNullOrEmpty(remoteMediaUrl))
builder.setRemoteMediaUrl(remoteMediaUrl);
if (mentionedTarget != null && mentionedTarget.size() > 0)
builder.addAllMentionedTarget(mentionedTarget);
return builder.build();
}
public static MessagePayload fromProtoMessageContent(WFCMessage.MessageContent protoContent) {
if (protoContent == null)
return null;
MessagePayload payload = new MessagePayload();
payload.type = protoContent.getType();
payload.searchableContent = protoContent.getSearchableContent();
payload.pushContent = protoContent.getPushContent();
payload.content = protoContent.getContent();
if (protoContent.getData() != null && protoContent.getData().size() > 0)
payload.base64edData = Base64.getEncoder().encodeToString(protoContent.getData().toByteArray());
payload.mediaType = protoContent.getMediaType();
payload.remoteMediaUrl = protoContent.getRemoteMediaUrl();
payload.persistFlag = protoContent.getPersistFlag();
payload.expireDuration = protoContent.getExpireDuration();
payload.mentionedType = protoContent.getMentionedType();
payload.mentionedTarget = new ArrayList<>();
payload.mentionedTarget.addAll(protoContent.getMentionedTargetList());
return payload;
}
}
@@ -0,0 +1,21 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
public class OutputCheckUserOnline {
private int status; //0 online, 1 have session offline, 2 no session
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
}
@@ -0,0 +1,29 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
public class OutputCreateChatroom {
private String chatroomId;
public OutputCreateChatroom() {
}
public OutputCreateChatroom(String chatroomId) {
this.chatroomId = chatroomId;
}
public String getChatroomId() {
return chatroomId;
}
public void setChatroomId(String chatroomId) {
this.chatroomId = chatroomId;
}
}
@@ -0,0 +1,31 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
import cn.wildfirechat.proto.WFCMessage;
import io.netty.util.internal.StringUtil;
import java.util.List;
public class OutputCreateGroupResult {
private String group_id;
public OutputCreateGroupResult(String group_id) {
this.group_id = group_id;
}
public String getGroup_id() {
return group_id;
}
public void setGroup_id(String group_id) {
this.group_id = group_id;
}
}
@@ -0,0 +1,35 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
public class OutputCreateRobot {
private String userId;
private String secret;
public OutputCreateRobot(String userId, String secret) {
this.userId = userId;
this.secret = secret;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
}
@@ -0,0 +1,35 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
public class OutputCreateUser {
private String userId;
private String name;
public OutputCreateUser(String userId, String name) {
this.userId = userId;
this.name = name;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@@ -0,0 +1,101 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.pojos;
import cn.wildfirechat.proto.WFCMessage;
import io.netty.util.internal.StringUtil;
public class OutputGetChatroomInfo {
private String chatroomId;
private String title;
private String desc;
private String portrait;
private String extra;
private int memberCount;
private long createDt;
private long updateDt;
public OutputGetChatroomInfo(String chatroomId, int memberCount, WFCMessage.ChatroomInfo chatroomInfo) {
this.chatroomId = chatroomId;
this.title = chatroomInfo.getTitle();
this.desc = chatroomInfo.getDesc();
this.portrait = chatroomInfo.getPortrait();
this.memberCount = memberCount;
this.createDt = chatroomInfo.getCreateDt();
this.updateDt = chatroomInfo.getUpdateDt();
}
public OutputGetChatroomInfo() {
}
public int getMemberCount() {
return memberCount;
}
public void setMemberCount(int memberCount) {
this.memberCount = memberCount;
}
public long getCreateDt() {
return createDt;
}
public void setCreateDt(long createDt) {
this.createDt = createDt;
}
public long getUpdateDt() {
return updateDt;
}
public void setUpdateDt(long updateDt) {
this.updateDt = updateDt;
}
public String getChatroomId() {
return chatroomId;
}
public void setChatroomId(String chatroomId) {
this.chatroomId = chatroomId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getPortrait() {
return portrait;
}
public void setPortrait(String portrait) {
this.portrait = portrait;
}
public String getExtra() {
return extra;
}
public void setExtra(String extra) {
this.extra = extra;
}
}

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