Compare commits

...

10 Commits

Author SHA1 Message Date
xiaobaozi-web
5a0ba9db20
Update EmailServiceTest.java 2025-03-16 12:37:14 +08:00
xiaobaozi-web
757713effe
Add files via upload 2025-03-16 12:29:07 +08:00
xiaobaozi-web
bb68dbfae0
Update README.md 2025-02-23 15:25:55 +08:00
Tong
1bc13ad195 Merge branch 'main' of github.com:youngyangyang04/kamanotes into main 2025-02-23 11:36:16 +08:00
Tong
254c1b73d8 version1 2025-02-23 11:35:43 +08:00
xiaobaozi-web
c109281a96
Update README.md 2025-02-23 11:30:10 +08:00
Tong
c7737979b9 version1 2025-02-23 11:29:10 +08:00
Tong
f3d4380831 version1 2025-02-23 11:21:21 +08:00
程序员Carl
ee25e36dd5
Initial commit 2025-02-20 21:32:46 +08:00
Tong
fe95091282 version1 卡码笔记 2025-01-23 17:29:24 +08:00
433 changed files with 38503 additions and 0 deletions

28
.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# 其它文件
stats.html

698
LICENSE Normal file
View File

@ -0,0 +1,698 @@
<<<<<<< HEAD
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
=======
MIT License
Copyright (c) 2025 程序员Carl
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
>>>>>>> ee25e36dd5348d3f8655fac46b663ec497672780

152
README.md Normal file
View File

@ -0,0 +1,152 @@
# 卡码笔记 (Kama Notes)
卡码笔记是一个面向程序员的在线笔记分享和学习平台,旨在为程序员提供一个高效的知识分享和交流空间。
## 项目特点
- 📝 支持Markdown格式的笔记编写
- 🔍 强大的笔记搜索功能
- 👥 用户互动和社交功能
- 📊 笔记数据统计和排行
- 🔔 实时消息通知系统
- 📱 响应式设计,支持多端访问
## 技术栈
***后端技术****
- 核心框架Spring Boot 2.7.18
- 安全框架Spring Security
- 持久层MyBatis
- 数据库MySQL 8.0
- 缓存Redis
- 消息推送WebSocket
- 搜索MySQL 全文索引 + Jieba 分词
- 文件存储:本地文件系统
- 日志系统Log4j2
- 测试框架JUnit
- 模板引擎Thymeleaf
- MarkdownFlexmark
- 工具库Hutool
**前端技术**
- 构建工具Vite
- 框架React + TypeScript
- 路由管理React Router DOM
- 状态管理Redux Toolkit
- UI 库Ant Design
- 样式TailwindCSS
- HTTP 客户端Axios
- WebSocket 客户端:原生 WebSocket
- Markdown 渲染
- 数据可视化
- 代码质量ESLint, Prettier
- 版本控制Husky, Lint-staged
## 快速开始
### 环境要求
- Node.js 16+
- JDK 17+
- MySQL 8.0+
- Maven 3.8+
### 开发环境搭建
1. 克隆项目
```bash
git clone https://github.com/youngyangyang04/kamanotes.git
cd kama-notes
```
2. 前端启动
```bash
cd frontend
npm install
npm run dev
```
3. 后端启动
```bash
IDEA直接启动
cd backend
mvn spring-boot:run
```
4. 数据库配置
- 创建数据库:*kamanote_tech*
- 执行SQL脚本kamanote-tech.sql
## 主要功能
### 1. 用户系统
- 用户注册和登录
- 个人信息管理
- 用户主页
### 2. 笔记管理
- 创建和编辑笔记
- 笔记分类和标签
- 笔记收藏
- 笔记搜索
### 3. 社交功能
- 笔记评论
- 点赞功能
- 消息通知
### 4. 数据统计
- 用户活跃度
- 笔记热度排行
- 个人数据统计
## 项目结构
```
├── backend/ # 后端项目
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── com/kama/notes/
│ │ │ │ ├── annotation/ # 自定义注解
│ │ │ │ ├── aspect/ # AOP切面
│ │ │ │ ├── config/ # 配置类
│ │ │ │ ├── controller/ # 控制器
│ │ │ │ ├── exception/ # 异常处理
│ │ │ │ ├── filter/ # 过滤器
│ │ │ │ ├── interceptor/ # 拦截器
│ │ │ │ ├── mapper/ # 数据访问层
│ │ │ │ ├── model/ # 数据模型
│ │ │ │ ├── scope/ # 作用域数据
│ │ │ │ ├── service/ # 业务逻辑层
│ │ │ │ ├── task/ # 定时任务
│ │ │ │ └── utils/ # 工具类
│ │ │ └── resources/
│ │ │ ├── mapper/ # MyBatis映射文件
│ │ │ └── application.yml # 配置文件
│ │ └── test/ # 测试代码
│ └── pom.xml # 项目依赖管理
```
## 贡献指南
1. Fork 本仓库
2. 创建新的分支: `git checkout -b feature/your-feature`
3. 提交更改: `git commit -m 'Add some feature'`
4. 推送到分支: `git push origin feature/your-feature`
5. 提交Pull Request

37
backend/.gitignore vendored Normal file
View File

@ -0,0 +1,37 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
## 排除 application-test.yml 和 application-prod.yml
application-dev.yml

View File

@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# 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.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip

23
backend/Dockerfile Normal file
View File

@ -0,0 +1,23 @@
# 构建阶段
FROM maven:3.9.9-amazoncorretto-17 AS build
WORKDIR /app
# 复制文件并忽略不必要的内容(通过 .dockerignore 配置)
COPY . .
# 使用 Maven 构建项目
RUN mvn -B clean package -DskipTests
# 运行阶段
FROM openjdk:17
WORKDIR /app
# 将构建产物复制到运行镜像
COPY --from=build /app/target/*.jar app.jar
# 设置启动命令并暴露端口
CMD ["java", "-Dspring.profiles.active=dev", "-jar", "app.jar"]
EXPOSE 8080

259
backend/mvnw vendored Normal file
View File

@ -0,0 +1,259 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# 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.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.2
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

149
backend/mvnw.cmd vendored Normal file
View File

@ -0,0 +1,149 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
if ($env:MAVEN_USER_HOME) {
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
}
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

182
backend/pom.xml Normal file
View File

@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.kama</groupId>
<artifactId>notes</artifactId>
<version>0.0.1</version>
<name>notes-tech</name>
<description>kamaNotes</description>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.4.Final</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.25</version>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-all</artifactId>
<version>0.64.8</version>
</dependency>
<!-- 排除 logback 改用 log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 邮件发送依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Java Mail API -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
<!-- 添加 jieba 分词依赖 -->
<dependency>
<groupId>com.huaban</groupId>
<artifactId>jieba-analysis</artifactId>
<version>1.0.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>aliyun</id>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>

View File

@ -0,0 +1,20 @@
package com.kama.notes;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* @ClassName NotesApplication
* @Description ToDo
* @Author Tong
* @LastChangeDate 2024-12-16 11:08
* @Version v1.0
*/
@SpringBootApplication
@EnableScheduling
public class NotesApplication {
public static void main(String[] args) {
SpringApplication.run(NotesApplication.class, args);
}
}

View File

@ -0,0 +1,9 @@
package com.kama.notes.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedLogin {
}

View File

@ -0,0 +1,32 @@
package com.kama.notes.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.kama.notes.annotation.NeedLogin;
import com.kama.notes.scope.RequestScopeData;
import com.kama.notes.utils.ApiResponseUtil;
@Aspect
@Component
public class NeedLoginAspect {
@Autowired
private RequestScopeData requestScopeData;
@Around("@annotation(needLogin)")
public Object around(ProceedingJoinPoint joinPoint, NeedLogin needLogin) throws Throwable {
if (!requestScopeData.isLogin()) {
return ApiResponseUtil.error("用户未登录");
}
if (requestScopeData.getUserId() == null) {
return ApiResponseUtil.error("用户 ID 异常");
}
return joinPoint.proceed();
}
}

View File

@ -0,0 +1,25 @@
package com.kama.notes.aspect;
import java.util.UUID;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PutTraceIdAspect {
private static final String TRACE_ID_KEY = "traceId";
/**
* 切面切入点拦截所有控制器的方法
*/
@Before("execution(* com.kama.notes..*(..))")
public void addTraceIdToLog() {
// 如果当前 MDC 中没有 traceId则生成一个新的
if (MDC.get(TRACE_ID_KEY) == null) {
String traceId = UUID.randomUUID().toString();
MDC.put(TRACE_ID_KEY, traceId);
}
}
}

View File

@ -0,0 +1,18 @@
package com.kama.notes.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @ClassName MyBatisConfig
* @Description MyBatis 配置类
* @Author Tong
* @LastChangeDate 2024-12-17 16:22
* @Version v1.0
*/
@Configuration// 修改为正确的Mapper包路径
@MapperScan("com.kama.notes.mapper")
@EnableTransactionManagement
public class MyBatisConfig {
}

View File

@ -0,0 +1,32 @@
package com.kama.notes.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用 String 序列化键key
template.setKeySerializer(new StringRedisSerializer());
// 使用 JSON 序列化值value
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// 使用 String 序列化哈希键hash key和值hash value
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}

View File

@ -0,0 +1,19 @@
package com.kama.notes.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@Configuration
@EnableScheduling
public class SchedulerConfig {
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("ScheduledTask-");
return scheduler;
}
}

View File

@ -0,0 +1,59 @@
package com.kama.notes.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
/**
* @ClassName Security配置类
* @Description ToDo
* @Author Tong
* @LastChangeDate 2024-12-17 15:40
* @Version v1.0
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().disable()
.httpBasic().disable();
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:5173")); // 允许的前端域名
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -0,0 +1,57 @@
package com.kama.notes.config;
import com.kama.notes.filter.TraceIdFilter;
import com.kama.notes.interceptor.TokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Value("${upload.path:D:/kamaNotes/upload}")
private String uploadPath;
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/images/**")
.addResourceLocations("file:" + uploadPath + "/");
}
/**
* 添加拦截器用于验证 token初始化请求周期中的用户相关信息
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login", "/error");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:5173", "http://127.0.0.1:5173") // 允许的域名
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")// 允许的 HTTP 方法
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
@Bean
public FilterRegistrationBean<TraceIdFilter> traceIdFilter() {
FilterRegistrationBean<TraceIdFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new TraceIdFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}

View File

@ -0,0 +1,50 @@
package com.kama.notes.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kama.notes.utils.JwtUtil;
import com.kama.notes.websocket.MessageWebSocketHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
/**
* WebSocket配置类
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
private final JwtUtil jwtUtil;
private final ObjectMapper objectMapper;
public WebSocketConfig(JwtUtil jwtUtil, ObjectMapper objectMapper) {
this.jwtUtil = jwtUtil;
this.objectMapper = objectMapper;
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(messageWebSocketHandler(), "/ws/message")
.setAllowedOrigins("*"); // 生产环境需要限制允许的域名
}
@Bean
public MessageWebSocketHandler messageWebSocketHandler() {
return new MessageWebSocketHandler(jwtUtil, objectMapper);
}
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
// 设置消息大小限制为8KB
container.setMaxTextMessageBufferSize(8192);
// 设置二进制消息大小限制为8KB
container.setMaxBinaryMessageBufferSize(8192);
// 设置空闲超时时间为60秒
container.setMaxSessionIdleTimeout(60000L);
return container;
}
}

View File

@ -0,0 +1,90 @@
package com.kama.notes.controller;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.model.base.EmptyVO;
import com.kama.notes.model.dto.category.CreateCategoryBody;
import com.kama.notes.model.dto.category.UpdateCategoryBody;
import com.kama.notes.model.vo.category.CategoryVO;
import com.kama.notes.model.vo.category.CreateCategoryVO;
import com.kama.notes.service.CategoryService;
@RestController
@RequestMapping("/api")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 获取分类列表用户端
*
* @return 包含分类列表的响应
*/
@GetMapping("/categories")
public ApiResponse<List<CategoryVO>> userCategories() {
return categoryService.categoryList();
}
/**
* 获取分类列表管理员端
*
* @return 包含分类列表的响应
*/
@GetMapping("/admin/categories")
public ApiResponse<List<CategoryVO>> categories() {
return categoryService.categoryList();
}
/**
* 创建新的分类
*
* @param createCategoryBody 包含分类创建信息的请求体
* @return 包含创建成功的分类信息的响应
*/
@PostMapping("/admin/categories")
public ApiResponse<CreateCategoryVO> createCategory(
@Valid @RequestBody CreateCategoryBody createCategoryBody) {
return categoryService.createCategory(createCategoryBody);
}
/**
* 更新指定的分类信息
*
* @param categoryId 分类ID必须为正整数
* @param updateCategoryBody 包含更新信息的请求体
* @return 包含更新操作结果的响应
*/
@PatchMapping("/admin/categories/{categoryId}")
public ApiResponse<EmptyVO> updateCategory(
@Min(value = 1, message = "categoryId 必须为正整数") @PathVariable Integer categoryId,
@Valid @RequestBody UpdateCategoryBody updateCategoryBody) {
return categoryService.updateCategory(categoryId, updateCategoryBody);
}
/**
* 删除指定的分类
*
* @param categoryId 分类ID必须为正整数
* @return 包含删除操作结果的响应
*/
@DeleteMapping("/admin/categories/{categoryId}")
public ApiResponse<EmptyVO> deleteCategory(
@Min(value = 1, message = "categoryId 必须为正整数") @PathVariable Integer categoryId) {
return categoryService.deleteCategory(categoryId);
}
}

View File

@ -0,0 +1,87 @@
package com.kama.notes.controller;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.model.base.EmptyVO;
import com.kama.notes.model.dto.collection.CollectionQueryParams;
import com.kama.notes.model.dto.collection.CreateCollectionBody;
import com.kama.notes.model.dto.collection.UpdateCollectionBody;
import com.kama.notes.model.vo.collection.CollectionVO;
import com.kama.notes.model.vo.collection.CreateCollectionVO;
import com.kama.notes.service.CollectionService;
@RestController
@RequestMapping("/api")
public class CollectionController {
@Autowired
private CollectionService collectionService;
/**
* 获取收藏夹列表接口
*
* @param queryParams 查询参数
* @return 收藏夹列表
*/
@GetMapping("/collections")
public ApiResponse<List<CollectionVO>> getCollections(
@Valid
CollectionQueryParams queryParams) {
return collectionService.getCollections(queryParams);
}
/**
* 创建收藏夹接口
*
* @param requestBody 创建收藏夹请求体
* @return 创建结果如果成功则包含收藏夹 ID
*/
@PostMapping("/collections")
public ApiResponse<CreateCollectionVO> createCollection(
@Valid
@RequestBody
CreateCollectionBody requestBody) {
return collectionService.createCollection(requestBody);
}
/**
* 删除收藏夹接口
*
* @param collectionId 收藏夹 ID
* @return 返回删除结果
*/
@DeleteMapping("/collections/{collectionId}")
public ApiResponse<EmptyVO> deleteCollection(
@PathVariable
@Min(value = 1, message = "collectionId 必须为正整数")
Integer collectionId) {
return collectionService.deleteCollection(collectionId);
}
/**
* 批量修改收藏夹接口
*
* @param collectionBody 收藏夹 ID
* @return 返回修改结果
*/
@PostMapping("/collections/batch")
public ApiResponse<EmptyVO> batchModifyCollection(
@Valid
@RequestBody
UpdateCollectionBody collectionBody) {
return collectionService.batchModifyCollection(collectionBody);
}
}

View File

@ -0,0 +1,9 @@
package com.kama.notes.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class CollectionNoteController {
}

View File

@ -0,0 +1,105 @@
package com.kama.notes.controller;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.model.base.EmptyVO;
import com.kama.notes.model.dto.comment.CommentQueryParams;
import com.kama.notes.model.dto.comment.CreateCommentRequest;
import com.kama.notes.model.dto.comment.UpdateCommentRequest;
import com.kama.notes.model.vo.comment.CommentVO;
import com.kama.notes.service.CommentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 评论控制器
*/
@Slf4j
@RestController
@RequestMapping("/api")
public class CommentController {
@Autowired
private CommentService commentService;
/**
* 创建评论
*
* @param request 创建评论请求
* @return 创建的评论ID
*/
@PostMapping("/comments")
public ApiResponse<Integer> createComment(
@Valid
@RequestBody
CreateCommentRequest request) {
return commentService.createComment(request);
}
/**
* 更新评论
*
* @param commentId 评论ID
* @param request 更新评论请求
* @return 空响应
*/
@PatchMapping("/comments/{commentId}")
public ApiResponse<EmptyVO> updateComment(
@PathVariable("commentId") Integer commentId,
@Valid
@RequestBody
UpdateCommentRequest request) {
return commentService.updateComment(commentId, request);
}
/**
* 删除评论
*
* @param commentId 评论ID
* @return 空响应
*/
@DeleteMapping("/comments/{commentId}")
public ApiResponse<EmptyVO> deleteComment(
@PathVariable("commentId") Integer commentId) {
return commentService.deleteComment(commentId);
}
/**
* 获取评论列表
*
* @param params 查询参数
* @return 评论列表
*/
@GetMapping("/comments")
public ApiResponse<List<CommentVO>> getComments(
@Valid CommentQueryParams params) {
return commentService.getComments(params);
}
/**
* 点赞评论
*
* @param commentId 评论ID
* @return 空响应
*/
@PostMapping("/comments/{commentId}/like")
public ApiResponse<EmptyVO> likeComment(
@PathVariable("commentId") Integer commentId) {
return commentService.likeComment(commentId);
}
/**
* 取消点赞评论
*
* @param commentId 评论ID
* @return 空响应
*/
@DeleteMapping("/comments/{commentId}/like")
public ApiResponse<EmptyVO> unlikeComment(
@PathVariable("commentId") Integer commentId) {
return commentService.unlikeComment(commentId);
}
}

View File

@ -0,0 +1,36 @@
package com.kama.notes.controller;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.service.EmailService;
import com.kama.notes.utils.ApiResponseUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
@RestController
@RequestMapping("/api/email")
@RequiredArgsConstructor
public class EmailController {
private final EmailService emailService;
@GetMapping("/verify-code")
public ApiResponse<Void> sendVerifyCode(
@RequestParam @NotBlank @Email String email,
@RequestParam @NotBlank String type) {
// 检查是否可以发送
if (!emailService.canSendCode(email)) {
return ApiResponseUtil.error("发送太频繁,请稍后再试");
}
try {
emailService.sendVerifyCode(email, type);
return ApiResponseUtil.success(null);
} catch (Exception e) {
return ApiResponseUtil.error(e.getMessage());
}
}
}

View File

@ -0,0 +1,88 @@
package com.kama.notes.controller;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.model.base.EmptyVO;
import com.kama.notes.model.base.PageVO;
import com.kama.notes.model.dto.message.MessageQueryParams;
import com.kama.notes.model.vo.message.MessageVO;
import com.kama.notes.model.vo.message.UnreadCountByType;
import com.kama.notes.service.MessageService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 消息控制器
*/
@RestController
@RequestMapping("/api/messages")
@RequiredArgsConstructor
public class MessageController {
private final MessageService messageService;
/**
* 获取消息列表
*
* @param params 查询参数
* @return 消息列表带分页信息
*/
@GetMapping
public ApiResponse<PageVO<MessageVO>> getMessages(@Validated MessageQueryParams params) {
return messageService.getMessages(params);
}
/**
* 标记消息为已读
*
* @param messageId 消息ID
* @return 空响应
*/
@PutMapping("/{messageId}/read")
public ApiResponse<EmptyVO> markAsRead(@PathVariable Integer messageId) {
return messageService.markAsRead(messageId);
}
/**
* 标记所有消息为已读
*
* @return 空响应
*/
@PutMapping("/read/all")
public ApiResponse<EmptyVO> markAllAsRead() {
return messageService.markAllAsRead();
}
/**
* 删除消息
*
* @param messageId 消息ID
* @return 空响应
*/
@DeleteMapping("/{messageId}")
public ApiResponse<EmptyVO> deleteMessage(@PathVariable Integer messageId) {
return messageService.deleteMessage(messageId);
}
/**
* 获取未读消息数量
*
* @return 未读消息数量
*/
@GetMapping("/unread/count")
public ApiResponse<Integer> getUnreadCount() {
return messageService.getUnreadCount();
}
/**
* 获取各类型未读消息数量
*
* @return 各类型未读消息数量
*/
@GetMapping("/unread/count/type")
public ApiResponse<List<UnreadCountByType>> getUnreadCountByType() {
return messageService.getUnreadCountByType();
}
}

View File

@ -0,0 +1,128 @@
package com.kama.notes.controller;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.model.base.EmptyVO;
import com.kama.notes.model.dto.note.CreateNoteRequest;
import com.kama.notes.model.dto.note.NoteQueryParams;
import com.kama.notes.model.dto.note.UpdateNoteRequest;
import com.kama.notes.model.vo.note.CreateNoteVO;
import com.kama.notes.model.vo.note.DownloadNoteVO;
import com.kama.notes.model.vo.note.NoteHeatMapItem;
import com.kama.notes.model.vo.note.NoteRankListItem;
import com.kama.notes.model.vo.note.NoteVO;
import com.kama.notes.model.vo.note.Top3Count;
import com.kama.notes.service.NoteService;
import lombok.extern.log4j.Log4j2;
/**
* 笔记控制器
*/
@Log4j2
@RestController
@RequestMapping("/api")
public class NoteController {
// 自动注入 NoteService 实例用于处理笔记相关的业务逻辑
@Autowired
private NoteService noteService;
/**
* 查询笔记列表
*
* @param params 查询参数对象包含筛选条件
* @return 返回一个包含笔记列表的 ApiResponse 对象
*/
@GetMapping("/notes")
public ApiResponse<List<NoteVO>> getNotes(
@Valid NoteQueryParams params) {
return noteService.getNotes(params);
}
/**
* 发布笔记
*
* @param request 创建笔记的请求对象包含笔记的内容等信息
* @return 返回一个包含新创建笔记信息的 ApiResponse 对象
*/
@PostMapping("/notes")
public ApiResponse<CreateNoteVO> createNote(
@Valid @RequestBody CreateNoteRequest request) {
return noteService.createNote(request);
}
/**
* 更新笔记
*
* @param noteId 笔记的唯一标识符用于定位要更新的笔记
* @param request 更新笔记的请求对象包含需要修改的信息
* @return 返回一个包含更新后笔记信息的 ApiResponse 对象
*/
@PatchMapping("/notes/{noteId}")
public ApiResponse<EmptyVO> updateNote(
@Min(value = 1, message = "noteId 必须为正整数") @PathVariable Integer noteId,
@Valid @RequestBody UpdateNoteRequest request) {
return noteService.updateNote(noteId, request);
}
/**
* 删除笔记
*
* @param noteId 笔记的唯一标识符用于定位要删除的笔记
* @return 返回一个包含删除结果信息的 ApiResponse 对象
*/
@DeleteMapping("/notes/{noteId}")
public ApiResponse<EmptyVO> deleteNote(
@Min(value = 1, message = "noteId 必须为正整数")
@PathVariable Integer noteId) {
return noteService.deleteNote(noteId);
}
/**
* 下载笔记
* @return
*/
@GetMapping("/notes/download")
public ApiResponse<DownloadNoteVO> downloadNote() {
return noteService.downloadNote();
}
/**
* 提交笔记排行榜
*/
@GetMapping("/notes/ranklist")
public ApiResponse<List<NoteRankListItem>> submitNoteRank() {
return noteService.submitNoteRank();
}
/**
* 用户提交热力图
*/
@GetMapping("/notes/heatmap")
public ApiResponse<List<NoteHeatMapItem>> submitNoteHeatMap() {
return noteService.submitNoteHeatMap();
}
/**
* 用户提交 top3 count
*/
@GetMapping("/notes/top3count")
public ApiResponse<Top3Count> submitNoteTop3Count() {
return noteService.submitNoteTop3Count();
}
}

View File

@ -0,0 +1,29 @@
package com.kama.notes.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.model.base.EmptyVO;
import com.kama.notes.service.NoteLikeService;
@RestController
@RequestMapping("/api")
public class NoteLikeController {
@Autowired
private NoteLikeService noteLikeService;
@PostMapping("/like/note/{noteId}")
public ApiResponse<EmptyVO> likeNote(@PathVariable Integer noteId) {
return noteLikeService.likeNote(noteId);
}
@DeleteMapping("/like/note/{noteId}")
public ApiResponse<EmptyVO> unlikeNote(@PathVariable Integer noteId) {
return noteLikeService.unlikeNote(noteId);
}
}

View File

@ -0,0 +1,41 @@
package com.kama.notes.controller;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.model.base.EmptyVO;
import com.kama.notes.model.dto.notification.NotificationDTO;
import com.kama.notes.model.vo.notification.NotificationVO;
import com.kama.notes.service.RedisService;
import com.kama.notes.utils.ApiResponseUtil;
@RestController
@RequestMapping("/api")
public class NotificationController {
// 由于比较简单直接全写在 controller 内了免得出现透传
@Autowired
private RedisService redisService;
@GetMapping("/notification")
public ApiResponse<NotificationVO> getNotifications() {
NotificationVO notificationVO = new NotificationVO();
Object o = redisService.get("kamanote:notification");
String content = o == null ? "" : o.toString();
notificationVO.setContent(content);
return ApiResponseUtil.success("获取通知成功", notificationVO);
}
@PostMapping("/notification")
public ApiResponse<EmptyVO> setNotifications(@Valid @RequestBody NotificationDTO notificationDTO) {
redisService.set("kamanote:notification", notificationDTO.getContent());
return ApiResponseUtil.success("设置通知成功");
}
}

View File

@ -0,0 +1,118 @@
package com.kama.notes.controller;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.model.base.EmptyVO;
import com.kama.notes.model.dto.question.CreateQuestionBody;
import com.kama.notes.model.dto.question.QuestionQueryParam;
import com.kama.notes.model.dto.question.SearchQuestionBody;
import com.kama.notes.model.dto.question.UpdateQuestionBody;
import com.kama.notes.model.vo.question.CreateQuestionVO;
import com.kama.notes.model.vo.question.QuestionNoteVO;
import com.kama.notes.model.vo.question.QuestionUserVO;
import com.kama.notes.model.vo.question.QuestionVO;
import com.kama.notes.service.QuestionService;
@RestController
@RequestMapping("/api")
public class QuestionController {
@Autowired
private QuestionService questionService;
/**
* 用户端获取问题列表
*
* @param queryParams 查询参数用于过滤问题列表如关键词分类等
* @return 包含用户可见问题的视图对象列表的响应
*/
@GetMapping("/questions")
public ApiResponse<List<QuestionUserVO>> userGetQuestions(@Valid QuestionQueryParam queryParams) {
return questionService.userGetQuestions(queryParams);
}
/**
* 用户端搜索问题
*
* @param body 包含搜索关键词的请求体
* @return 包含搜索结果的视图对象列表的响应
*/
@PostMapping("/questions/search")
public ApiResponse<List<QuestionVO>> searchQuestions(@Valid @RequestBody SearchQuestionBody body) {
return questionService.searchQuestions(body);
}
/**
* 用户端获取单个问题详情
*
* @param questionId 问题ID必须为正整数
* @return 包含问题详情及关联笔记的视图对象的响应
*/
@GetMapping("/questions/{questionId}")
public ApiResponse<QuestionNoteVO> userGetQuestion(@Min(value = 1, message = "questionId 必须为正整数")
@PathVariable Integer questionId) {
return questionService.userGetQuestion(questionId);
}
/**
* 管理端获取问题列表
*
* @param queryParams 查询参数用于过滤问题列表如关键词时间范围等
* @return 包含所有问题的视图对象列表的响应
*/
@GetMapping("/admin/questions")
public ApiResponse<List<QuestionVO>> getQuestions(@Valid QuestionQueryParam queryParams) {
return questionService.getQuestions(queryParams);
}
/**
* 管理端创建新问题
*
* @param createQuestionBody 创建问题的请求体包含问题的标题内容等信息
* @return 包含新创建问题视图对象的响应
*/
@PostMapping("/admin/questions")
public ApiResponse<CreateQuestionVO> createQuestion(@Valid @RequestBody CreateQuestionBody createQuestionBody) {
return questionService.createQuestion(createQuestionBody);
}
/**
* 管理端更新问题
*
* @param questionId 问题ID必须为正整数
* @param updateQuestionBody 更新问题的请求体包含要更新的字段和值
* @return 空视图对象的响应表示更新操作成功
*/
@PatchMapping("/admin/questions/{questionId}")
public ApiResponse<EmptyVO> updateQuestion(@Min(value = 1, message = "questionId 必须为正整数")
@PathVariable Integer questionId,
@Valid @RequestBody UpdateQuestionBody updateQuestionBody) {
return questionService.updateQuestion(questionId, updateQuestionBody);
}
/**
* 管理端删除问题
*
* @param questionId 问题ID必须为正整数
* @return 空视图对象的响应表示删除操作成功
*/
@DeleteMapping("/admin/questions/{questionId}")
public ApiResponse<EmptyVO> deleteQuestion(@Min(value = 1, message = "questionId 必须为正整数")
@PathVariable Integer questionId) {
return questionService.deleteQuestion(questionId);
}
}

View File

@ -0,0 +1,90 @@
package com.kama.notes.controller;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.model.base.EmptyVO;
import com.kama.notes.model.dto.questionList.CreateQuestionListBody;
import com.kama.notes.model.dto.questionList.UpdateQuestionListBody;
import com.kama.notes.model.entity.QuestionList;
import com.kama.notes.model.vo.questionList.CreateQuestionListVO;
import com.kama.notes.service.QuestionListService;
@RestController
@RequestMapping("/api")
public class QuestionListController {
@Autowired
private QuestionListService questionListService;
/**
* 获取题单
*
* @return 包含题单列表的响应
*/
@GetMapping("/admin/questionlists/{questionListId}")
public ApiResponse<QuestionList> getQuestionList(@Min(value = 1, message = "questionListId 必须为正整数")
@PathVariable Integer questionListId) {
return questionListService.getQuestionList(questionListId);
}
/**
* 获取题单列表
*
* @return 包含题单列表的响应
*/
@GetMapping("/admin/questionlists")
public ApiResponse<List<QuestionList>> getQuestionLists() {
return questionListService.getQuestionLists();
}
/**
* 创建新的题单
*
* @param body 包含题单创建信息的请求体
* @return 包含创建成功的题单信息的响应
*/
@PostMapping("/admin/questionlists")
public ApiResponse<CreateQuestionListVO> createQuestionList(@Valid @RequestBody CreateQuestionListBody body) {
return questionListService.createQuestionList(body);
}
/**
* 删除指定的题单
*
* @param questionListId 要删除的题单ID必须为正整数
* @return 包含删除操作结果的响应
*/
@DeleteMapping("/admin/questionlists/{questionListId}")
public ApiResponse<EmptyVO> deleteQuestionList(@Min(value = 1, message = "questionListId 必须为正整数")
@PathVariable Integer questionListId) {
return questionListService.deleteQuestionList(questionListId);
}
/**
* 更新指定的题单信息
*
* @param questionListId 要更新的题单ID必须为正整数
* @param body 包含更新信息的请求体
* @return 包含更新操作结果的响应
*/
@PatchMapping("/admin/questionlists/{questionListId}")
public ApiResponse<EmptyVO> updateQuestionList(@Min(value = 1, message = "questionListId 必须为正整数")
@PathVariable Integer questionListId,
@Valid @RequestBody UpdateQuestionListBody body) {
return questionListService.updateQuestionList(questionListId, body);
}
}

View File

@ -0,0 +1,94 @@
package com.kama.notes.controller;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.model.base.EmptyVO;
import com.kama.notes.model.dto.questionListItem.CreateQuestionListItemBody;
import com.kama.notes.model.dto.questionListItem.QuestionListItemQueryParams;
import com.kama.notes.model.dto.questionListItem.SortQuestionListItemBody;
import com.kama.notes.model.vo.questionListItem.CreateQuestionListItemVO;
import com.kama.notes.model.vo.questionListItem.QuestionListItemUserVO;
import com.kama.notes.model.vo.questionListItem.QuestionListItemVO;
import com.kama.notes.service.QuestionListItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import java.util.List;
@RestController
@RequestMapping("/api")
public class QuestionListItemController {
@Autowired
private QuestionListItemService questionListItemService;
/**
* 获取指定题单中的题单项列表用户端
*
* @param queryParams 查询参数
* @return 包含题单项列表的响应
*/
@GetMapping("/questionlist-items")
public ApiResponse<List<QuestionListItemUserVO>> userGetQuestionListItems(
@Valid QuestionListItemQueryParams queryParams) {
return questionListItemService.userGetQuestionListItems(queryParams);
}
/**
* 获取指定题单中的题单项列表管理端
*
* @param questionListId 题单ID可选参数若提供则获取指定题单的题单项
* @return 包含题单项列表的响应
*/
@GetMapping("/admin/questionlist-items/{questionListId}")
public ApiResponse<List<QuestionListItemVO>> getQuestionListItems(
@Min(value = 1, message = "questionListId 必须为正整数")
@PathVariable Integer questionListId) {
return questionListItemService.getQuestionListItems(questionListId);
}
/**
* 创建新的题单项
*
* @param body 包含题单项创建信息的请求体
* @return 包含创建成功的题单项信息的响应
*/
@PostMapping("/admin/questionlist-items")
public ApiResponse<CreateQuestionListItemVO> createQuestionListItem(
@Valid
@RequestBody
CreateQuestionListItemBody body) {
return questionListItemService.createQuestionListItem(body);
}
/**
* 删除指定的题单项
*
* @param questionListId 题单ID必须为正整数
* @param questionId 题目ID必须为正整数
* @return 包含删除操作结果的响应
*/
@DeleteMapping("/admin/questionlist-items/{questionListId}/{questionId}")
public ApiResponse<EmptyVO> deleteQuestionListItem(
@Min(value = 1, message = "questionListId 必须为正整数")
@PathVariable Integer questionListId,
@Min(value = 1, message = "questionId 必须为正整数")
@PathVariable Integer questionId) {
return questionListItemService.deleteQuestionListItem(questionListId, questionId);
}
/**
* 更新题单项的排序
*
* @param body 包含题单项排序信息的请求体
* @return 包含更新操作结果的响应
*/
@PatchMapping("/admin/questionlist-items/sort")
public ApiResponse<EmptyVO> sortQuestionListItem(
@Valid
@RequestBody
SortQuestionListItemBody body) {
return questionListItemService.sortQuestionListItem(body);
}
}

View File

@ -0,0 +1,44 @@
package com.kama.notes.controller;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.model.entity.Note;
import com.kama.notes.model.entity.User;
import com.kama.notes.service.SearchService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.Min;
import java.util.List;
@RestController
@RequestMapping("/api/search")
@RequiredArgsConstructor
public class SearchController {
private final SearchService searchService;
@GetMapping("/notes")
public ApiResponse<List<Note>> searchNotes(
@RequestParam String keyword,
@RequestParam(defaultValue = "1") @Min(1) Integer page,
@RequestParam(defaultValue = "20") @Min(1) Integer pageSize) {
return searchService.searchNotes(keyword, page, pageSize);
}
@GetMapping("/users")
public ApiResponse<List<User>> searchUsers(
@RequestParam String keyword,
@RequestParam(defaultValue = "1") @Min(1) Integer page,
@RequestParam(defaultValue = "20") @Min(1) Integer pageSize) {
return searchService.searchUsers(keyword, page, pageSize);
}
@GetMapping("/notes/tag")
public ApiResponse<List<Note>> searchNotesByTag(
@RequestParam String keyword,
@RequestParam String tag,
@RequestParam(defaultValue = "1") @Min(1) Integer page,
@RequestParam(defaultValue = "20") @Min(1) Integer pageSize) {
return searchService.searchNotesByTag(keyword, tag, page, pageSize);
}
}

View File

@ -0,0 +1,28 @@
package com.kama.notes.controller;
import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.model.dto.statistic.StatisticQueryParam;
import com.kama.notes.model.entity.Statistic;
import com.kama.notes.service.StatisticService;
@RestController
@RequestMapping("/api")
public class StatisticController {
@Autowired
StatisticService statisticService;
@GetMapping("/statistic")
public ApiResponse<List<Statistic>> getStatistic(@Valid StatisticQueryParam queryParam) {
return statisticService.getStatistic(queryParam);
}
}

View File

@ -0,0 +1,29 @@
package com.kama.notes.controller;
import com.kama.notes.scope.RequestScopeData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class TestController {
@Autowired
private RequestScopeData requestScopeData;
@GetMapping("/hello")
public String hello() {
System.out.println("get data in /test/hello");
System.out.println(requestScopeData.getUserId());
System.out.println(requestScopeData.getToken());
return "Hello World!";
}
@GetMapping("/exception")
public String exception() {
throw new RuntimeException("test exception");
}
}

View File

@ -0,0 +1,31 @@
package com.kama.notes.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.model.vo.upload.ImageVO;
import com.kama.notes.service.UploadService;
/**
* 文件上传控制器
*/
@RestController
@RequestMapping("/api")
public class UploadController {
@Autowired
private UploadService uploadService;
/**
* 上传图片
*/
@PostMapping("/upload/image")
public ApiResponse<ImageVO> uploadImage(@RequestParam("file") MultipartFile file) {
return uploadService.uploadImage(file);
}
}

View File

@ -0,0 +1,137 @@
package com.kama.notes.controller;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.Pattern;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.kama.notes.model.base.ApiResponse;
import com.kama.notes.model.dto.user.LoginRequest;
import com.kama.notes.model.dto.user.RegisterRequest;
import com.kama.notes.model.dto.user.UpdateUserRequest;
import com.kama.notes.model.dto.user.UserQueryParam;
import com.kama.notes.model.entity.User;
import com.kama.notes.model.vo.user.AvatarVO;
import com.kama.notes.model.vo.user.LoginUserVO;
import com.kama.notes.model.vo.user.RegisterVO;
import com.kama.notes.model.vo.user.UserVO;
import com.kama.notes.service.UserService;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequestMapping("/api")
public class UserController {
// 自动注入UserService以使用用户相关服务
@Autowired
private UserService userService;
/**
* 用户注册接口
* 处理用户注册请求验证请求体并调用 userService 进行注册
*
* @param request 用户注册请求对象包含用户注册所需信息
* @return 返回注册结果包括用户信息等
*/
@PostMapping("/users")
public ApiResponse<RegisterVO> register(
@Valid
@RequestBody
RegisterRequest request) {
return userService.register(request);
}
/**
* 用户登录接口
* 处理用户登录请求验证请求体并调用userService进行登录
*
* @param request 用户登录请求对象包含用户登录所需信息
* @return 返回登录结果包括用户信息和认证令牌等
*/
@PostMapping("/users/login")
public ApiResponse<LoginUserVO> login(
@Valid
@RequestBody
LoginRequest request) {
return userService.login(request);
}
/**
* 自动登录接口
* 当用户已登录并请求自动登录时调用userService获取当前用户信息
*
* @return 返回当前用户信息
*/
@PostMapping("/users/whoami")
public ApiResponse<LoginUserVO> whoami() {
return userService.whoami();
}
/**
* 查询用户信息接口
* 根据用户ID查询用户信息验证ID格式并调用userService获取用户详情
*
* @param userId 用户ID需为数字格式
* @return 返回指定用户的详细信息
*/
@GetMapping("/users/{userId}")
public ApiResponse<UserVO> getUserInfo(
@PathVariable
@Pattern(regexp = "\\d+", message = "ID 格式错误")
Long userId) {
return userService.getUserInfo(userId);
}
/**
* 更新用户信息接口
* 处理更新用户信息请求验证请求体并调用userService更新用户详情
*
* @param request 更新用户请求对象包含需要更新的用户信息
* @return 返回更新后的用户信息
*/
@PatchMapping("/users/me")
public ApiResponse<LoginUserVO> updateUserInfo(
@Valid
@RequestBody
UpdateUserRequest request) {
return userService.updateUserInfo(request);
}
/**
* 上传用户头像接口
*
* @param file 头像文件
* @return 返回上传结果包括头像URL等
*/
@PostMapping("/users/avatar")
public ApiResponse<AvatarVO> uploadAvatar(
@RequestParam("file") MultipartFile file) {
return userService.uploadAvatar(file);
}
/**
* 管理员获取用户信息列表的接口
* 该接口允许管理员查询系统的用户列表支持分页和条件查询
*
* @param queryParam 查询参数对象封装了用户查询条件和分页信息通过验证确保参数有效性
* @return 返回一个包含用户列表的ApiResponse对象响应中包含用户数据
*/
@GetMapping("/admin/users")
public ApiResponse<List<User>> adminGetUser(
@Valid UserQueryParam queryParam) {
return userService.getUserList(queryParam);
}
}

View File

@ -0,0 +1,36 @@
package com.kama.notes.event;
import com.kama.notes.model.vo.message.MessageVO;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
/**
* 消息事件
* 用于系统内部的消息传递
*/
@Getter
public class MessageEvent extends ApplicationEvent {
private final MessageVO message;
private final Long receiverId;
private final String eventType;
public MessageEvent(Object source, MessageVO message, Long receiverId, String eventType) {
super(source);
this.message = message;
this.receiverId = receiverId;
this.eventType = eventType;
}
public static MessageEvent createCommentEvent(Object source, MessageVO message, Long receiverId) {
return new MessageEvent(source, message, receiverId, "COMMENT");
}
public static MessageEvent createLikeEvent(Object source, MessageVO message, Long receiverId) {
return new MessageEvent(source, message, receiverId, "LIKE");
}
public static MessageEvent createSystemEvent(Object source, MessageVO message, Long receiverId) {
return new MessageEvent(source, message, receiverId, "SYSTEM");
}
}

View File

@ -0,0 +1,38 @@
package com.kama.notes.exception;
import com.kama.notes.model.base.ApiResponse;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class ParamExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResponse<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return ApiResponse.error(HttpStatus.BAD_REQUEST.value(), "Validation Failed", errors);
}
@ExceptionHandler(ConstraintViolationException.class)
public ApiResponse<Map<String, String>> handleConstraintViolationExceptions(ConstraintViolationException ex) {
Map<String, String> errors = new HashMap<>();
ex.getConstraintViolations().forEach(violation ->
errors.put(violation.getPropertyPath().toString(), violation.getMessage())
);
return ApiResponse.error(HttpStatus.BAD_REQUEST.value(), "Validation Failed", errors);
}
@ExceptionHandler(Exception.class)
public ApiResponse<String> handleException(Exception ex) {
return ApiResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Internal Server Error", ex.getMessage());
}
}

View File

@ -0,0 +1,33 @@
package com.kama.notes.filter;
import org.slf4j.MDC;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class TraceIdFilter implements Filter {
private static final String TRACE_ID_KEY = "traceId";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
chain.doFilter(request, response);
} finally {
// 清理 MDC 中的 traceId
MDC.remove(TRACE_ID_KEY);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}

View File

@ -0,0 +1,49 @@
package com.kama.notes.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import com.kama.notes.scope.RequestScopeData;
import com.kama.notes.utils.JwtUtil;
@Component
public class TokenInterceptor implements HandlerInterceptor
{
@Autowired
private RequestScopeData requestScopeData;
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 对于每个请求进行拦截获取请求头中的 token
// 然后对 token 进行处理并将 token 携带的信息存储到在请求周期中全局存在的 requestScopeData
String token = request.getHeader("Authorization");
if (token == null) {
requestScopeData.setLogin(false);
requestScopeData.setToken(null);
requestScopeData.setUserId(null);
return true;
}
token = token.replace("Bearer ", "");
if (jwtUtil.validateToken(token)) {
Long userId = jwtUtil.getUserIdFromToken(token);
requestScopeData.setUserId(userId);
requestScopeData.setToken(token);
requestScopeData.setLogin(true);
} else {
requestScopeData.setLogin(false);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}

View File

@ -0,0 +1,52 @@
package com.kama.notes.listener;
import com.kama.notes.event.MessageEvent;
import com.kama.notes.service.MessageService;
import com.kama.notes.websocket.MessageWebSocketHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 消息事件监听器
* 用于处理系统内的消息事件
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MessageEventListener {
private final MessageService messageService;
private final MessageWebSocketHandler messageWebSocketHandler;
/**
* 异步处理消息事件
*/
@Async
@EventListener
public void handleMessageEvent(MessageEvent event) {
try {
log.info("收到{}类型的消息事件, 接收者: {}", event.getEventType(), event.getReceiverId());
// 1. 保存消息到数据库
messageService.createMessage(
event.getReceiverId(),
event.getMessage().getSender().getUserId(),
event.getEventType(),
event.getMessage().getTargetId(),
event.getMessage().getContent()
);
// 2. 如果用户在线通过WebSocket推送消息
if (messageWebSocketHandler.isUserOnline(event.getReceiverId())) {
messageWebSocketHandler.sendMessageToUser(event.getReceiverId(), event.getMessage());
}
} catch (Exception e) {
log.error("处理消息事件时发生错误", e);
// 这里可以添加重试逻辑或者将失败的消息记录到特定的队列中
}
}
}

View File

@ -0,0 +1,84 @@
package com.kama.notes.mapper;
import com.kama.notes.model.entity.Category;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface CategoryMapper {
/**
* 此方法负责接收一个Category对象并执行插入操作具体操作如将分类信息保存到数据库
* 主要用于添加新的分类信息
*
* @param category 要插入的分类对象包含分类相关信息
* @return 返回插入操作的结果表示插入的记录数
*/
int insert(Category category);
/**
* 批量插入分类数据
*
* @param categories 分类对象列表包含多个Category实例
* @return 插入操作影响的行数
*/
int insertBatch(@Param("categories") List<Category> categories);
/**
* 获取所有分类
*
* @return 分类列表
*/
List<Category> categoryList();
/**
* 根据分类ID查找分类信息
*
* @param categoryId 分类ID用于唯一标识一个分类
* @return 返回找到的Category对象如果不存在则返回null
*/
Category findById(Integer categoryId);
/**
* 批量通过分类ID查找分类信息
* 此方法允许一次性查询多个分类的信息通过提供一个分类ID列表作为参数
*
* @param categoryIds 分类ID列表用于指定需要查找的分类
* @return 匹配给定ID的分类对象列表如果没有找到匹配的分类则返回空列表
*/
List<Category> findByIdBatch(@Param("categoryIds") List<Integer> categoryIds);
/**
* 根据分类ID或父分类ID查找分类信息
* 此方法旨在处理分类信息的查询通过分类ID或父分类ID来过滤并返回匹配的分类对象列表
*
* @param categoryId 分类ID或父分类ID用于查找分类的依据
* @return 返回一个Category对象的列表这些对象的分类ID或父分类ID与给定的categoryId匹配
*/
List<Category> findByIdOrParentId(Integer categoryId);
/**
* 删除分类以及子分类
*
* @param categoryId 分类 ID
*/
int deleteById(Integer categoryId);
/**
* 批量删除分类
* 通过多个分类ID一次性删除对应的分类信息
*
* @param categoryIds 包含多个分类ID的列表用于指定需要删除的分类
* @return 返回删除操作的影响行数表示成功删除的分类数量
*/
int deleteByIdBatch(@Param("categoryIds") List<Integer> categoryIds);
/**
* 更新分类信息
*
* @param category 要更新的分类对象包含新的分类信息
* @return 返回更新操作的影响行数表示成功更新的分类数量
*/
int update(Category category);
}

View File

@ -0,0 +1,71 @@
package com.kama.notes.mapper;
import com.kama.notes.model.entity.Collection;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface CollectionMapper {
/**
* 根据ID查询收藏夹
*
* @param collectionId 收藏夹的ID
* @return 返回查询到的收藏夹对象
*/
Collection findById(@Param("collectionId") Integer collectionId);
/**
* 根据创建者 ID 查询收藏夹
*
* @param creatorId 创建者 ID
* @return 返回查询到的收藏夹列表
*/
List<Collection> findByCreatorId(@Param("creatorId") Long creatorId);
/**
* 根据收藏夹 ID 和创建者 ID 查询收藏夹
*
* @param collectionId 收藏夹 ID
* @param creatorId 创建者 ID
* @return 返回查询到的收藏夹对象
*/
Collection findByIdAndCreatorId(@Param("collectionId") Integer collectionId, @Param("creatorId") Long creatorId);
/**
* 根据收藏夹 ID创建者 ID 和笔记 ID 查询收藏夹
*
* @param collectionId 收藏夹 ID
* @param creatorId 创建者 ID
* @param noteId 笔记 ID
* @return 返回查询到的收藏夹对象
*/
int countByCreatorIdAndNoteId(
@Param("creatorId") Long creatorId,
@Param("noteId") Integer noteId);
/**
* 创建收藏夹
*
* @param collection 要创建的收藏夹对象
* @return 返回插入操作的影响行数
*/
int insert(Collection collection);
/**
* 更新收藏夹
*
* @param collection 要更新的收藏夹对象
* @return 返回更新操作的影响行数
*/
int update(Collection collection);
/**
* 删除收藏夹
*
* @param collectionId 要删除的收藏夹的ID
* @return 返回删除操作的影响行数
*/
int deleteById(@Param("collectionId") Integer collectionId);
}

View File

@ -0,0 +1,61 @@
package com.kama.notes.mapper;
import com.kama.notes.model.entity.CollectionNote;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Set;
@Mapper
public interface CollectionNoteMapper {
/**
* 查询用户收藏的笔记 ID 列表
*
* @param userId 用户 ID
* @param noteIds 笔记 ID 列表
* @return 用户收藏的笔记 ID 列表
*/
List<Integer> findUserCollectedNoteIds(
@Param("userId") Long userId,
@Param("noteIds") List<Integer> noteIds
);
/**
* 筛选出所给的收藏夹 ID 列表中收藏了 noteId 对应的 note 的记录
*
* @param noteId 笔记 ID
* @param collectionIds 收藏夹 ID 列表
* @return 筛选结果
*/
Set<Integer> filterCollectionIdsByNoteId(
@Param("noteId") Integer noteId,
@Param("collectionIds") List<Integer> collectionIds);
/**
* 插入记录
*
* @param collectionNote 收藏笔记记录
* @return 插入记录数
*/
int insert(CollectionNote collectionNote);
/**
* 根据 collectionId 删除记录
*
* @param collectionId 收藏夹 ID
* @return 删除记录数
*/
int deleteByCollectionId(@Param("collectionId") Integer collectionId);
/**
* 根据 collectionId noteId 删除记录
*
* @param collectionId 收藏夹 ID
* @param noteId 笔记 ID
* @return 删除记录数
*/
int deleteByCollectionIdAndNoteId(
@Param("collectionId") Integer collectionId,
@Param("noteId") Integer noteId);
}

View File

@ -0,0 +1,44 @@
package com.kama.notes.mapper;
import com.kama.notes.model.entity.CommentLike;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Set;
/**
* 评论点赞Mapper接口
*/
@Mapper
public interface CommentLikeMapper {
/**
* 插入评论点赞
*
* @param commentLike 评论点赞实体
*/
void insert(CommentLike commentLike);
/**
* 删除评论点赞
*
* @param commentId 评论ID
* @param userId 用户ID
*/
void delete(@Param("commentId") Integer commentId, @Param("userId") Long userId);
/**
* 查询用户点赞的评论ID列表
*
* @param userId 用户ID
* @param commentIds 评论ID列表
* @return 用户点赞的评论ID集合
*/
Set<Integer> findUserLikedCommentIds(@Param("userId") Long userId,
@Param("commentIds") List<Integer> commentIds);
@Select("SELECT COUNT(*) > 0 FROM comment_like " +
"WHERE user_id = #{userId} AND comment_id = #{commentId}")
Boolean checkIsLiked(@Param("userId") Long userId, @Param("commentId") Integer commentId);
}

View File

@ -0,0 +1,91 @@
package com.kama.notes.mapper;
import com.kama.notes.model.dto.comment.CommentQueryParams;
import com.kama.notes.model.entity.Comment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 评论Mapper接口
*/
@Mapper
public interface CommentMapper {
/**
* 插入评论
*
* @param comment 评论实体
*/
void insert(Comment comment);
/**
* 更新评论
*
* @param comment 评论实体
*/
void update(Comment comment);
/**
* 删除评论
*
* @param commentId 评论ID
*/
void deleteById(Integer commentId);
/**
* 根据ID查询评论
*
* @param commentId 评论ID
* @return 评论实体
*/
Comment findById(Integer commentId);
/**
* 查询评论列表
*
* @param params 查询参数
* @param pageSize 每页大小
* @param offset 偏移量
* @return 评论列表
*/
List<Comment> findByQueryParam(@Param("params") CommentQueryParams params,
@Param("pageSize") Integer pageSize,
@Param("offset") Integer offset);
/**
* 统计评论数量
*
* @param params 查询参数
* @return 评论数量
*/
int countByQueryParam(@Param("params") CommentQueryParams params);
/**
* 增加评论点赞数
*
* @param commentId 评论ID
*/
void incrementLikeCount(Integer commentId);
/**
* 减少评论点赞数
*
* @param commentId 评论ID
*/
void decrementLikeCount(Integer commentId);
/**
* 增加评论回复数
*
* @param commentId 评论ID
*/
void incrementReplyCount(Integer commentId);
/**
* 减少评论回复数
*
* @param commentId 评论ID
*/
void decrementReplyCount(Integer commentId);
}

View File

@ -0,0 +1,23 @@
package com.kama.notes.mapper;
import com.kama.notes.model.entity.EmailVerifyCode;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface EmailVerifyCodeMapper {
/**
* 插入验证码记录
*/
int insert(EmailVerifyCode code);
/**
* 查询最新的有效验证码
*/
EmailVerifyCode findLatestValidCode(@Param("email") String email, @Param("type") String type);
/**
* 标记验证码为已使用
*/
int markAsUsed(@Param("id") Long id);
}

View File

@ -0,0 +1,84 @@
package com.kama.notes.mapper;
import com.kama.notes.model.dto.message.MessageQueryParams;
import com.kama.notes.model.entity.Message;
import com.kama.notes.model.vo.message.UnreadCountByType;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 消息Mapper接口
*/
@Mapper
public interface MessageMapper {
/**
* 插入消息
*
* @param message 消息实体
* @return 影响行数
*/
int insert(Message message);
/**
* 根据参数查询消息列表
*
* @param userId 用户ID
* @param params 查询参数
* @param offset 偏移量
* @return 消息列表
*/
List<Message> selectByParams(@Param("userId") Long userId, @Param("params") MessageQueryParams params, @Param("offset") int offset);
/**
* 统计符合条件的消息数量
*
* @param userId 用户ID
* @param params 查询参数
* @return 消息数量
*/
int countByParams(@Param("userId") Long userId, @Param("params") MessageQueryParams params);
/**
* 标记消息为已读
*
* @param messageId 消息ID
* @param userId 用户ID
* @return 影响行数
*/
int markAsRead(@Param("messageId") Integer messageId, @Param("userId") Long userId);
/**
* 标记所有消息为已读
*
* @param userId 用户ID
* @return 影响行数
*/
int markAllAsRead(@Param("userId") Long userId);
/**
* 删除消息
*
* @param messageId 消息ID
* @param userId 用户ID
* @return 影响行数
*/
int deleteMessage(@Param("messageId") Integer messageId, @Param("userId") Long userId);
/**
* 统计未读消息数量
*
* @param userId 用户ID
* @return 未读消息数量
*/
int countUnread(@Param("userId") Long userId);
/**
* 按类型统计未读消息数量
*
* @param userId 用户ID
* @return 各类型未读消息数量
*/
List<UnreadCountByType> countUnreadByType(@Param("userId") Long userId);
}

View File

@ -0,0 +1,47 @@
package com.kama.notes.mapper;
import com.kama.notes.model.entity.NoteCollect;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 笔记收藏Mapper接口
*/
@Mapper
public interface NoteCollectMapper {
/**
* 插入收藏记录
*
* @param noteCollect 收藏记录
* @return 影响的行数
*/
int insert(NoteCollect noteCollect);
/**
* 删除收藏记录
*
* @param noteId 笔记ID
* @param userId 用户ID
* @return 影响的行数
*/
int delete(@Param("noteId") Integer noteId, @Param("userId") Long userId);
/**
* 查找收藏记录
*
* @param noteId 笔记ID
* @param userId 用户ID
* @return 收藏记录
*/
NoteCollect findByNoteIdAndUserId(@Param("noteId") Integer noteId, @Param("userId") Long userId);
/**
* 获取用户收藏的笔记ID列表
*
* @param userId 用户ID
* @return 笔记ID列表
*/
List<Integer> findNoteIdsByUserId(@Param("userId") Long userId);
}

View File

@ -0,0 +1,31 @@
package com.kama.notes.mapper;
import com.kama.notes.model.entity.NoteComment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface NoteCommentMapper {
/**
* 插入评论
*/
void insert(NoteComment comment);
/**
* 更新评论
*/
void update(NoteComment comment);
/**
* 根据ID查询评论
*/
NoteComment findById(@Param("id") Integer id);
/**
* 查询笔记的评论列表
*/
List<NoteComment> findByNoteId(@Param("noteId") Integer noteId);
}

View File

@ -0,0 +1,52 @@
package com.kama.notes.mapper;
import com.kama.notes.model.entity.NoteLike;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface NoteLikeMapper {
/**
* 插入一个点赞记录
*
* @param noteLike 要插入的点赞记录对象包含了用户ID和笔记ID等信息
* @return 返回影响的行数表示插入操作是否成功
*/
int insert(NoteLike noteLike);
/**
* 删除一个点赞记录对象
*
* @param noteLike 要删除的点赞记录通常包含用户ID和笔记ID以定位数据库中的记录
* @return 返回影响的行数表示删除操作是否成功
*/
int delete(NoteLike noteLike);
/**
* 根据用户ID和笔记ID列表查找用户点赞过笔记ID列表
* 此方法用于过滤给定的笔记ID列表仅返回该用户标记为点赞过笔记ID
*
* @param userId 用户ID用于标识用户
* @param noteIds 笔记ID列表待过滤的笔记ID集合
* @return 用户点赞过笔记ID列表
*/
List<Integer> findUserLikedNoteIds(
@Param("userId") Long userId,
@Param("noteIds") List<Integer> noteIds
);
/**
* 根据用户ID和笔记ID查找特定的笔记点赞记录
* 此方法用于验证用户是否点赞特定的笔记通过用户ID和笔记ID的组合来查询
*
* @param userId 用户ID用于标识用户
* @param noteId 笔记ID用于标识笔记
* @return 笔记点赞记录如果找到则返回否则返回null
*/
NoteLike findByUserIdAndNoteId(
@Param("userId") Long userId,
@Param("noteId") Integer noteId
);
}

View File

@ -0,0 +1,207 @@
package com.kama.notes.mapper;
import com.kama.notes.model.dto.note.NoteQueryParams;
import com.kama.notes.model.entity.Note;
import com.kama.notes.model.vo.note.NoteHeatMapItem;
import com.kama.notes.model.vo.note.NoteRankListItem;
import com.kama.notes.model.vo.note.Top3Count;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Set;
@Mapper
public interface NoteMapper {
/**
* 查询笔记的总数
*
* @param params 查询参数用于过滤笔记
* @return 笔记的总数量
*/
int countNotes(@Param("params") NoteQueryParams params);
/**
* 根据笔记ID查询笔记
*
* @param noteId 笔记ID用于定位特定笔记
* @return 返回对应的笔记对象如果找不到则返回 null
*/
Note findById(@Param("noteId") Integer noteId);
/**
* 根据查询参数获取笔记列表
*
* @param params 查询参数用于过滤笔记
* @param offset 偏移量用于分页
* @param limit 每页大小用于分页
* @return 笔记列表返回符合查询条件的笔记
*/
List<Note> findByQueryParams(@Param("params") NoteQueryParams params,
@Param("offset") int offset,
@Param("limit") int limit);
/**
* 根据用户ID和问题ID查询笔记
*
* @param authorId 用户ID用于标识特定用户
* @param questionId 问题ID用于标识特定问题
* @return 返回匹配的笔记对象如果找不到匹配的笔记则返回 null
*/
Note findByAuthorIdAndQuestionId(@Param("authorId") Long authorId,
@Param("questionId") Integer questionId);
/**
* 根据用户ID查询笔记列表
* @param authorId 用户ID
* @return 用户创建的笔记列表
*/
List<Note> findByAuthorId(@Param("authorId") Long authorId);
/**
* 根据用户ID和问题ID列表过滤出用户已完成的问题ID列表
*
* @param authorId 用户ID用于标识特定用户
* @param questionIds 问题ID列表表示待查询的问题范围
* @return 用户已完成的问题ID列表如果用户未完成任何问题则返回空集合
*/
Set<Integer> filterFinishedQuestionIdsByUser(@Param("authorId") Long authorId,
@Param("questionIds") List<Integer> questionIds);
/**
* 插入一条新的笔记
*
* @param note 笔记对象包含要插入的笔记信息
* @return 插入成功
*/
int insert(Note note);
/**
* 更新笔记信息
*
* @param note 笔记对象包含要更新的笔记信息
* @return 更新成功记录数
*/
int update(Note note);
/**
* 点赞笔记
*
* @param noteId 笔记ID用于标识要点赞的笔记
* @return 点赞成功记录数
*/
int likeNote(@Param("noteId") Integer noteId);
/**
* 取消点赞笔记
*
* @param noteId 笔记ID用于标识要取消点赞的笔记
* @return 取消点赞成功记录数
*/
int unlikeNote(@Param("noteId") Integer noteId);
/**
* 收藏笔记
*
* @param noteId 笔记ID用于标识要收藏的笔记
* @return 收藏结果
*/
int collectNote(@Param("noteId") Integer noteId);
/**
* 取消收藏笔记
*
* @param noteId 笔记ID用于标识要取消收藏的笔记
* @return 取消收藏结果
*/
int unCollectNote(@Param("noteId") Integer noteId);
/**
* 根据笔记ID删除笔记
*
* @param noteId 笔记ID用于标识要删除的笔记
* @return 删除成功记录数
*/
int deleteById(@Param("noteId") Integer noteId);
/**
* 每日笔记提交数排行榜
*
* @return 排行榜数组
*/
List<NoteRankListItem> submitNoteRank();
/**
* 提交热力图
*
* @return 用户提交热力图信息
*/
List<NoteHeatMapItem> submitNoteHeatMap(@Param("authorId") Long authorId);
/**
* 用户提交 top3Count
*
* @return 用户提交 top3Count
*/
Top3Count submitNoteTop3Count(@Param("authorId") Long authorId);
/**
* 当日笔记数
*
* @return 当日笔记数
*/
int getTodayNoteCount();
/**
* 当日提交笔记人数
* @return 当日提交笔记人数
*/
int getTodaySubmitNoteUserCount();
/**
* 笔记总数
* @return 笔记总数
*/
int getTotalNoteCount();
/**
* 增加笔记评论数
*
* @param noteId 笔记ID
*/
void incrementCommentCount(@Param("noteId") Integer noteId);
/**
* 减少笔记评论数
*
* @param noteId 笔记ID
*/
void decrementCommentCount(@Param("noteId") Integer noteId);
/**
* 搜索笔记
*
* @param keyword 关键词
* @param limit 限制数量
* @param offset 偏移量
* @return 笔记列表
*/
List<Note> searchNotes(@Param("keyword") String keyword,
@Param("limit") int limit,
@Param("offset") int offset);
/**
* 根据标签搜索笔记
*
* @param keyword 关键词
* @param tag 标签
* @param limit 限制数量
* @param offset 偏移量
* @return 笔记列表
*/
List<Note> searchNotesByTag(@Param("keyword") String keyword,
@Param("tag") String tag,
@Param("limit") int limit,
@Param("offset") int offset);
}

View File

@ -0,0 +1,79 @@
package com.kama.notes.mapper;
import com.kama.notes.model.entity.QuestionListItem;
import com.kama.notes.model.vo.questionListItem.QuestionListItemVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface QuestionListItemMapper {
/**
* 插入一个题单项
*
* @param questionListItem 题单项对象包含需要插入的题单项的信息
* @return 影响的行数表示插入操作是否成功
*/
int insert(QuestionListItem questionListItem);
/**
* 根据题单ID查找题单项
*
* @param questionListId 题单的唯一标识符
* @return 返回一个包含题单项的列表如果找不到对应的项则返回空列表
*/
List<QuestionListItemVO> findByQuestionListId(@Param("questionListId") Integer questionListId);
/**
* 根据题单ID查找题单项的数量
*
* @param questionListId 题单的ID用于标识要查找的题单项数量
* @return 返回题单项的数量
*/
int countByQuestionListId(@Param("questionListId") Integer questionListId);
/**
* 根据题单ID查找题单项分页
*
* @param questionListId 题单的ID用于标识要查找的题单项
* @param limit 每页显示的记录数
* @param offset 从第几条记录开始查询
*/
List<QuestionListItemVO> findByQuestionListIdPage(@Param("questionListId") Integer questionListId,
@Param("limit") Integer limit,
@Param("offset") Integer offset);
/**
* 根据题单ID删除题单项
*
* @param questionListId 题单的ID用于标识要删除的题单项
* @return 影响的行数表示删除操作是否成功
*/
int deleteByQuestionListId(Integer questionListId);
/**
* 根据题单ID和题目ID删除题单项
*
* @param questionListId 题单的ID用于标识要删除的题单项
* @param questionId 题目的ID用于标识要删除的题单项
* @return 影响的行数表示删除操作是否成功
*/
int deleteByQuestionListIdAndQuestionId(@Param("questionListId") Integer questionListId, @Param("questionId") Integer questionId);
/**
* 根据题单ID获取下一个序号
*
* @param questionListId 题单的ID
* @return 返回下一个序号
*/
int nextRank(Integer questionListId);
/**
* 更新题单项的序号
*
* @param questionListItem 新顺序的题单项
* @return 影响的行数表示更新操作是否成功
*/
int updateQuestionRank(QuestionListItem questionListItem);
}

View File

@ -0,0 +1,49 @@
package com.kama.notes.mapper;
import com.kama.notes.model.entity.QuestionList;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface QuestionListMapper {
/**
* 插入一个题单
*
* @param questionList 要插入的题单对象
* @return 插入操作影响的行数
*/
int insert(QuestionList questionList);
/**
* 根据题单ID查找题单
*
* @param questionListId 题单的唯一标识符
* @return 返回找到的题单对象如果没有找到则返回 null
*/
QuestionList findById(@Param("questionListId") Integer questionListId);
/**
* 获取所有题单
*
* @return 返回所有题单的列表
*/
List<QuestionList> findAll();
/**
* 更新一个题单的信息
*
* @param questionList 要更新的题单对象包含需要更新的字段
* @return 更新操作影响的行数
*/
int update(QuestionList questionList);
/**
* 根据题单ID删除题单
*
* @param questionListId 题单的唯一标识符
* @return 删除操作影响的行数
*/
int deleteById(@Param("questionListId") Integer questionListId);
}

View File

@ -0,0 +1,101 @@
package com.kama.notes.mapper;
import com.kama.notes.model.dto.question.QuestionQueryParam;
import com.kama.notes.model.entity.Question;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface QuestionMapper {
/**
* 插入一个问题对象到数据库中
*
* @param question 要插入的问题对象包含问题的相关信息
* @return 插入成功返回1否则返回0
*/
int insert(Question question);
/**
* 根据问题ID查找问题
*
* @param questionId 问题的唯一标识符
* @return 返回找到的问题对象如果没有找到则返回null
*/
Question findById(@Param("questionId") Integer questionId);
/**
* 批量通过问题 ID查找问题
* 此方法允许一次性传入多个问题ID从而批量获取问题信息
*
* @param questionIds 一个问题ID的列表用于指定需要查找的问题
* @return 返回一个Question对象的列表每个对象包含一个问题的详细信息
*/
List<Question> findByIdBatch(@Param("questionIds") List<Integer> questionIds);
/**
* 根据查询参数获取问题列表
*
* @param queryParam 查询参数对象包含多种可能的查询条件
* @param offset 分页查询的起始位置
* @param limit 每页返回的最大记录数
* @return 匹配查询条件的问题列表
*/
List<Question> findByQueryParam(@Param("queryParam") QuestionQueryParam queryParam,
@Param("offset") int offset,
@Param("limit") int limit);
/**
* 根据关键字搜索问题
*
* @param keyword 关键字用于匹配问题标题或内容
* @return 匹配关键字的问题列表
*/
List<Question> findByKeyword(@Param("keyword") String keyword);
/**
* 更新问题
* @param question 问题对象包含要更新的问题信息
* @return 更新成功返回1否则返回0
*/
int update(@Param("question") Question question);
/**
* 更新问题的浏览次数
* @param questionId 问题ID
* @return 更新成功返回1否则返回0
*/
int incrementViewCount(@Param("questionId") Integer questionId);
/**
* 根据查询参数统计问题的数量
*
* @param queryParam 查询参数对象包含多个查询条件
* @return 满足查询条件的问题数量
*/
int countByQueryParam(@Param("queryParam") QuestionQueryParam queryParam);
/**
* 根据问题ID删除问题
*
* @param questionId 需要删除的问题的ID
*/
int deleteById(Integer questionId);
/**
* 根据分类ID删除相关记录
* 此方法旨在删除与给定分类ID 关联的实体
*
* @param categoryId 分类ID用于标识要删除的记录
*/
int deleteByCategoryId(Integer categoryId);
/**
* 批量删除指定分类ID的实体
* 通过分类ID列表来删除实体主要用于批量操作场景
*
* @param categoryIds 分类ID列表用于指定待删除实体的分类
*/
int deleteByCategoryIdBatch(@Param("categoryIds") List<Integer> categoryIds);
}

View File

@ -0,0 +1,31 @@
package com.kama.notes.mapper;
import com.kama.notes.model.entity.Statistic;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface StatisticMapper {
/**
* 添加统计数据
* @param statistic 统计数据
* @return 添加的记录数
*/
int insert(Statistic statistic);
/**
* 获取统计数据的 total
* @return total
*/
int countStatistic();
/**
* 查询统计数据
* @param limit 限制
* @param offset 偏移
* @return 统计数据
*/
List<Statistic> findByPage(@Param("limit") Integer limit, @Param("offset") Integer offset);
}

View File

@ -0,0 +1,139 @@
package com.kama.notes.mapper;
import com.kama.notes.model.dto.user.UserQueryParam;
import com.kama.notes.model.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* UserMapper接口定义了用户数据访问对象DAO的方法
*/
@Mapper
public interface UserMapper {
/**
* 插入新用户
*
* @param user 待插入的用户对象包含用户的所有信息
*/
int insert(User user);
/**
* 根据ID查找用户
*
* @param userId 用户ID用于查询用户信息
* @return 返回用户对象如果未找到则返回null
*/
User findById(@Param("userId") Long userId);
/**
* 根据 ID 数组批量查找用户
*
* @param userIds 用户ID列表用于批量查询用户信息
* @return 返回用户列表如果未找到任何用户则返回空列表
*/
List<User> findByIdBatch(@Param("userIds") List<Long> userIds);
/**
* 根据账号查找用户
*
* @param account 用户账号用于查询用户信息
* @return 返回用户对象如果未找到则返回null
*/
User findByAccount(@Param("account") String account);
/**
* 根据 OpenId 查找用户
*
* @param openId 用户的 OpenId用于查询用户信息
* @return 返回用户对象如果未找到则返回 null
*/
User findByOpenId(@Param("openId") String openId);
/**
* 根据 UnionId 查找用户
*
* @param unionId 用户的 UnionId用于查询用户信息
* @return 返回用户对象如果未找到则返回 null
*/
User findByUnionId(@Param("unionId") String unionId);
/**
* 根据查询参数查找用户列表
*
* @param queryParams 用户查询参数对象封装了查询用户时的各种筛选条件
* @return 符合查询条件的用户列表
*/
List<User> findByQueryParam(@Param("queryParams") UserQueryParam queryParams,
@Param("limit") Integer limit,
@Param("offset") Integer offset);
/**
* 根据查询参数统计用户数量
*
* @param queryParams 用户查询参数对象封装了查询条件
* @return 满足查询条件的用户数量
*/
int countByQueryParam(@Param("queryParams") UserQueryParam queryParams);
/**
* 更新用户信息
*
* @param user 待更新的用户对象包含用户的所有信息
*/
int update(User user);
/**
* 更新用户在线时间
*
* @param userId 用户ID用于标识需要更新在线时间的用户
*/
int updateLastLoginAt(@Param("userId") Long userId);
/**
* 绑定手机号
* @param userId 用户ID
* @param phone 手机号码
* @return 绑定结果
*/
int bindPhone(@Param("userId") Long userId, @Param("phone") String phone);
/**
* 获取今日登录人数
* @return 今日登录人数
*/
int getTodayLoginCount();
/**
* 今日注册人数
* @return 今日注册人数
*/
int getTodayRegisterCount();
/**
* 总注册人数
* @return 总注册人数
*/
int getTotalRegisterCount();
/**
* 根据邮箱查找用户
*
* @param email 用户邮箱用于查询用户信息
* @return 返回用户对象如果未找到则返回null
*/
User findByEmail(@Param("email") String email);
/**
* 搜索用户
*
* @param keyword 关键词
* @param limit 限制数量
* @param offset 偏移量
* @return 用户列表
*/
List<User> searchUsers(@Param("keyword") String keyword,
@Param("limit") int limit,
@Param("offset") int offset);
}

View File

@ -0,0 +1,95 @@
package com.kama.notes.model.base;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* API响应类
*
* @param <T> 响应数据类型
*/
@Data
@NoArgsConstructor
public class ApiResponse<T> {
/**
* 响应码
*/
private int code;
/**
* 响应消息
*/
private String message;
/**
* 响应数据
*/
private T data;
/**
* 构造函数
*
* @param code 响应码
* @param message 响应消息
* @param data 响应数据
*/
public ApiResponse(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
/**
* 创建成功响应
*
* @param data 响应数据
* @param <T> 响应数据类型
* @return API响应
*/
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(200);
response.setMessage("success");
response.setData(data);
return response;
}
/**
* 创建成功响应无数据
*
* @return API响应
*/
public static ApiResponse<EmptyVO> success() {
return success(new EmptyVO());
}
/**
* 创建错误响应
*
* @param code 错误码
* @param message 错误消息
* @return API响应
*/
public static <T> ApiResponse<T> error(int code, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(code);
response.setMessage(message);
return response;
}
/**
* 创建带数据的错误响应
*
* @param code 错误码
* @param message 错误消息
* @param data 错误数据
* @return API响应
*/
public static <T> ApiResponse<T> error(int code, String message, T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(code);
response.setMessage(message);
response.setData(data);
return response;
}
}

View File

@ -0,0 +1,7 @@
package com.kama.notes.model.base;
/**
* 空响应类用于表示无数据返回的情况
*/
public class EmptyVO {
}

View File

@ -0,0 +1,59 @@
package com.kama.notes.model.base;
import lombok.Data;
import java.util.List;
/**
* 分页数据模型
*
* @param <T> 数据类型
*/
@Data
public class PageVO<T> {
/**
* 当前页码
*/
private Integer page;
/**
* 每页大小
*/
private Integer pageSize;
/**
* 总记录数
*/
private Integer total;
/**
* 总页数
*/
private Integer totalPages;
/**
* 数据列表
*/
private List<T> list;
public PageVO(Integer page, Integer pageSize, Integer total, List<T> list) {
this.page = page;
this.pageSize = pageSize;
this.total = total;
this.list = list;
}
/**
* 创建分页结果
*
* @param list 数据列表
* @param page 当前页码
* @param pageSize 每页大小
* @param total 总记录数
* @return 分页结果
*/
public static <T> PageVO<T> of(List<T> list, Integer page, Integer pageSize, Integer total) {
PageVO<T> pageVO = new PageVO<>(page, pageSize, total, list);
pageVO.setTotalPages((total + pageSize - 1) / pageSize);
return pageVO;
}
}

View File

@ -0,0 +1,12 @@
package com.kama.notes.model.base;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Pagination {
private Integer page; // 当前页码
private Integer pageSize; // 每页显示的记录数
private Integer total; // 总记录数
}

View File

@ -0,0 +1,34 @@
package com.kama.notes.model.base;
/**
* PaginationApiResponse类用于处理分页的API响应
* 它继承自ApiResponse类并添加了分页信息的支持
*
* @param <T> 泛型参数表示API响应中携带的数据类型
*/
public class PaginationApiResponse<T> extends ApiResponse<T> {
// 分页信息对象
private final Pagination pagination;
/**
* 构造方法用于创建PaginationApiResponse对象
*
* @param code 响应代码
* @param msg 响应消息
* @param data 响应数据类型为泛型T
* @param pagination 分页信息对象包含分页相关数据
*/
public PaginationApiResponse(int code, String msg, T data, Pagination pagination) {
super(code, msg, data); // 调用父类ApiResponse的构造方法
this.pagination = pagination; // 初始化分页信息
}
/**
* 获取分页信息的方法
*
* @return 返回Pagination对象包含分页相关数据
*/
public Pagination getPagination() {
return pagination;
}
}

View File

@ -0,0 +1,36 @@
package com.kama.notes.model.base;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* TokenApiResponse类是ApiResponse的一个子类用于处理包含Token的API响应
* 它提供了一个方法来获取Token
*
* @param <T> 泛型参数表示API响应中数据的类型
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class TokenApiResponse<T> extends ApiResponse<T> {
/**
* -- GETTER --
* 获取Token的方法
*
* @return 返回API响应中的Token
*/
// 用于存储API响应中的Token
private final String token;
/**
* 构造函数用于初始化TokenApiResponse对象
*
* @param code 状态码表示API响应的状态
* @param msg 消息提供关于API响应的额外信息
* @param data 数据包含API响应的具体内容
* @param token Token包含API响应中的Token
*/
public TokenApiResponse(Integer code, String msg, T data, String token) {
super(code, msg, data);
this.token = token;
}
}

View File

@ -0,0 +1,21 @@
package com.kama.notes.model.dto.category;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class CreateCategoryBody {
@NotBlank(message = "name 不能为空")
@NotNull(message = "name 不能为空")
@Length(max = 32, min = 1, message = "name 长度在 1 - 32 之间")
private String name;
@NotNull(message = "parentCategoryId 不能为空")
@Min(value = 0, message = "parentCategoryId 必须为正整数")
private Integer parentCategoryId;
}

View File

@ -0,0 +1,16 @@
package com.kama.notes.model.dto.category;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class UpdateCategoryBody {
@NotBlank(message = "name 不能为空")
@NotNull(message = "name 不能为空")
@Length(max = 32, min = 1, message = "name 长度在 1 - 32 之间")
private String name;
}

View File

@ -0,0 +1,16 @@
package com.kama.notes.model.dto.collection;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Data
public class CollectionQueryParams {
@NotNull(message = "creatorId 不能为空")
@Min(value = 1, message = "creatorId 必须为正整数")
private Long creatorId;
@Min(value = 1, message = "noteId 必须为正整数")
private Integer noteId;
}

View File

@ -0,0 +1,14 @@
package com.kama.notes.model.dto.collection;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class CreateCollectionBody {
@NotNull(message = "name 不能为空")
@NotBlank(message = "name 不能为空")
private String name;
private String description;
}

View File

@ -0,0 +1,27 @@
package com.kama.notes.model.dto.collection;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
@Data
public class UpdateCollectionBody {
@Min(value = 1, message = "noteId 必须为正整数")
private Integer noteId;
private UpdateItem[] collections;
@Data
public static class UpdateItem {
@Min(value = 1, message = "collectionId 必须为正整数")
private Integer collectionId;
// 必须为 create 或者 delete
@NotNull(message = "action 不能为空")
@NotEmpty(message = "action 不能为空")
@Pattern(regexp = "create|delete", message = "action 必须为 create 或者 delete")
private String action;
}
}

View File

@ -0,0 +1,42 @@
package com.kama.notes.model.dto.comment;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
* 评论查询参数
*/
@Data
public class CommentQueryParams {
/**
* 笔记ID
*/
@NotNull(message = "笔记ID不能为空")
private Integer noteId;
/**
* 父评论ID
*/
private Integer parentId;
/**
* 作者ID
*/
private Long authorId;
/**
* 页码
*/
@NotNull(message = "页码不能为空")
@Min(value = 1, message = "页码必须大于0")
private Integer page;
/**
* 每页大小
*/
@NotNull(message = "每页大小不能为空")
@Min(value = 1, message = "每页大小必须大于0")
private Integer pageSize;
}

View File

@ -0,0 +1,29 @@
package com.kama.notes.model.dto.comment;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 创建评论请求
*/
@Data
public class CreateCommentRequest {
/**
* 笔记ID
*/
@NotNull(message = "笔记ID不能为空")
private Integer noteId;
/**
* 父评论ID
*/
private Integer parentId;
/**
* 评论内容
*/
@NotBlank(message = "评论内容不能为空")
private String content;
}

View File

@ -0,0 +1,17 @@
package com.kama.notes.model.dto.comment;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 更新评论请求
*/
@Data
public class UpdateCommentRequest {
/**
* 评论内容
*/
@NotBlank(message = "评论内容不能为空")
private String content;
}

View File

@ -0,0 +1,54 @@
package com.kama.notes.model.dto.message;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* 消息查询参数
*/
@Data
public class MessageQueryParams {
/**
* 消息类型
*/
private String type;
/**
* 是否已读
*/
private Boolean isRead;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 结束时间
*/
private LocalDateTime endTime;
/**
* 当前页码从1开始
*/
@Min(value = 1, message = "页码必须大于0")
private Integer page = 1;
/**
* 每页大小
*/
@Min(value = 1, message = "每页大小必须大于0")
private Integer pageSize = 10;
/**
* 排序字段默认创建时间
*/
private String sortField = "created_at";
/**
* 排序方向默认降序
*/
private String sortOrder = "desc";
}

View File

@ -0,0 +1,27 @@
package com.kama.notes.model.dto.note;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 发布笔记请求DTO
*/
@Data
public class CreateNoteRequest {
/*
* 问题ID
*/
@NotNull(message = "问题 ID 不能为空")
@Min(value = 1, message = "问题 ID 必须为正整数")
private Integer questionId;
/*
* 笔记内容
*/
@NotBlank(message = "笔记内容不能为空")
@NotNull(message = "笔记内容不能为空")
private String content;
}

View File

@ -0,0 +1,81 @@
package com.kama.notes.model.dto.note;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
/**
* 笔记查询参数DTO
*/
@Data
public class NoteQueryParams {
/*
* 问题ID
* 必须是正整数
*/
@Min(value = 1, message = "问题ID必须是正整数")
private Integer questionId;
/*
* 作者ID
* 必须是正整数且符合系统生成的范围
*/
@Min(value = 1, message = "作者ID必须是正整数")
private Long authorId;
/*
* 收藏夹ID
* 必须是正整数
*/
@Min(value = 1, message = "收藏夹ID必须是正整数")
private Integer collectionId;
/*
* 排序字段
* 只能是固定的枚举值比如 "create", "update"
*/
@Pattern(
regexp = "create",
message = "create"
)
private String sort;
/*
* 排序方向
* 只能是 "asc" "desc"区分大小写
*/
@Pattern(
regexp = "asc|desc",
message = "排序方向必须是 asc 或 desc"
)
private String order;
/*
* 最近天数
* 必须是1到365之间的整数默认限制为一年内
*/
@Min(value = 1, message = "最近天数必须至少为1天")
@Max(value = 365, message = "最近天数不能超过365天")
private Integer recentDays;
/*
* 当前页码
* 必须是正整数默认为1
*/
@NotNull(message = "当前页码不能为空")
@Min(value = 1, message = "当前页码必须大于等于1")
@Max(value = 10000, message = "当前页码不能超过10,000")
private Integer page = 1;
/*
* 每页大小
* 必须是正整数限制范围在 1到100之间
*/
@NotNull(message = "每页大小不能为空")
@Min(value = 1, message = "每页大小必须大于等于1")
@Max(value = 200, message = "每页大小不能超过100")
private Integer pageSize = 10;
}

View File

@ -0,0 +1,19 @@
package com.kama.notes.model.dto.note;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 更新笔记请求DTO
*/
@Data
public class UpdateNoteRequest {
/*
* 笔记内容
*/
@NotNull(message = "笔记内容不能为空")
@NotBlank(message = "笔记内容不能为空")
private String content;
}

View File

@ -0,0 +1,13 @@
package com.kama.notes.model.dto.notification;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data
public class NotificationDTO {
@NotEmpty(message = "content 不能为空")
@NotNull(message = "content 不能为空")
private String content;
}

View File

@ -0,0 +1,28 @@
package com.kama.notes.model.dto.question;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class CreateQuestionBody {
@NotNull(message = "categoryId 不能为空")
@Min(value = 1, message = "categoryId 必须为正整数")
private Integer categoryId;
@NotNull(message = "title 不能为空")
@NotBlank(message = "title 不能为空")
@Length(max = 255, message = "title 长度不能超过 255")
private String title;
@NotNull(message = "difficulty 不能为空")
@Range(min = 1, max = 3, message = "difficulty 必须为 1, 2, 3")
private Integer difficulty;
@Length(max = 255, message = "examPoint 长度不能超过 255")
private String examPoint;
}

View File

@ -0,0 +1,30 @@
package com.kama.notes.model.dto.question;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
@Data
public class QuestionQueryParam {
@Min(value = 1, message = "categoryId 必须为正整数")
private Integer categoryId;
@Pattern(regexp = "^(view|difficulty)$", message = "sort 必须为 view 或 difficulty")
private String sort;
@Pattern(regexp = "^(asc|desc)$", message = "order 必须为 asc 或 desc")
private String order;
@NotNull(message = "page 不能为空")
@Min(value = 1, message = "page 必须为正整数")
private Integer page;
@NotNull(message = "pageSize 不能为空")
@Min(value = 1, message = "pageSize 必须为正整数")
@Max(value = 200, message = "pageSize 不能超过 200")
private Integer pageSize;
}

View File

@ -0,0 +1,16 @@
package com.kama.notes.model.dto.question;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data
public class SearchQuestionBody {
@NotNull(message = "keyword 不能为空")
@NotEmpty(message = "keyword 不能为空")
@Length(min = 1, max = 32, message = "keyword 长度在 1 和 32 范围内")
private String keyword;
// TODO: 后续需要完善添加分页功能
}

View File

@ -0,0 +1,33 @@
package com.kama.notes.model.dto.question;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class UpdateQuestionBody {
/*
* 问题标题
*/
@NotNull(message = "title 不能为空")
@NotBlank(message = "title 不能为空")
@Length(max = 255, message = "title 长度不能超过 255")
private String title;
/*
* 问题难度
* 1=简单2=中等3=困难
*/
@NotNull(message = "difficulty 不能为空")
@Range(min = 1, max = 3, message = "difficulty 必须为 1, 2, 3")
private Integer difficulty;
/*
* 题目考点
*/
@Length(max = 255, message = "examPoint 长度不能超过 255")
private String examPoint;
}

View File

@ -0,0 +1,26 @@
package com.kama.notes.model.dto.questionList;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
@Data
public class CreateQuestionListBody {
/*
* 题单名称
*/
@Length(max = 32, message = "name 长度不能超过 32")
private String name;
/**
* 题单类型
*/
@Range(min = 1, max = 2, message = "type 必须为 1 或 2")
private Integer type;
/*
* 题单描述
*/
@Length(max = 255, message = "description 长度不能超过 255")
private String description;
}

View File

@ -0,0 +1,26 @@
package com.kama.notes.model.dto.questionList;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
@Data
public class UpdateQuestionListBody {
/*
* 题单名称
*/
@Length(max = 32, message = "name 长度不能超过 32")
private String name;
/**
* 题单类型
*/
@Range(min = 1, max = 2, message = "type 必须为 1 或 2")
private Integer type;
/*
* 题单描述
*/
@Length(max = 255, message = "description 长度不能超过 255")
private String description;
}

View File

@ -0,0 +1,17 @@
package com.kama.notes.model.dto.questionListItem;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Data
public class CreateQuestionListItemBody {
@NotNull(message = "questionListId 不能为空")
@Min(value = 1, message = "questionListId 必须为正整数")
private Integer questionListId;
@NotNull(message = "questionId 不能为空")
@Min(value = 1, message = "questionId 必须为正整数")
private Integer questionId;
}

View File

@ -0,0 +1,22 @@
package com.kama.notes.model.dto.questionListItem;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Data
public class QuestionListItemQueryParams {
@NotNull(message = "questionListId 不能为空")
@Min(value = 1, message = "questionListId 必须为正整数")
private Integer questionListId;
@NotNull(message = "page 不能为空")
@Min(value = 1, message = "page 必须为正整数")
private Integer page;
@NotNull(message = "pageSize 不能为空")
@Range(min = 1, max = 100, message = "pageSize 必须为 1 到 100 之间的整数")
private Integer pageSize;
}

View File

@ -0,0 +1,17 @@
package com.kama.notes.model.dto.questionListItem;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.List;
@Data
public class SortQuestionListItemBody {
@NotNull(message = "questionListId 不能为空")
@Min(value = 1, message = "questionListId 必须为正整数")
private Integer questionListId;
@NotNull(message = "questionListItemIds 不能为空")
private List<Integer> questionIds;
}

View File

@ -0,0 +1,17 @@
package com.kama.notes.model.dto.statistic;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Data
public class StatisticQueryParam {
@NotNull(message = "page 不能为空")
@Min(value = 1, message = "page 必须为正整数")
private Integer page;
@NotNull(message = "page 不能为空")
@Min(value = 1, message = "page 必须为正整数")
private Integer pageSize;
}

View File

@ -0,0 +1,38 @@
package com.kama.notes.model.dto.user;
import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import javax.validation.constraints.AssertTrue;
/**
* 登录请求DTO
*/
@Data
public class LoginRequest {
/*
* 用户账号
*/
@Size(min = 6, max = 32, message = "账号长度必须在 6 到 32 个字符之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "账号只能包含字母、数字和下划线")
private String account;
@Email(message = "邮箱格式不正确")
private String email;
/**
* 登录密码
* 必填长度 6-32
*/
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 32, message = "密码长度必须在 6 到 32 个字符之间")
private String password;
@AssertTrue(message = "账号和邮箱必须至少提供一个")
private boolean isValidLogin() {
return account != null || email != null;
}
}

View File

@ -0,0 +1,53 @@
package com.kama.notes.model.dto.user;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import javax.validation.constraints.Email;
/**
* 用户注册请求DTO
*/
@Data
public class RegisterRequest {
/**
* 用户账号
* 必填长度 6-32支持字母数字和下划线
*/
@NotBlank(message = "用户账号不能为空")
@Size(min = 6, max = 32, message = "账号长度必须在 6 到 32 个字符之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "账号只能包含字母、数字和下划线")
private String account;
/**
* 用户昵称
* 必填长度 1-16支持中文字母数字下划线分隔符
*/
@NotBlank(message = "用户名不能为空")
@Size(max = 16, message = "用户名长度不能超过 16 个字符")
@Pattern(regexp = "^[\\u4e00-\\u9fa5_a-zA-Z0-9\\-\\.]+$", message = "用户名只能包含中文、字母、数字、下划线、分隔符")
private String username;
/**
* 登录密码
* 必填长度 6-32
*/
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 32, message = "密码长度必须在 6 到 32 个字符之间")
private String password;
/**
* 邮箱选填
*/
@Email(message = "邮箱格式不正确")
private String email;
/**
* 验证码选填邮箱填写时必填
*/
@Size(min = 6, max = 6, message = "验证码长度必须为6位")
private String verifyCode;
}

View File

@ -0,0 +1,63 @@
package com.kama.notes.model.dto.user;
import lombok.Data;
import javax.validation.constraints.*;
import java.time.LocalDate;
/**
* 用户信息更新请求 DTO
*/
@Data
public class UpdateUserRequest {
/**
* 用户昵称
* 非必填长度在 1-16 个字符允许中文字母数字下划线
*/
@Size(min = 1, max = 16, message = "用户名长度必须在 1 到 16 个字符之间")
@Pattern(regexp = "^[\\u4e00-\\u9fa5_a-zA-Z0-9]+$", message = "用户名只能包含中文、字母、数字和下划线")
private String username;
/**
* 用户性别
* 非必填取值范围1=2=3=保密
*/
@Min(value = 1, message = "性别取值无效")
@Max(value = 3, message = "性别取值无效")
private Integer gender;
/**
* 用户生日
* 非必填必须是过去的日期
*/
@Past(message = "生日必须是一个过去的日期")
private LocalDate birthday;
/**
* 用户头像
* 非必填必须是有效的 URL
*/
@Pattern(regexp = "^(https?|ftp)://.*$", message = "头像地址必须是有效的 URL")
private String avatarUrl;
/**
* 用户邮箱
* 非必填必须是有效的邮箱地址
*/
@Email(message = "邮箱格式无效")
private String email;
/**
* 用户学校
* 非必填长度在 1-64 个字符
*/
@Size(max = 64, message = "学校名称长度不能超过 64 个字符")
private String school;
/**
* 用户签名
* 非必填长度在 1-128 个字符
*/
@Size(max = 128, message = "签名长度不能超过 128 个字符")
private String signature;
}

View File

@ -0,0 +1,38 @@
package com.kama.notes.model.dto.user;
import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
/**
* 图片上传响应DTO
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UploadImageResponse {
/*
* 状态码
*/
private Integer code;
/*
* 提示信息
*/
private String msg;
/*
* 响应数据
*/
private UploadImageResponseData data;
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UploadImageResponseData {
/*
* 图片访问URL
*/
private String url;
}
}

View File

@ -0,0 +1,35 @@
package com.kama.notes.model.dto.user;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Data
public class UserQueryParam {
@Min(value = 1, message = "userId 必须为正整数")
private Long userId;
private String account;
@Length(max = 16, message = "用户名长度不能超过 16 个字符")
private String username;
@Min(value = 0, message = "isAdmin 最小只能是 0")
@Max(value = 1, message = "isAdmin 最大只能是 1")
private Integer isAdmin;
@Min(value = 0, message = "isBanned 最小只能是 0")
@Max(value = 1, message = "isBanned 最大只能是 1")
private Integer isBanned;
@NotNull(message = "page 不能为空")
@Min(value = 1, message = "page 必须为正整数")
private Integer page;
@NotNull(message = "pageSize 不能为空")
@Min(value = 1, message = "pageSize 必须为正整数")
@Max(value = 200, message = "pageSize 不能超过 200")
private Integer pageSize;
}

View File

@ -0,0 +1,40 @@
package com.kama.notes.model.entity;
import lombok.Data;
import java.util.Date;
/**
* @ClassName Category
* @Description 分类实体类
* @Author Tong
* @LastChangeDate 2024-12-16 19:53
* @Version v1.0
*/
@Data
public class Category {
/*
* 分类ID主键
*/
private Integer categoryId;
/*
* 分类名称
*/
private String name;
/*
* 上级分类ID
* 为0时表示当前分类是一级分类
*/
private Integer parentCategoryId;
/*
* 创建时间
*/
private Date createdAt;
/*
* 更新时间
*/
private Date updatedAt;
}

View File

@ -0,0 +1,40 @@
package com.kama.notes.model.entity;
import lombok.Data;
import java.util.Date;
/**
* 收藏夹实体类
*/
@Data
public class Collection {
/*
* 收藏夹ID主键
*/
private Integer collectionId;
/*
* 收藏夹名称
*/
private String name;
/*
* 收藏夹描述
*/
private String description;
/*
* 收藏夹创建者ID
*/
private Long creatorId;
/*
* 创建时间
*/
private Date createdAt;
/*
* 更新时间
*/
private Date updatedAt;
}

View File

@ -0,0 +1,34 @@
package com.kama.notes.model.entity;
import lombok.Data;
import java.util.Date;
/**
* @ClassName CollectionNote
* @Description 收藏夹-笔记关联实体类
* @Author Tong
* @LastChangeDate 2024-12-16 20:11
* @Version v1.0
*/
@Data
public class CollectionNote {
/*
* 收藏夹ID联合主键
*/
private Integer collectionId;
/*
* 笔记ID联合主键
*/
private Integer noteId;
/*
* 创建时间
*/
private Date createdAt;
/*
* 更新时间
*/
private Date updatedAt;
}

View File

@ -0,0 +1,56 @@
package com.kama.notes.model.entity;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 评论实体类
*/
@Data
public class Comment {
/**
* 评论ID
*/
private Integer commentId;
/**
* 笔记ID
*/
private Integer noteId;
/**
* 作者ID
*/
private Long authorId;
/**
* 父评论ID
*/
private Integer parentId;
/**
* 评论内容
*/
private String content;
/**
* 点赞数
*/
private Integer likeCount;
/**
* 回复数
*/
private Integer replyCount;
/**
* 创建时间
*/
private LocalDateTime createdAt;
/**
* 更新时间
*/
private LocalDateTime updatedAt;
}

View File

@ -0,0 +1,31 @@
package com.kama.notes.model.entity;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 评论点赞实体类
*/
@Data
public class CommentLike {
/**
* 评论点赞ID
*/
private Integer commentLikeId;
/**
* 评论ID
*/
private Integer commentId;
/**
* 用户ID
*/
private Long userId;
/**
* 创建时间
*/
private LocalDateTime createdAt;
}

View File

@ -0,0 +1,15 @@
package com.kama.notes.model.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class EmailVerifyCode {
private Long id;
private String email;
private String code;
private String type;
private LocalDateTime expiredAt;
private LocalDateTime createdAt;
private Boolean used;
}

View File

@ -0,0 +1,55 @@
package com.kama.notes.model.entity;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 消息实体类
*/
@Data
public class Message {
/**
* 消息ID
*/
private Integer messageId;
/**
* 接收者ID
*/
private Long receiverId;
/**
* 发送者ID
*/
private Long senderId;
/**
* 消息类型
*/
private String type;
/**
* 目标ID
*/
private Integer targetId;
/**
* 消息内容
*/
private String content;
/**
* 是否已读
*/
private Boolean isRead;
/**
* 创建时间
*/
private LocalDateTime createdAt;
/**
* 更新时间
*/
private LocalDateTime updatedAt;
}

View File

@ -0,0 +1,60 @@
package com.kama.notes.model.entity;
import lombok.Data;
import java.time.LocalDateTime;
/**
* @ClassName Note
* @Description 笔记实体类
* @Author Tong
* @LastChangeDate 2024-12-16 20:01
* @Version v1.0
*/
@Data
public class Note {
/**
* 笔记ID
*/
private Integer noteId;
/**
* 作者ID
*/
private Long authorId;
/**
* 问题ID
*/
private Integer questionId;
/**
* 笔记内容
*/
private String content;
/**
* 点赞数
*/
private Integer likeCount;
/**
* 评论数
*/
private Integer commentCount;
/**
* 收藏数
*/
private Integer collectCount;
/**
* 创建时间
*/
private LocalDateTime createdAt;
/**
* 更新时间
*/
private LocalDateTime updatedAt;
}

View File

@ -0,0 +1,30 @@
package com.kama.notes.model.entity;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 笔记收藏实体类
*/
@Data
public class NoteCollect {
/**
* 收藏ID
*/
private Integer collectId;
/**
* 笔记ID
*/
private Integer noteId;
/**
* 用户ID
*/
private Long userId;
/**
* 创建时间
*/
private LocalDateTime createdAt;
}

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