commit 9431f1557d41c960296f926588e80bfba0d7b38b Author: Tong <864508127@qq.com> Date: Thu Jan 23 17:25:59 2025 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c25c5ae --- /dev/null +++ b/.gitignore @@ -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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..24c5ddc --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# kama-notes + +【代码随想录知识星球】项目分享-卡码笔记 + +## 项目介绍 diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..96df913 --- /dev/null +++ b/backend/.gitignore @@ -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 diff --git a/backend/.mvn/wrapper/maven-wrapper.properties b/backend/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..d58dfb7 --- /dev/null +++ b/backend/.mvn/wrapper/maven-wrapper.properties @@ -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 diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..840f97c --- /dev/null +++ b/backend/Dockerfile @@ -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 diff --git a/backend/mvnw b/backend/mvnw new file mode 100644 index 0000000..19529dd --- /dev/null +++ b/backend/mvnw @@ -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-,maven-mvnd--}/ +[ -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 "$@" diff --git a/backend/mvnw.cmd b/backend/mvnw.cmd new file mode 100644 index 0000000..249bdf3 --- /dev/null +++ b/backend/mvnw.cmd @@ -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-,maven-mvnd--}/ +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" diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 0000000..99341c7 --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,150 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.18 + + + com.kama + notes + 0.0.1 + notes-tech + kamaNotes + jar + + 17 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-security + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.2.0 + + + mysql + mysql-connector-java + 8.0.33 + + + io.jsonwebtoken + jjwt + 0.9.1 + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-aop + + + com.fasterxml.jackson.core + jackson-databind + + + junit + junit + test + + + javax.validation + validation-api + 2.0.1.Final + + + org.hibernate.validator + hibernate-validator + 6.2.4.Final + + + cn.hutool + hutool-core + 5.8.25 + + + jakarta.xml.bind + jakarta.xml.bind-api + 2.3.2 + + + org.glassfish.jaxb + jaxb-runtime + 2.3.2 + + + com.vladsch.flexmark + flexmark-all + 0.64.8 + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + + aliyun + aliyun maven + https://maven.aliyun.com/repository/public + + true + + + false + + + + diff --git a/backend/src/main/java/com/kama/notes/NotesApplication.java b/backend/src/main/java/com/kama/notes/NotesApplication.java new file mode 100644 index 0000000..f96d062 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/NotesApplication.java @@ -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); + } +} diff --git a/backend/src/main/java/com/kama/notes/annotation/NeedLogin.java b/backend/src/main/java/com/kama/notes/annotation/NeedLogin.java new file mode 100644 index 0000000..683b13e --- /dev/null +++ b/backend/src/main/java/com/kama/notes/annotation/NeedLogin.java @@ -0,0 +1,9 @@ +package com.kama.notes.annotation; + +import java.lang.annotation.*; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface NeedLogin { +} diff --git a/backend/src/main/java/com/kama/notes/aspect/NeedLoginAspect.java b/backend/src/main/java/com/kama/notes/aspect/NeedLoginAspect.java new file mode 100644 index 0000000..19588f2 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/aspect/NeedLoginAspect.java @@ -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(); + } +} diff --git a/backend/src/main/java/com/kama/notes/aspect/PutTraceIdAspect.java b/backend/src/main/java/com/kama/notes/aspect/PutTraceIdAspect.java new file mode 100644 index 0000000..b36d8f4 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/aspect/PutTraceIdAspect.java @@ -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); + } + } +} diff --git a/backend/src/main/java/com/kama/notes/config/MyBatisConfig.java b/backend/src/main/java/com/kama/notes/config/MyBatisConfig.java new file mode 100644 index 0000000..a79944f --- /dev/null +++ b/backend/src/main/java/com/kama/notes/config/MyBatisConfig.java @@ -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 { +} diff --git a/backend/src/main/java/com/kama/notes/config/RedisConfig.java b/backend/src/main/java/com/kama/notes/config/RedisConfig.java new file mode 100644 index 0000000..3672bbb --- /dev/null +++ b/backend/src/main/java/com/kama/notes/config/RedisConfig.java @@ -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 redisTemplate(RedisConnectionFactory factory) { + RedisTemplate 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); + } +} diff --git a/backend/src/main/java/com/kama/notes/config/SchedulerConfig.java b/backend/src/main/java/com/kama/notes/config/SchedulerConfig.java new file mode 100644 index 0000000..e73870c --- /dev/null +++ b/backend/src/main/java/com/kama/notes/config/SchedulerConfig.java @@ -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; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/kama/notes/config/SecurityConfig.java b/backend/src/main/java/com/kama/notes/config/SecurityConfig.java new file mode 100644 index 0000000..116eb00 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/config/SecurityConfig.java @@ -0,0 +1,40 @@ +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.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +/** + * @ClassName Security配置类 + * @Description ToDo + * @Author Tong + * @LastChangeDate 2024-12-17 15:40 + * @Version v1.0 + */ +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf() + .disable() + .authorizeRequests() + .antMatchers("/api/**", "/images/**") + .permitAll() + .anyRequest() + .authenticated() + .and() + .formLogin() + .disable(); + } +} diff --git a/backend/src/main/java/com/kama/notes/config/WebConfig.java b/backend/src/main/java/com/kama/notes/config/WebConfig.java new file mode 100644 index 0000000..d1f84e3 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/config/WebConfig.java @@ -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() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new TraceIdFilter()); + registrationBean.addUrlPatterns("/*"); + return registrationBean; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/kama/notes/controller/CategoryController.java b/backend/src/main/java/com/kama/notes/controller/CategoryController.java new file mode 100644 index 0000000..eaa2872 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/controller/CategoryController.java @@ -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> userCategories() { + return categoryService.categoryList(); + } + + /** + * 获取分类列表(管理员端)。 + * + * @return 包含分类列表的响应。 + */ + @GetMapping("/admin/categories") + public ApiResponse> categories() { + return categoryService.categoryList(); + } + + /** + * 创建新的分类。 + * + * @param createCategoryBody 包含分类创建信息的请求体。 + * @return 包含创建成功的分类信息的响应。 + */ + @PostMapping("/admin/categories") + public ApiResponse createCategory( + @Valid @RequestBody CreateCategoryBody createCategoryBody) { + return categoryService.createCategory(createCategoryBody); + } + + /** + * 更新指定的分类信息。 + * + * @param categoryId 分类ID,必须为正整数。 + * @param updateCategoryBody 包含更新信息的请求体。 + * @return 包含更新操作结果的响应。 + */ + @PatchMapping("/admin/categories/{categoryId}") + public ApiResponse 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 deleteCategory( + @Min(value = 1, message = "categoryId 必须为正整数") @PathVariable Integer categoryId) { + return categoryService.deleteCategory(categoryId); + } +} diff --git a/backend/src/main/java/com/kama/notes/controller/CollectionController.java b/backend/src/main/java/com/kama/notes/controller/CollectionController.java new file mode 100644 index 0000000..e8ea9a5 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/controller/CollectionController.java @@ -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> getCollections( + @Valid + CollectionQueryParams queryParams) { + return collectionService.getCollections(queryParams); + } + + /** + * 创建收藏夹接口 + * + * @param requestBody 创建收藏夹请求体 + * @return 创建结果,如果成功则包含收藏夹 ID + */ + @PostMapping("/collections") + public ApiResponse createCollection( + @Valid + @RequestBody + CreateCollectionBody requestBody) { + return collectionService.createCollection(requestBody); + } + + /** + * 删除收藏夹接口 + * + * @param collectionId 收藏夹 ID + * @return 返回删除结果 + */ + @DeleteMapping("/collections/{collectionId}") + public ApiResponse deleteCollection( + @PathVariable + @Min(value = 1, message = "collectionId 必须为正整数") + Integer collectionId) { + return collectionService.deleteCollection(collectionId); + } + + /** + * 批量修改收藏夹接口 + * + * @param collectionBody 收藏夹 ID + * @return 返回修改结果 + */ + @PostMapping("/collections/batch") + public ApiResponse batchModifyCollection( + @Valid + @RequestBody + UpdateCollectionBody collectionBody) { + return collectionService.batchModifyCollection(collectionBody); + } +} diff --git a/backend/src/main/java/com/kama/notes/controller/CollectionNoteController.java b/backend/src/main/java/com/kama/notes/controller/CollectionNoteController.java new file mode 100644 index 0000000..ccefa7f --- /dev/null +++ b/backend/src/main/java/com/kama/notes/controller/CollectionNoteController.java @@ -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 { +} diff --git a/backend/src/main/java/com/kama/notes/controller/NoteController.java b/backend/src/main/java/com/kama/notes/controller/NoteController.java new file mode 100644 index 0000000..a2ce89d --- /dev/null +++ b/backend/src/main/java/com/kama/notes/controller/NoteController.java @@ -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> getNotes( + @Valid NoteQueryParams params) { + return noteService.getNotes(params); + } + + /** + * 发布笔记 + * + * @param request 创建笔记的请求对象,包含笔记的内容等信息 + * @return 返回一个包含新创建笔记信息的 ApiResponse 对象 + */ + @PostMapping("/notes") + public ApiResponse createNote( + @Valid @RequestBody CreateNoteRequest request) { + return noteService.createNote(request); + } + + /** + * 更新笔记 + * + * @param noteId 笔记的唯一标识符,用于定位要更新的笔记 + * @param request 更新笔记的请求对象,包含需要修改的信息 + * @return 返回一个包含更新后笔记信息的 ApiResponse 对象 + */ + @PatchMapping("/notes/{noteId}") + public ApiResponse 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 deleteNote( + @Min(value = 1, message = "noteId 必须为正整数") + @PathVariable Integer noteId) { + return noteService.deleteNote(noteId); + } + + /** + * 下载笔记 + * @return + */ + @GetMapping("/notes/download") + public ApiResponse downloadNote() { + return noteService.downloadNote(); + } + + /** + * 提交笔记排行榜 + */ + @GetMapping("/notes/ranklist") + public ApiResponse> submitNoteRank() { + return noteService.submitNoteRank(); + } + + /** + * 用户提交热力图 + */ + @GetMapping("/notes/heatmap") + public ApiResponse> submitNoteHeatMap() { + return noteService.submitNoteHeatMap(); + } + + /** + * 用户提交 top3 count + */ + @GetMapping("/notes/top3count") + public ApiResponse submitNoteTop3Count() { + return noteService.submitNoteTop3Count(); + } +} diff --git a/backend/src/main/java/com/kama/notes/controller/NoteLikeController.java b/backend/src/main/java/com/kama/notes/controller/NoteLikeController.java new file mode 100644 index 0000000..53049ad --- /dev/null +++ b/backend/src/main/java/com/kama/notes/controller/NoteLikeController.java @@ -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 likeNote(@PathVariable Integer noteId) { + return noteLikeService.likeNote(noteId); + } + + @DeleteMapping("/like/note/{noteId}") + public ApiResponse unlikeNote(@PathVariable Integer noteId) { + return noteLikeService.unlikeNote(noteId); + } +} diff --git a/backend/src/main/java/com/kama/notes/controller/NotificationController.java b/backend/src/main/java/com/kama/notes/controller/NotificationController.java new file mode 100644 index 0000000..ac1e810 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/controller/NotificationController.java @@ -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 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 setNotifications(@Valid @RequestBody NotificationDTO notificationDTO) { + redisService.set("kamanote:notification", notificationDTO.getContent()); + return ApiResponseUtil.success("设置通知成功"); + } +} diff --git a/backend/src/main/java/com/kama/notes/controller/QuestionController.java b/backend/src/main/java/com/kama/notes/controller/QuestionController.java new file mode 100644 index 0000000..5c07d1f --- /dev/null +++ b/backend/src/main/java/com/kama/notes/controller/QuestionController.java @@ -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> userGetQuestions(@Valid QuestionQueryParam queryParams) { + return questionService.userGetQuestions(queryParams); + } + + /** + * 用户端搜索问题 + * + * @param body 包含搜索关键词的请求体 + * @return 包含搜索结果的视图对象列表的响应 + */ + @PostMapping("/questions/search") + public ApiResponse> searchQuestions(@Valid @RequestBody SearchQuestionBody body) { + return questionService.searchQuestions(body); + } + + /** + * 用户端获取单个问题详情 + * + * @param questionId 问题ID,必须为正整数 + * @return 包含问题详情及关联笔记的视图对象的响应 + */ + @GetMapping("/questions/{questionId}") + public ApiResponse userGetQuestion(@Min(value = 1, message = "questionId 必须为正整数") + @PathVariable Integer questionId) { + return questionService.userGetQuestion(questionId); + } + + /** + * 管理端获取问题列表 + * + * @param queryParams 查询参数,用于过滤问题列表(如关键词、时间范围等) + * @return 包含所有问题的视图对象列表的响应 + */ + @GetMapping("/admin/questions") + public ApiResponse> getQuestions(@Valid QuestionQueryParam queryParams) { + return questionService.getQuestions(queryParams); + } + + /** + * 管理端创建新问题 + * + * @param createQuestionBody 创建问题的请求体,包含问题的标题、内容等信息 + * @return 包含新创建问题视图对象的响应 + */ + @PostMapping("/admin/questions") + public ApiResponse createQuestion(@Valid @RequestBody CreateQuestionBody createQuestionBody) { + return questionService.createQuestion(createQuestionBody); + } + + /** + * 管理端更新问题 + * + * @param questionId 问题ID,必须为正整数 + * @param updateQuestionBody 更新问题的请求体,包含要更新的字段和值 + * @return 空视图对象的响应,表示更新操作成功 + */ + @PatchMapping("/admin/questions/{questionId}") + public ApiResponse 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 deleteQuestion(@Min(value = 1, message = "questionId 必须为正整数") + @PathVariable Integer questionId) { + return questionService.deleteQuestion(questionId); + } +} diff --git a/backend/src/main/java/com/kama/notes/controller/QuestionListController.java b/backend/src/main/java/com/kama/notes/controller/QuestionListController.java new file mode 100644 index 0000000..9711634 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/controller/QuestionListController.java @@ -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 getQuestionList(@Min(value = 1, message = "questionListId 必须为正整数") + @PathVariable Integer questionListId) { + return questionListService.getQuestionList(questionListId); + } + + /** + * 获取题单列表。 + * + * @return 包含题单列表的响应。 + */ + @GetMapping("/admin/questionlists") + public ApiResponse> getQuestionLists() { + return questionListService.getQuestionLists(); + } + + /** + * 创建新的题单。 + * + * @param body 包含题单创建信息的请求体。 + * @return 包含创建成功的题单信息的响应。 + */ + @PostMapping("/admin/questionlists") + public ApiResponse createQuestionList(@Valid @RequestBody CreateQuestionListBody body) { + return questionListService.createQuestionList(body); + } + + /** + * 删除指定的题单。 + * + * @param questionListId 要删除的题单ID,必须为正整数。 + * @return 包含删除操作结果的响应。 + */ + @DeleteMapping("/admin/questionlists/{questionListId}") + public ApiResponse 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 updateQuestionList(@Min(value = 1, message = "questionListId 必须为正整数") + @PathVariable Integer questionListId, + @Valid @RequestBody UpdateQuestionListBody body) { + return questionListService.updateQuestionList(questionListId, body); + } +} diff --git a/backend/src/main/java/com/kama/notes/controller/QuestionListItemController.java b/backend/src/main/java/com/kama/notes/controller/QuestionListItemController.java new file mode 100644 index 0000000..54eebb5 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/controller/QuestionListItemController.java @@ -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> userGetQuestionListItems( + @Valid QuestionListItemQueryParams queryParams) { + return questionListItemService.userGetQuestionListItems(queryParams); + } + + /** + * 获取指定题单中的题单项列表(管理端)。 + * + * @param questionListId 题单ID,可选参数,若提供则获取指定题单的题单项。 + * @return 包含题单项列表的响应。 + */ + @GetMapping("/admin/questionlist-items/{questionListId}") + public ApiResponse> getQuestionListItems( + @Min(value = 1, message = "questionListId 必须为正整数") + @PathVariable Integer questionListId) { + return questionListItemService.getQuestionListItems(questionListId); + } + + /** + * 创建新的题单项。 + * + * @param body 包含题单项创建信息的请求体。 + * @return 包含创建成功的题单项信息的响应。 + */ + @PostMapping("/admin/questionlist-items") + public ApiResponse createQuestionListItem( + @Valid + @RequestBody + CreateQuestionListItemBody body) { + return questionListItemService.createQuestionListItem(body); + } + + /** + * 删除指定的题单项。 + * + * @param questionListId 题单ID,必须为正整数。 + * @param questionId 题目ID,必须为正整数。 + * @return 包含删除操作结果的响应。 + */ + @DeleteMapping("/admin/questionlist-items/{questionListId}/{questionId}") + public ApiResponse 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 sortQuestionListItem( + @Valid + @RequestBody + SortQuestionListItemBody body) { + return questionListItemService.sortQuestionListItem(body); + } +} diff --git a/backend/src/main/java/com/kama/notes/controller/StatisticController.java b/backend/src/main/java/com/kama/notes/controller/StatisticController.java new file mode 100644 index 0000000..03b7fc5 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/controller/StatisticController.java @@ -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> getStatistic(@Valid StatisticQueryParam queryParam) { + return statisticService.getStatistic(queryParam); + } +} diff --git a/backend/src/main/java/com/kama/notes/controller/TestController.java b/backend/src/main/java/com/kama/notes/controller/TestController.java new file mode 100644 index 0000000..e8b7e15 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/controller/TestController.java @@ -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"); + } +} diff --git a/backend/src/main/java/com/kama/notes/controller/UploadController.java b/backend/src/main/java/com/kama/notes/controller/UploadController.java new file mode 100644 index 0000000..30712cd --- /dev/null +++ b/backend/src/main/java/com/kama/notes/controller/UploadController.java @@ -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 uploadImage(@RequestParam("file") MultipartFile file) { + return uploadService.uploadImage(file); + } +} diff --git a/backend/src/main/java/com/kama/notes/controller/UserController.java b/backend/src/main/java/com/kama/notes/controller/UserController.java new file mode 100644 index 0000000..d61a801 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/controller/UserController.java @@ -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 register( + @Valid + @RequestBody + RegisterRequest request) { + return userService.register(request); + } + + /** + * 用户登录接口 + * 处理用户登录请求,验证请求体并调用userService进行登录 + * + * @param request 用户登录请求对象,包含用户登录所需信息 + * @return 返回登录结果,包括用户信息和认证令牌等 + */ + @PostMapping("/users/login") + public ApiResponse login( + @Valid + @RequestBody + LoginRequest request) { + return userService.login(request); + } + + /** + * 自动登录接口 + * 当用户已登录并请求自动登录时,调用userService获取当前用户信息 + * + * @return 返回当前用户信息 + */ + @PostMapping("/users/whoami") + public ApiResponse whoami() { + return userService.whoami(); + } + + /** + * 查询用户信息接口 + * 根据用户ID查询用户信息,验证ID格式并调用userService获取用户详情 + * + * @param userId 用户ID,需为数字格式 + * @return 返回指定用户的详细信息 + */ + @GetMapping("/users/{userId}") + public ApiResponse getUserInfo( + @PathVariable + @Pattern(regexp = "\\d+", message = "ID 格式错误") + Long userId) { + return userService.getUserInfo(userId); + } + + /** + * 更新用户信息接口 + * 处理更新用户信息请求,验证请求体并调用userService更新用户详情 + * + * @param request 更新用户请求对象,包含需要更新的用户信息 + * @return 返回更新后的用户信息 + */ + @PatchMapping("/users/me") + public ApiResponse updateUserInfo( + @Valid + @RequestBody + UpdateUserRequest request) { + return userService.updateUserInfo(request); + } + + /** + * 上传用户头像接口 + * + * @param file 头像文件 + * @return 返回上传结果,包括头像URL等 + */ + @PostMapping("/users/avatar") + public ApiResponse uploadAvatar( + @RequestParam("file") MultipartFile file) { + return userService.uploadAvatar(file); + } + + /** + * 管理员获取用户信息列表的接口 + * 该接口允许管理员查询系统的用户列表,支持分页和条件查询 + * + * @param queryParam 查询参数对象,封装了用户查询条件和分页信息,通过验证确保参数有效性 + * @return 返回一个包含用户列表的ApiResponse对象,响应中包含用户数据 + */ + @GetMapping("/admin/users") + public ApiResponse> adminGetUser( + @Valid UserQueryParam queryParam) { + return userService.getUserList(queryParam); + } +} diff --git a/backend/src/main/java/com/kama/notes/exception/ParamExceptionHandler.java b/backend/src/main/java/com/kama/notes/exception/ParamExceptionHandler.java new file mode 100644 index 0000000..2011507 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/exception/ParamExceptionHandler.java @@ -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> handleValidationExceptions(MethodArgumentNotValidException ex) { + Map errors = new HashMap<>(); + ex.getBindingResult().getFieldErrors().forEach(error -> + errors.put(error.getField(), error.getDefaultMessage()) + ); + return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), "Validation Failed", errors); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ApiResponse> handleConstraintViolationExceptions(ConstraintViolationException ex) { + Map errors = new HashMap<>(); + ex.getConstraintViolations().forEach(violation -> + errors.put(violation.getPropertyPath().toString(), violation.getMessage()) + ); + return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), "Validation Failed", errors); + } + + @ExceptionHandler(Exception.class) + public ApiResponse handleException(Exception ex) { + return new ApiResponse<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Internal Server Error", ex.getMessage()); + } +} diff --git a/backend/src/main/java/com/kama/notes/filter/TraceIdFilter.java b/backend/src/main/java/com/kama/notes/filter/TraceIdFilter.java new file mode 100644 index 0000000..afa8c12 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/filter/TraceIdFilter.java @@ -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() {} +} diff --git a/backend/src/main/java/com/kama/notes/interceptor/TokenInterceptor.java b/backend/src/main/java/com/kama/notes/interceptor/TokenInterceptor.java new file mode 100644 index 0000000..db36928 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/interceptor/TokenInterceptor.java @@ -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); + } +} diff --git a/backend/src/main/java/com/kama/notes/mapper/CategoryMapper.java b/backend/src/main/java/com/kama/notes/mapper/CategoryMapper.java new file mode 100644 index 0000000..2386897 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/mapper/CategoryMapper.java @@ -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 categories); + + /** + * 获取所有分类 + * + * @return 分类列表 + */ + List categoryList(); + + /** + * 根据分类ID查找分类信息 + * + * @param categoryId 分类ID,用于唯一标识一个分类 + * @return 返回找到的Category对象,如果不存在则返回null + */ + Category findById(Integer categoryId); + + /** + * 批量通过分类ID查找分类信息 + * 此方法允许一次性查询多个分类的信息,通过提供一个分类ID列表作为参数 + * + * @param categoryIds 分类ID列表,用于指定需要查找的分类 + * @return 匹配给定ID的分类对象列表如果没有找到匹配的分类,则返回空列表 + */ + List findByIdBatch(@Param("categoryIds") List categoryIds); + + /** + * 根据分类ID或父分类ID查找分类信息 + * 此方法旨在处理分类信息的查询,通过分类ID或父分类ID来过滤并返回匹配的分类对象列表 + * + * @param categoryId 分类ID或父分类ID,用于查找分类的依据 + * @return 返回一个Category对象的列表,这些对象的分类ID或父分类ID与给定的categoryId匹配 + */ + List findByIdOrParentId(Integer categoryId); + + /** + * 删除分类以及子分类 + * + * @param categoryId 分类 ID + */ + int deleteById(Integer categoryId); + + /** + * 批量删除分类 + * 通过多个分类ID一次性删除对应的分类信息 + * + * @param categoryIds 包含多个分类ID的列表,用于指定需要删除的分类 + * @return 返回删除操作的影响行数,表示成功删除的分类数量 + */ + int deleteByIdBatch(@Param("categoryIds") List categoryIds); + + /** + * 更新分类信息 + * + * @param category 要更新的分类对象,包含新的分类信息 + * @return 返回更新操作的影响行数,表示成功更新的分类数量 + */ + int update(Category category); +} diff --git a/backend/src/main/java/com/kama/notes/mapper/CollectionMapper.java b/backend/src/main/java/com/kama/notes/mapper/CollectionMapper.java new file mode 100644 index 0000000..22002e0 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/mapper/CollectionMapper.java @@ -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 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); +} \ No newline at end of file diff --git a/backend/src/main/java/com/kama/notes/mapper/CollectionNoteMapper.java b/backend/src/main/java/com/kama/notes/mapper/CollectionNoteMapper.java new file mode 100644 index 0000000..6c8729b --- /dev/null +++ b/backend/src/main/java/com/kama/notes/mapper/CollectionNoteMapper.java @@ -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 findUserCollectedNoteIds( + @Param("userId") Long userId, + @Param("noteIds") List noteIds + ); + + /** + * 筛选出所给的收藏夹 ID 列表中,收藏了 noteId 对应的 note 的记录 + * + * @param noteId 笔记 ID + * @param collectionIds 收藏夹 ID 列表 + * @return 筛选结果 + */ + Set filterCollectionIdsByNoteId( + @Param("noteId") Integer noteId, + @Param("collectionIds") List 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); +} diff --git a/backend/src/main/java/com/kama/notes/mapper/NoteLikeMapper.java b/backend/src/main/java/com/kama/notes/mapper/NoteLikeMapper.java new file mode 100644 index 0000000..2f96a14 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/mapper/NoteLikeMapper.java @@ -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 findUserLikedNoteIds( + @Param("userId") Long userId, + @Param("noteIds") List noteIds + ); + + /** + * 根据用户ID和笔记ID,查找特定的笔记点赞记录 + * 此方法用于验证用户是否点赞特定的笔记,通过用户ID和笔记ID的组合来查询 + * + * @param userId 用户ID,用于标识用户 + * @param noteId 笔记ID,用于标识笔记 + * @return 笔记点赞记录,如果找到则返回,否则返回null + */ + NoteLike findByUserIdAndNoteId( + @Param("userId") Long userId, + @Param("noteId") Integer noteId + ); +} diff --git a/backend/src/main/java/com/kama/notes/mapper/NoteMapper.java b/backend/src/main/java/com/kama/notes/mapper/NoteMapper.java new file mode 100644 index 0000000..6344627 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/mapper/NoteMapper.java @@ -0,0 +1,167 @@ +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 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 findByAuthorId(@Param("authorId") Long authorId); + + + /** + * 根据用户ID和问题ID列表,过滤出用户已完成的问题ID列表 + * + * @param authorId 用户ID,用于标识特定用户 + * @param questionIds 问题ID列表,表示待查询的问题范围 + * @return 用户已完成的问题ID列表,如果用户未完成任何问题,则返回空集合 + */ + Set filterFinishedQuestionIdsByUser(@Param("authorId") Long authorId, + @Param("questionIds") List 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 submitNoteRank(); + + /** + * 提交热力图 + * + * @return 用户提交热力图信息 + */ + List submitNoteHeatMap(@Param("authorId") Long authorId); + + /** + * 用户提交 top3Count + * + * @return 用户提交 top3Count + */ + Top3Count submitNoteTop3Count(@Param("authorId") Long authorId); + + /** + * 当日笔记数 + * + * @return 当日笔记数 + */ + int getTodayNoteCount(); + + /** + * 当日提交笔记人数 + * @return 当日提交笔记人数 + */ + int getTodaySubmitNoteUserCount(); + + /** + * 笔记总数 + * @return 笔记总数 + */ + int getTotalNoteCount(); +} diff --git a/backend/src/main/java/com/kama/notes/mapper/QuestionListItemMapper.java b/backend/src/main/java/com/kama/notes/mapper/QuestionListItemMapper.java new file mode 100644 index 0000000..5a202f8 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/mapper/QuestionListItemMapper.java @@ -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 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 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); +} diff --git a/backend/src/main/java/com/kama/notes/mapper/QuestionListMapper.java b/backend/src/main/java/com/kama/notes/mapper/QuestionListMapper.java new file mode 100644 index 0000000..aae69fb --- /dev/null +++ b/backend/src/main/java/com/kama/notes/mapper/QuestionListMapper.java @@ -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 findAll(); + + /** + * 更新一个题单的信息 + * + * @param questionList 要更新的题单对象,包含需要更新的字段 + * @return 更新操作影响的行数 + */ + int update(QuestionList questionList); + + /** + * 根据题单ID删除题单 + * + * @param questionListId 题单的唯一标识符 + * @return 删除操作影响的行数 + */ + int deleteById(@Param("questionListId") Integer questionListId); +} diff --git a/backend/src/main/java/com/kama/notes/mapper/QuestionMapper.java b/backend/src/main/java/com/kama/notes/mapper/QuestionMapper.java new file mode 100644 index 0000000..4147357 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/mapper/QuestionMapper.java @@ -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 findByIdBatch(@Param("questionIds") List questionIds); + + /** + * 根据查询参数获取问题列表 + * + * @param queryParam 查询参数对象,包含多种可能的查询条件 + * @param offset 分页查询的起始位置 + * @param limit 每页返回的最大记录数 + * @return 匹配查询条件的问题列表 + */ + List findByQueryParam(@Param("queryParam") QuestionQueryParam queryParam, + @Param("offset") int offset, + @Param("limit") int limit); + + /** + * 根据关键字搜索问题 + * + * @param keyword 关键字,用于匹配问题标题或内容 + * @return 匹配关键字的问题列表 + */ + List 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 categoryIds); +} diff --git a/backend/src/main/java/com/kama/notes/mapper/StatisticMapper.java b/backend/src/main/java/com/kama/notes/mapper/StatisticMapper.java new file mode 100644 index 0000000..3b09692 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/mapper/StatisticMapper.java @@ -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 findByPage(@Param("limit") Integer limit, @Param("offset") Integer offset); +} diff --git a/backend/src/main/java/com/kama/notes/mapper/UserMapper.java b/backend/src/main/java/com/kama/notes/mapper/UserMapper.java new file mode 100644 index 0000000..c07b4bc --- /dev/null +++ b/backend/src/main/java/com/kama/notes/mapper/UserMapper.java @@ -0,0 +1,119 @@ +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 findByIdBatch(@Param("userIds") List 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 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(); +} diff --git a/backend/src/main/java/com/kama/notes/model/base/ApiResponse.java b/backend/src/main/java/com/kama/notes/model/base/ApiResponse.java new file mode 100644 index 0000000..50144fa --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/base/ApiResponse.java @@ -0,0 +1,14 @@ +package com.kama.notes.model.base; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApiResponse { + private Integer code; + private String msg; + private T data; +} \ No newline at end of file diff --git a/backend/src/main/java/com/kama/notes/model/base/EmptyVO.java b/backend/src/main/java/com/kama/notes/model/base/EmptyVO.java new file mode 100644 index 0000000..7c7bf4c --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/base/EmptyVO.java @@ -0,0 +1,6 @@ +package com.kama.notes.model.base; + +// 占位 +// 用于操作成功,只需要返回状态码,无任何额外数据时使用 +public class EmptyVO { +} diff --git a/backend/src/main/java/com/kama/notes/model/base/Pagination.java b/backend/src/main/java/com/kama/notes/model/base/Pagination.java new file mode 100644 index 0000000..dbc1428 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/base/Pagination.java @@ -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; // 总记录数 +} diff --git a/backend/src/main/java/com/kama/notes/model/base/PaginationApiResponse.java b/backend/src/main/java/com/kama/notes/model/base/PaginationApiResponse.java new file mode 100644 index 0000000..101e9d4 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/base/PaginationApiResponse.java @@ -0,0 +1,34 @@ +package com.kama.notes.model.base; + +/** + * PaginationApiResponse类用于处理分页的API响应 + * 它继承自ApiResponse类,并添加了分页信息的支持 + * + * @param 泛型参数,表示API响应中携带的数据类型 + */ +public class PaginationApiResponse extends ApiResponse { + // 分页信息对象 + 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; + } +} diff --git a/backend/src/main/java/com/kama/notes/model/base/TokenApiResponse.java b/backend/src/main/java/com/kama/notes/model/base/TokenApiResponse.java new file mode 100644 index 0000000..39121f0 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/base/TokenApiResponse.java @@ -0,0 +1,36 @@ +package com.kama.notes.model.base; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * TokenApiResponse类是ApiResponse的一个子类,用于处理包含Token的API响应 + * 它提供了一个方法来获取Token + * + * @param 泛型参数,表示API响应中数据的类型 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TokenApiResponse extends ApiResponse { + /** + * -- 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; + } +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/category/CreateCategoryBody.java b/backend/src/main/java/com/kama/notes/model/dto/category/CreateCategoryBody.java new file mode 100644 index 0000000..4aaeb5b --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/category/CreateCategoryBody.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/category/UpdateCategoryBody.java b/backend/src/main/java/com/kama/notes/model/dto/category/UpdateCategoryBody.java new file mode 100644 index 0000000..52d280e --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/category/UpdateCategoryBody.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/collection/CollectionQueryParams.java b/backend/src/main/java/com/kama/notes/model/dto/collection/CollectionQueryParams.java new file mode 100644 index 0000000..a9693e5 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/collection/CollectionQueryParams.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/collection/CreateCollectionBody.java b/backend/src/main/java/com/kama/notes/model/dto/collection/CreateCollectionBody.java new file mode 100644 index 0000000..cbf8841 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/collection/CreateCollectionBody.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/collection/UpdateCollectionBody.java b/backend/src/main/java/com/kama/notes/model/dto/collection/UpdateCollectionBody.java new file mode 100644 index 0000000..cffe7f9 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/collection/UpdateCollectionBody.java @@ -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; + } +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/note/CreateNoteRequest.java b/backend/src/main/java/com/kama/notes/model/dto/note/CreateNoteRequest.java new file mode 100644 index 0000000..af93110 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/note/CreateNoteRequest.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/note/NoteQueryParams.java b/backend/src/main/java/com/kama/notes/model/dto/note/NoteQueryParams.java new file mode 100644 index 0000000..3c9c20e --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/note/NoteQueryParams.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/note/UpdateNoteRequest.java b/backend/src/main/java/com/kama/notes/model/dto/note/UpdateNoteRequest.java new file mode 100644 index 0000000..5298ab4 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/note/UpdateNoteRequest.java @@ -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; +} \ No newline at end of file diff --git a/backend/src/main/java/com/kama/notes/model/dto/notification/NotificationDTO.java b/backend/src/main/java/com/kama/notes/model/dto/notification/NotificationDTO.java new file mode 100644 index 0000000..39e9696 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/notification/NotificationDTO.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/question/CreateQuestionBody.java b/backend/src/main/java/com/kama/notes/model/dto/question/CreateQuestionBody.java new file mode 100644 index 0000000..3b637ed --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/question/CreateQuestionBody.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/question/QuestionQueryParam.java b/backend/src/main/java/com/kama/notes/model/dto/question/QuestionQueryParam.java new file mode 100644 index 0000000..0118caf --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/question/QuestionQueryParam.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/question/SearchQuestionBody.java b/backend/src/main/java/com/kama/notes/model/dto/question/SearchQuestionBody.java new file mode 100644 index 0000000..800a316 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/question/SearchQuestionBody.java @@ -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: 后续需要完善,添加分页功能 +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/question/UpdateQuestionBody.java b/backend/src/main/java/com/kama/notes/model/dto/question/UpdateQuestionBody.java new file mode 100644 index 0000000..5ebbd98 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/question/UpdateQuestionBody.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/questionList/CreateQuestionListBody.java b/backend/src/main/java/com/kama/notes/model/dto/questionList/CreateQuestionListBody.java new file mode 100644 index 0000000..6617374 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/questionList/CreateQuestionListBody.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/questionList/UpdateQuestionListBody.java b/backend/src/main/java/com/kama/notes/model/dto/questionList/UpdateQuestionListBody.java new file mode 100644 index 0000000..7c68d9a --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/questionList/UpdateQuestionListBody.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/questionListItem/CreateQuestionListItemBody.java b/backend/src/main/java/com/kama/notes/model/dto/questionListItem/CreateQuestionListItemBody.java new file mode 100644 index 0000000..dcee1e9 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/questionListItem/CreateQuestionListItemBody.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/questionListItem/QuestionListItemQueryParams.java b/backend/src/main/java/com/kama/notes/model/dto/questionListItem/QuestionListItemQueryParams.java new file mode 100644 index 0000000..68331e5 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/questionListItem/QuestionListItemQueryParams.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/questionListItem/SortQuestionListItemBody.java b/backend/src/main/java/com/kama/notes/model/dto/questionListItem/SortQuestionListItemBody.java new file mode 100644 index 0000000..93b3282 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/questionListItem/SortQuestionListItemBody.java @@ -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 questionIds; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/statistic/StatisticQueryParam.java b/backend/src/main/java/com/kama/notes/model/dto/statistic/StatisticQueryParam.java new file mode 100644 index 0000000..e5bb645 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/statistic/StatisticQueryParam.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/user/LoginRequest.java b/backend/src/main/java/com/kama/notes/model/dto/user/LoginRequest.java new file mode 100644 index 0000000..fea0a23 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/user/LoginRequest.java @@ -0,0 +1,29 @@ +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; + +/** + * 登录请求DTO + */ +@Data +public class LoginRequest { + /* + * 用户账号 + */ + @NotBlank(message = "用户账号不能为空") + @Size(min = 6, max = 32, message = "账号长度必须在 6 到 32 个字符之间") + @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "账号只能包含字母、数字和下划线") + private String account; + + /** + * 登录密码 + * 必填,长度 6-32 + */ + @NotBlank(message = "密码不能为空") + @Size(min = 6, max = 32, message = "密码长度必须在 6 到 32 个字符之间") + private String password; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/user/RegisterRequest.java b/backend/src/main/java/com/kama/notes/model/dto/user/RegisterRequest.java new file mode 100644 index 0000000..52d458d --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/user/RegisterRequest.java @@ -0,0 +1,40 @@ +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; + +/** + * 用户注册请求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; +} \ No newline at end of file diff --git a/backend/src/main/java/com/kama/notes/model/dto/user/UpdateUserRequest.java b/backend/src/main/java/com/kama/notes/model/dto/user/UpdateUserRequest.java new file mode 100644 index 0000000..0032086 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/user/UpdateUserRequest.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/dto/user/UploadImageResponse.java b/backend/src/main/java/com/kama/notes/model/dto/user/UploadImageResponse.java new file mode 100644 index 0000000..a2ad007 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/user/UploadImageResponse.java @@ -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; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/kama/notes/model/dto/user/UserQueryParam.java b/backend/src/main/java/com/kama/notes/model/dto/user/UserQueryParam.java new file mode 100644 index 0000000..5059e60 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/dto/user/UserQueryParam.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/entity/Category.java b/backend/src/main/java/com/kama/notes/model/entity/Category.java new file mode 100644 index 0000000..1acfa0c --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/entity/Category.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/entity/Collection.java b/backend/src/main/java/com/kama/notes/model/entity/Collection.java new file mode 100644 index 0000000..9dfb0f7 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/entity/Collection.java @@ -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; +} \ No newline at end of file diff --git a/backend/src/main/java/com/kama/notes/model/entity/CollectionNote.java b/backend/src/main/java/com/kama/notes/model/entity/CollectionNote.java new file mode 100644 index 0000000..00dc399 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/entity/CollectionNote.java @@ -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; +} diff --git a/backend/src/main/java/com/kama/notes/model/entity/Note.java b/backend/src/main/java/com/kama/notes/model/entity/Note.java new file mode 100644 index 0000000..f0921dd --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/entity/Note.java @@ -0,0 +1,61 @@ +package com.kama.notes.model.entity; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Date; + +/** + * @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; +} diff --git a/backend/src/main/java/com/kama/notes/model/entity/NoteLike.java b/backend/src/main/java/com/kama/notes/model/entity/NoteLike.java new file mode 100644 index 0000000..83720f8 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/entity/NoteLike.java @@ -0,0 +1,34 @@ +package com.kama.notes.model.entity; + +import lombok.Data; +import java.util.Date; + +/** + * @ClassName NoteLike + * @Description 笔记点赞关联实体类 + * @Author Tong + * @LastChangeDate 2024-12-16 20:04 + * @Version v1.0 + */ +@Data +public class NoteLike { + /* + * 笔记ID(联合主键) + */ + private Integer noteId; + + /* + * 点赞用户ID(联合主键) + */ + private Long userId; + + /* + * 创建时间 + */ + private Date createdAt; + + /* + * 更新时间 + */ + private Date updatedAt; +} diff --git a/backend/src/main/java/com/kama/notes/model/entity/Question.java b/backend/src/main/java/com/kama/notes/model/entity/Question.java new file mode 100644 index 0000000..a5ccbcd --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/entity/Question.java @@ -0,0 +1,57 @@ +package com.kama.notes.model.entity; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Date; + +/** + * @ClassName Question + * @Description 问题实体类 + * @Author Tong + * @LastChangeDate 2024-12-16 19:56 + * @Version v1.0 + */ +@Data +public class Question { + /* + * 问题ID(主键) + */ + private Integer questionId; + + /* + * 问题所属分类ID + */ + private Integer categoryId; + + /* + * 问题标题 + */ + private String title; + + /* + * 问题难度 + * 1=简单,2=中等,3=困难 + */ + private Integer difficulty; + + /* + * 题目考点 + */ + private String examPoint; + + /* + * 浏览量 + */ + private Integer viewCount; + + /* + * 创建时间 + */ + private LocalDateTime createdAt; + + /* + * 更新时间 + */ + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/kama/notes/model/entity/QuestionList.java b/backend/src/main/java/com/kama/notes/model/entity/QuestionList.java new file mode 100644 index 0000000..d262694 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/entity/QuestionList.java @@ -0,0 +1,44 @@ +package com.kama.notes.model.entity; + +import lombok.Data; +import java.util.Date; + +/** + * @ClassName QuestionList + * @Description 题单实体类 + * @Author Tong + * @LastChangeDate 2024-12-16 20:14 + * @Version v1.0 + */ +@Data +public class QuestionList { + /* + * 题单ID(主键) + */ + private Integer questionListId; + + /* + * 题单名称 + */ + private String name; + + /** + * 题单类型 + */ + private Integer type; + + /* + * 题单描述 + */ + private String description; + + /* + * 创建时间 + */ + private Date createdAt; + + /* + * 更新时间 + */ + private Date updatedAt; +} diff --git a/backend/src/main/java/com/kama/notes/model/entity/QuestionListItem.java b/backend/src/main/java/com/kama/notes/model/entity/QuestionListItem.java new file mode 100644 index 0000000..6403c02 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/entity/QuestionListItem.java @@ -0,0 +1,39 @@ +package com.kama.notes.model.entity; + +import lombok.Data; +import java.util.Date; + +/** + * @ClassName QuestionListItem + * @Description 题单-题目关联实体类 + * @Author Tong + * @LastChangeDate 2024-12-16 20:15 + * @Version v1.0 + */ +@Data +public class QuestionListItem { + /* + * 题单ID(联合主键) + */ + private Integer questionListId; + + /* + * 题目ID(联合主键) + */ + private Integer questionId; + + /* + * 题单内题目的顺序,从1开始 + */ + private Integer rank; + + /* + * 创建时间 + */ + private Date createdAt; + + /* + * 更新时间 + */ + private Date updatedAt; +} diff --git a/backend/src/main/java/com/kama/notes/model/entity/Statistic.java b/backend/src/main/java/com/kama/notes/model/entity/Statistic.java new file mode 100644 index 0000000..44d869d --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/entity/Statistic.java @@ -0,0 +1,51 @@ +package com.kama.notes.model.entity; + +import lombok.Data; + +import java.time.LocalDate; + +/** + * 统计信息实体,包含登录、注册、笔记等统计数据 + */ +@Data +public class Statistic { + /** + * 主键 ID + */ + private Integer id; + + /** + * 当天登录次数 + */ + private Integer loginCount; + + /** + * 当天注册人数 + */ + private Integer registerCount; + + /** + * 累计注册总人数 + */ + private Integer totalRegisterCount; + + /** + * 当天笔记数量 + */ + private Integer noteCount; + + /** + * 当天提交的笔记数量 + */ + private Integer submitNoteCount; + + /** + * 累计笔记总数量 + */ + private Integer totalNoteCount; + + /** + * 统计日期 + */ + private LocalDate date; +} diff --git a/backend/src/main/java/com/kama/notes/model/entity/User.java b/backend/src/main/java/com/kama/notes/model/entity/User.java new file mode 100644 index 0000000..fc07b0b --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/entity/User.java @@ -0,0 +1,103 @@ +package com.kama.notes.model.entity; + +import lombok.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * @ClassName User + * @Description 用户实体类 + * @Author Tong + * @LastChangeDate 2024-12-16 10:27 + * @Version v1.0 + */ +@Data +@Getter +@Setter +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class User { + /** + * 用户ID(主键) + * 系统分配不可修改 + */ + private Long userId; + + /** + * 账号(唯一) + * 注册时自定义,注册后不可修改 + * 包含数字、字母、下划线 + */ + private String account; + + /** + * 用户名 + * 可修改,包含中文、字母、数字、下划线 + */ + private String username; + + /** + * 加密后的登录密码 + */ + private String password; + + /** + * 用户性别 + * 1=男,2=女,3=保密 + */ + private Integer gender; + + /** + * 用户生日 + */ + private LocalDate birthday; + + /** + * 头像地址 + */ + private String avatarUrl; + + /** + * 用户邮箱 + */ + private String email; + + /** + * 用户学校 + */ + private String school; + + /** + * 用户签名 + */ + private String signature; + + /** + * 封禁状态 + * 0=未封禁,1=已封禁 + */ + private Integer isBanned; + + /** + * 管理员状态 + * 0=普通用户,1=管理员 + */ + private Integer isAdmin; + + /** + * 最后登录时间 + */ + private LocalDateTime lastLoginAt; + + /** + * 创建时间 + */ + private LocalDateTime createdAt; + + /** + * 更新时间 + */ + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/kama/notes/model/enums/questionList/QuestionListType.java b/backend/src/main/java/com/kama/notes/model/enums/questionList/QuestionListType.java new file mode 100644 index 0000000..6236cee --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/enums/questionList/QuestionListType.java @@ -0,0 +1,15 @@ +package com.kama.notes.model.enums.questionList; + +import lombok.Getter; + +@Getter +public enum QuestionListType { + COMMON(1, "普通题单"), + TRAINING_CAMP(2, "训练营题单"); + private final Integer type; + private final String desc; + QuestionListType(Integer type, String desc) { + this.type = type; + this.desc = desc; + } +} diff --git a/backend/src/main/java/com/kama/notes/model/enums/user/UserBanned.java b/backend/src/main/java/com/kama/notes/model/enums/user/UserBanned.java new file mode 100644 index 0000000..e241e2d --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/enums/user/UserBanned.java @@ -0,0 +1,6 @@ +package com.kama.notes.model.enums.user; + +public class UserBanned { + public static final Integer NOT_BANNED = 0; + public static final Integer IS_BANNED = 1; +} diff --git a/backend/src/main/java/com/kama/notes/model/enums/user/UserGender.java b/backend/src/main/java/com/kama/notes/model/enums/user/UserGender.java new file mode 100644 index 0000000..0fbb40d --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/enums/user/UserGender.java @@ -0,0 +1,7 @@ +package com.kama.notes.model.enums.user; + +public class UserGender { + public static final Integer MALE = 1; + public static final Integer FEMALE = 2; + public static final Integer SECRET = 3; +} diff --git a/backend/src/main/java/com/kama/notes/model/enums/user/UserRole.java b/backend/src/main/java/com/kama/notes/model/enums/user/UserRole.java new file mode 100644 index 0000000..3535fbd --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/enums/user/UserRole.java @@ -0,0 +1,6 @@ +package com.kama.notes.model.enums.user; + +public class UserRole { + public static final Integer NOT_ADMIN = 0; + public static final Integer IS_ADMIN = 1; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/category/CategoryVO.java b/backend/src/main/java/com/kama/notes/model/vo/category/CategoryVO.java new file mode 100644 index 0000000..737c259 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/category/CategoryVO.java @@ -0,0 +1,20 @@ +package com.kama.notes.model.vo.category; + +import lombok.Data; + +import java.util.List; + +@Data +public class CategoryVO { + private Integer categoryId; + private String name; + private Integer parentCategoryId; + private List children; + + @Data + public static class ChildrenCategoryVO { + private Integer categoryId; + private String name; + private Integer parentCategoryId; + } +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/category/CreateCategoryVO.java b/backend/src/main/java/com/kama/notes/model/vo/category/CreateCategoryVO.java new file mode 100644 index 0000000..041575d --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/category/CreateCategoryVO.java @@ -0,0 +1,8 @@ +package com.kama.notes.model.vo.category; + +import lombok.Data; + +@Data +public class CreateCategoryVO { + private Integer categoryId; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/collection/CollectionVO.java b/backend/src/main/java/com/kama/notes/model/vo/collection/CollectionVO.java new file mode 100644 index 0000000..aae0289 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/collection/CollectionVO.java @@ -0,0 +1,20 @@ +package com.kama.notes.model.vo.collection; + +import lombok.Data; + +@Data +public class CollectionVO { + private Integer collectionId; + private String name; + private String description; + /** + * 查询收藏夹时,可能会携带的 noteId 参数,这个 noteStatus 可以用来判断该 note 是否被收藏 + */ + private NoteStatus noteStatus; + + @Data + public static class NoteStatus { + private Integer noteId; + private Boolean isCollected; + } +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/collection/CreateCollectionVO.java b/backend/src/main/java/com/kama/notes/model/vo/collection/CreateCollectionVO.java new file mode 100644 index 0000000..842801d --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/collection/CreateCollectionVO.java @@ -0,0 +1,8 @@ +package com.kama.notes.model.vo.collection; + +import lombok.Data; + +@Data +public class CreateCollectionVO { + private Integer collectionId; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/note/CreateNoteVO.java b/backend/src/main/java/com/kama/notes/model/vo/note/CreateNoteVO.java new file mode 100644 index 0000000..fc3641c --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/note/CreateNoteVO.java @@ -0,0 +1,8 @@ +package com.kama.notes.model.vo.note; + +import lombok.Data; + +@Data +public class CreateNoteVO { + private Integer noteId; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/note/DownloadNoteVO.java b/backend/src/main/java/com/kama/notes/model/vo/note/DownloadNoteVO.java new file mode 100644 index 0000000..0fd1878 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/note/DownloadNoteVO.java @@ -0,0 +1,8 @@ +package com.kama.notes.model.vo.note; + +import lombok.Data; + +@Data +public class DownloadNoteVO { + private String markdown; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/note/NoteHeatMapItem.java b/backend/src/main/java/com/kama/notes/model/vo/note/NoteHeatMapItem.java new file mode 100644 index 0000000..756a374 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/note/NoteHeatMapItem.java @@ -0,0 +1,12 @@ +package com.kama.notes.model.vo.note; + +import lombok.Data; + +import java.time.LocalDate; + +@Data +public class NoteHeatMapItem { + private LocalDate date; + private Integer count; + private Integer rank; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/note/NoteRankListItem.java b/backend/src/main/java/com/kama/notes/model/vo/note/NoteRankListItem.java new file mode 100644 index 0000000..8ea9983 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/note/NoteRankListItem.java @@ -0,0 +1,12 @@ +package com.kama.notes.model.vo.note; + +import lombok.Data; + +@Data +public class NoteRankListItem { + private Long userId; + private String username; + private String avatarUrl; + private Integer noteCount; + private Integer rank; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/note/NoteVO.java b/backend/src/main/java/com/kama/notes/model/vo/note/NoteVO.java new file mode 100644 index 0000000..e30e558 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/note/NoteVO.java @@ -0,0 +1,39 @@ +package com.kama.notes.model.vo.note; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class NoteVO { + private Integer noteId; + private String content; + private Boolean needCollapsed = false; + private String displayContent; + private Integer likeCount; + private Integer commentCount; + private Integer collectCount; + private LocalDateTime createdAt; + private SimpleAuthorVO author; + private UserActionsVO userActions; + private SimpleQuestionVO question; + + @Data + public static class SimpleAuthorVO { + private Long userId; + private String username; + private String avatarUrl; + } + + @Data + public static class UserActionsVO { + private Boolean isLiked = false; + private Boolean isCollected = false; + } + + @Data + public static class SimpleQuestionVO { + private Integer questionId; + private String title; + } +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/note/Top3Count.java b/backend/src/main/java/com/kama/notes/model/vo/note/Top3Count.java new file mode 100644 index 0000000..c74f883 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/note/Top3Count.java @@ -0,0 +1,9 @@ +package com.kama.notes.model.vo.note; + +import lombok.Data; + +@Data +public class Top3Count { + private Integer lastMonthTop3Count; + private Integer thisMonthTop3Count; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/notification/NotificationVO.java b/backend/src/main/java/com/kama/notes/model/vo/notification/NotificationVO.java new file mode 100644 index 0000000..aab6be7 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/notification/NotificationVO.java @@ -0,0 +1,8 @@ +package com.kama.notes.model.vo.notification; + +import lombok.Data; + +@Data +public class NotificationVO { + private String content; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/question/BaseQuestionVO.java b/backend/src/main/java/com/kama/notes/model/vo/question/BaseQuestionVO.java new file mode 100644 index 0000000..0924bdf --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/question/BaseQuestionVO.java @@ -0,0 +1,37 @@ +package com.kama.notes.model.vo.question; + +import lombok.Data; + +@Data +public class BaseQuestionVO { + /* + * 问题ID(主键) + */ + private Integer questionId; + + /* + * 问题所属分类ID + */ + private Integer categoryId; + + /* + * 问题标题 + */ + private String title; + + /* + * 问题难度 + * 1=简单,2=中等,3=困难 + */ + private Integer difficulty; + + /* + * 题目考点 + */ + private String examPoint; + + /* + * 浏览量 + */ + private Integer viewCount; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/question/CreateQuestionVO.java b/backend/src/main/java/com/kama/notes/model/vo/question/CreateQuestionVO.java new file mode 100644 index 0000000..2048d04 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/question/CreateQuestionVO.java @@ -0,0 +1,8 @@ +package com.kama.notes.model.vo.question; + +import lombok.Data; + +@Data +public class CreateQuestionVO { + private Integer questionId; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/question/QuestionNoteVO.java b/backend/src/main/java/com/kama/notes/model/vo/question/QuestionNoteVO.java new file mode 100644 index 0000000..40b99fd --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/question/QuestionNoteVO.java @@ -0,0 +1,53 @@ +package com.kama.notes.model.vo.question; + +import lombok.Data; + +@Data +public class QuestionNoteVO { + /* + * 问题ID(主键) + */ + private Integer questionId; + + /* + * 问题标题 + */ + private String title; + + /* + * 问题难度 + * 1=简单,2=中等,3=困难 + */ + private Integer difficulty; + + /* + * 题目考点 + */ + private String examPoint; + + /* + * 浏览量 + */ + private Integer viewCount; + + /** + * 关于这道题用户的详细信息 + */ + private UserNote userNote; + + @Data + public static class UserNote { + /* + * 是否完成 + */ + private boolean finished = false; + /** + * noteId + */ + private Integer noteId; + /** + * 笔记内容 + */ + private String content; + } +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/question/QuestionUserVO.java b/backend/src/main/java/com/kama/notes/model/vo/question/QuestionUserVO.java new file mode 100644 index 0000000..9059b41 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/question/QuestionUserVO.java @@ -0,0 +1,43 @@ +package com.kama.notes.model.vo.question; + +import lombok.Data; + +// 用于普通用户查询携带个人信息的问题 VO +@Data +public class QuestionUserVO { + /* + * 问题ID(主键) + */ + private Integer questionId; + + /* + * 问题标题 + */ + private String title; + + /* + * 问题难度 + * 1=简单,2=中等,3=困难 + */ + private Integer difficulty; + + /* + * 题目考点 + */ + private String examPoint; + + /* + * 浏览量 + */ + private Integer viewCount; + + /** + * 用户问题状态 + */ + private UserQuestionStatus userQuestionStatus; + + @Data + public static class UserQuestionStatus { + private boolean finished = false; // 用户是否完成过这道题 + } +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/question/QuestionVO.java b/backend/src/main/java/com/kama/notes/model/vo/question/QuestionVO.java new file mode 100644 index 0000000..d186532 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/question/QuestionVO.java @@ -0,0 +1,18 @@ +package com.kama.notes.model.vo.question; + +import lombok.Data; + +import java.time.LocalDateTime; + +// 用于管理员批量查询题目 +@Data +public class QuestionVO { + private Integer questionId; + private Integer categoryId; + private String title; + private Integer difficulty; + private String examPoint; + private Integer viewCount; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/question/SimpleQuestionVO.java b/backend/src/main/java/com/kama/notes/model/vo/question/SimpleQuestionVO.java new file mode 100644 index 0000000..7aaab16 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/question/SimpleQuestionVO.java @@ -0,0 +1,9 @@ +package com.kama.notes.model.vo.question; + +import lombok.Data; + +@Data +public class SimpleQuestionVO { + private Integer questionId; + private String title; +} \ No newline at end of file diff --git a/backend/src/main/java/com/kama/notes/model/vo/questionList/CreateQuestionListVO.java b/backend/src/main/java/com/kama/notes/model/vo/questionList/CreateQuestionListVO.java new file mode 100644 index 0000000..2121c56 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/questionList/CreateQuestionListVO.java @@ -0,0 +1,8 @@ +package com.kama.notes.model.vo.questionList; + +import lombok.Data; + +@Data +public class CreateQuestionListVO { + private Integer questionListId; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/questionList/QuestionListVO.java b/backend/src/main/java/com/kama/notes/model/vo/questionList/QuestionListVO.java new file mode 100644 index 0000000..742e08c --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/questionList/QuestionListVO.java @@ -0,0 +1,5 @@ +package com.kama.notes.model.vo.questionList; + +public class QuestionListVO { + +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/questionListItem/CreateQuestionListItemVO.java b/backend/src/main/java/com/kama/notes/model/vo/questionListItem/CreateQuestionListItemVO.java new file mode 100644 index 0000000..b24377d --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/questionListItem/CreateQuestionListItemVO.java @@ -0,0 +1,8 @@ +package com.kama.notes.model.vo.questionListItem; + +import lombok.Data; + +@Data +public class CreateQuestionListItemVO { + private Integer rank; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/questionListItem/QuestionListItemUserVO.java b/backend/src/main/java/com/kama/notes/model/vo/questionListItem/QuestionListItemUserVO.java new file mode 100644 index 0000000..1706d3f --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/questionListItem/QuestionListItemUserVO.java @@ -0,0 +1,32 @@ +package com.kama.notes.model.vo.questionListItem; + +import com.kama.notes.model.vo.question.BaseQuestionVO; +import lombok.Data; + +@Data +public class QuestionListItemUserVO { + /** + * 题单ID(联合主键) + */ + private Integer questionListId; + + /** + * 题目ID(联合主键) + */ + private BaseQuestionVO question; + + /** + * 用户是否完成了这道题 + */ + private UserQuestionStatus userQuestionStatus; + + /* + * 题单内题目的顺序,从1开始 + */ + private Integer rank; + + @Data + public static class UserQuestionStatus { + private boolean finished; + } +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/questionListItem/QuestionListItemVO.java b/backend/src/main/java/com/kama/notes/model/vo/questionListItem/QuestionListItemVO.java new file mode 100644 index 0000000..27889fc --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/questionListItem/QuestionListItemVO.java @@ -0,0 +1,22 @@ +package com.kama.notes.model.vo.questionListItem; + +import com.kama.notes.model.vo.question.BaseQuestionVO; +import lombok.Data; + +@Data +public class QuestionListItemVO { + /* + * 题单ID(联合主键) + */ + private Integer questionListId; + + /* + * 题目ID(联合主键) + */ + private BaseQuestionVO question; + + /* + * 题单内题目的顺序,从1开始 + */ + private Integer rank; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/upload/ImageVO.java b/backend/src/main/java/com/kama/notes/model/vo/upload/ImageVO.java new file mode 100644 index 0000000..888d61b --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/upload/ImageVO.java @@ -0,0 +1,8 @@ +package com.kama.notes.model.vo.upload; + +import lombok.Data; + +@Data +public class ImageVO { + private String url; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/user/AvatarVO.java b/backend/src/main/java/com/kama/notes/model/vo/user/AvatarVO.java new file mode 100644 index 0000000..cf4963f --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/user/AvatarVO.java @@ -0,0 +1,8 @@ +package com.kama.notes.model.vo.user; + +import lombok.Data; + +@Data +public class AvatarVO { + private String url; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/user/LoginUserVO.java b/backend/src/main/java/com/kama/notes/model/vo/user/LoginUserVO.java new file mode 100644 index 0000000..150d7d1 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/user/LoginUserVO.java @@ -0,0 +1,62 @@ +package com.kama.notes.model.vo.user; + +import lombok.Data; + +import java.time.LocalDate; + +/** + * LoginUserVO 是当前登录的用户,承载自己的信息的 VO + * 而 UserVO 是当前登录的用户,获取的其他的用户的信息 + */ +@Data +public class LoginUserVO { + /* + * 用户ID + */ + private Long userId; + + /* + * 用户账号 + */ + private String account; + + /* + * 用户昵称 + */ + private String username; + + /* + * 用户性别 + */ + private Integer gender; + + /* + * 用户生日 + */ + private LocalDate birthday; + + /* + * 用户头像 + */ + private String avatarUrl; + + /* + * 用户邮箱 + */ + private String email; + + /* + * 用户学校 + */ + private String school; + + /* + * 用户签名 + */ + private String signature; + + /* + * 是否管理员 + */ + private Integer isAdmin; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/user/RegisterVO.java b/backend/src/main/java/com/kama/notes/model/vo/user/RegisterVO.java new file mode 100644 index 0000000..44643e8 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/user/RegisterVO.java @@ -0,0 +1,8 @@ +package com.kama.notes.model.vo.user; + +import lombok.Data; + +@Data +public class RegisterVO { + private Long userId; +} diff --git a/backend/src/main/java/com/kama/notes/model/vo/user/UserVO.java b/backend/src/main/java/com/kama/notes/model/vo/user/UserVO.java new file mode 100644 index 0000000..953497a --- /dev/null +++ b/backend/src/main/java/com/kama/notes/model/vo/user/UserVO.java @@ -0,0 +1,46 @@ +package com.kama.notes.model.vo.user; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 当前登录的用户获取别人的信息的 VO + */ +@Data +public class UserVO { + /* + * 用户昵称 + */ + private String username; + + /* + * 用户性别 + */ + private Integer gender; + + /* + * 用户头像 + */ + private String avatarUrl; + + /* + * 用户邮箱 + */ + private String email; + + /* + * 用户学校 + */ + private String school; + + /* + * 用户签名 + */ + private String signature; + + /** + * 最后登录时间 + */ + private LocalDateTime lastLoginAt; +} diff --git a/backend/src/main/java/com/kama/notes/scope/RequestScopeData.java b/backend/src/main/java/com/kama/notes/scope/RequestScopeData.java new file mode 100644 index 0000000..a86ed8b --- /dev/null +++ b/backend/src/main/java/com/kama/notes/scope/RequestScopeData.java @@ -0,0 +1,18 @@ +package com.kama.notes.scope; + +import lombok.Data; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * 用于存放当前请求生命周期内的全局数据 + */ +@Component +@RequestScope +@Data +public class RequestScopeData { + private String token; + private Long userId; + private boolean isLogin; + // private boolean isAdmin; +} diff --git a/backend/src/main/java/com/kama/notes/service/CategoryService.java b/backend/src/main/java/com/kama/notes/service/CategoryService.java new file mode 100644 index 0000000..73eafd4 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/CategoryService.java @@ -0,0 +1,56 @@ +package com.kama.notes.service; + +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 org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Transactional +public interface CategoryService { + /** + * 构建分类树 + * + * @return 返回一个包含所有分类信息的列表 + */ + List buildCategoryTree(); + + /** + * 获取所有分类 + * @return 分类列表 + */ + ApiResponse> categoryList(); + + /** + * 删除分类及其子分类 + * 该方法旨在删除指定的分类及其所有子分类,并且会将隶属与分类下的所有问题都删除 + * + * @param categoryId 要删除的分类ID这是要删除分类及其子分类的唯一标识 + * @return 返回删除结果 + */ + ApiResponse deleteCategory(Integer categoryId); + + /** + * 创建新分类 + * 此方法用于接收一个 Category对象并将其保存到数据库中,在保存之前会进行必要的验证 + * 如果保存成功,将返回包含新创建分类信息的 CategoryVO 对象 + * + * @param createCategoryBody 要创建的分类对象,包含分类的名称、描述等信息 + * @return 返回一个 ApiResponse 对象,包含HTTP状态码和可能的错误信息,以及新创建的CategoryVO对象 + */ + ApiResponse createCategory(CreateCategoryBody createCategoryBody); + + /** + * 更新分类信息 + * 此方法用于更新指定 ID 的分类信息,包括名称字段 + * + * @param categoryId 要更新的分类的ID + * @param updateCategoryBody 包含要更新的分类信息的 Category 对象 + * @return 返回一个ApiResponse对象,包含HTTP状态码和可能的错误信息,以及更新后的CategoryVO对象 + */ + ApiResponse updateCategory(Integer categoryId, UpdateCategoryBody updateCategoryBody); +} diff --git a/backend/src/main/java/com/kama/notes/service/CollectionNoteService.java b/backend/src/main/java/com/kama/notes/service/CollectionNoteService.java new file mode 100644 index 0000000..2d304f7 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/CollectionNoteService.java @@ -0,0 +1,18 @@ +package com.kama.notes.service; + +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Set; + +@Transactional +public interface CollectionNoteService { + /** + * 查询用户收藏的笔记 Id + * + * @param userId 用户 ID + * @param noteIds 查询的笔记 ID 列表范围 + * @return 返回 noteIds 中被用户收藏的笔记 ID + */ + Set findUserCollectedNoteIds(Long userId, List noteIds); +} diff --git a/backend/src/main/java/com/kama/notes/service/CollectionService.java b/backend/src/main/java/com/kama/notes/service/CollectionService.java new file mode 100644 index 0000000..a96403d --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/CollectionService.java @@ -0,0 +1,46 @@ +package com.kama.notes.service; + +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 org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Transactional +public interface CollectionService { + /** + * 获取收藏夹列表 + * + * @param queryParams 收藏夹查询参数 + * @return 收藏夹列表 + */ + ApiResponse> getCollections(CollectionQueryParams queryParams); + + /** + * 创建收藏夹 + * + * @param requestBody 收藏夹信息 + * @return 收藏夹信息,包含收藏夹 Id + */ + ApiResponse createCollection(CreateCollectionBody requestBody); + + /** + * 删除收藏夹 + * @param collectionId 收藏夹 Id + * @return 占位 + */ + ApiResponse deleteCollection(Integer collectionId); + + /** + * 批量收藏或者取消收藏笔记 + * @param requestBody 包含收藏夹 Id 和笔记 Id 的对象 + * action 为 create 时为收藏,为 delete 时为取消收藏 + * @return 占位 + */ + ApiResponse batchModifyCollection(UpdateCollectionBody requestBody); +} diff --git a/backend/src/main/java/com/kama/notes/service/FileService.java b/backend/src/main/java/com/kama/notes/service/FileService.java new file mode 100644 index 0000000..b13c177 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/FileService.java @@ -0,0 +1,22 @@ +package com.kama.notes.service; + + +import org.springframework.web.multipart.MultipartFile; + +public interface FileService { + /** + * 上传文件,并返回文件的访问路径或存储位置 + * + * @param file 上传的文件 + * @return 存储后的文件URL或路径 + */ + String uploadFile(MultipartFile file); + + /** + * 上传图片,并返回图片的访问路径或存储位置 + * + * @param file 上传的图片文件 + * @return 存储后的图片URL或路径 + */ + String uploadImage(MultipartFile file); +} diff --git a/backend/src/main/java/com/kama/notes/service/NoteLikeService.java b/backend/src/main/java/com/kama/notes/service/NoteLikeService.java new file mode 100644 index 0000000..25a1af3 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/NoteLikeService.java @@ -0,0 +1,38 @@ +package com.kama.notes.service; + +import com.kama.notes.model.base.ApiResponse; +import com.kama.notes.model.base.EmptyVO; +import com.kama.notes.model.entity.NoteLike; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Set; + +@Transactional +public interface NoteLikeService { + + /** + * 查询用户点赞过的笔记 ID + * + * @param userId 用户 ID + * @param noteIds 笔记 ID + * @return 用户点赞的笔记 ID 集合 + */ + Set findUserLikedNoteIds(Long userId, List noteIds); + + /** + * 点赞笔记 + * + * @param noteId 笔记的唯一标识符 + * @return 返回一个包含操作结果的ApiResponse对象 + */ + ApiResponse likeNote(Integer noteId); + + /** + * 取消点赞笔记 + * + * @param noteId 笔记的唯一标识符 + * @return 返回一个包含操作结果的ApiResponse对象 + */ + ApiResponse unlikeNote(Integer noteId); +} diff --git a/backend/src/main/java/com/kama/notes/service/NoteService.java b/backend/src/main/java/com/kama/notes/service/NoteService.java new file mode 100644 index 0000000..41a4d91 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/NoteService.java @@ -0,0 +1,71 @@ +package com.kama.notes.service; + +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.*; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Transactional +public interface NoteService { + /** + * 查询笔记列表 + * + * @param params 查询参数对象,用于指定过滤条件(如关键词、时间范围等) + * @return 包含符合查询条件的笔记视图对象列表的响应 + */ + ApiResponse> getNotes(NoteQueryParams params); + + /** + * 发布笔记 + * + * @param request 发布笔记的请求对象,包含笔记内容、标题等信息 + * @return 包含新创建的笔记视图对象的响应 + */ + ApiResponse createNote(CreateNoteRequest request); + + /** + * 更新笔记 + * + * @param noteId 笔记ID,用于标识需要更新的笔记 + * @param request 更新笔记的请求对象,包含要更新的内容和属性 + * @return 空视图对象的响应,表示更新操作成功 + */ + ApiResponse updateNote(Integer noteId, UpdateNoteRequest request); + + /** + * 删除笔记 + * + * @param noteId 笔记ID,用于标识需要删除的笔记 + * @return 空视图对象的响应,表示删除操作成功 + */ + ApiResponse deleteNote(Integer noteId); + + /** + * 下载笔记 + * @return 包含下载笔记 + */ + ApiResponse downloadNote(); + + /** + * 笔记排行榜 + * @return 包含笔记排行榜视图对象的响应 + */ + ApiResponse> submitNoteRank(); + + /** + * 用户提交热力图 + * @return 用户提交热力图 + */ + ApiResponse> submitNoteHeatMap(); + + /** + * 用户提交top3Count + * @return 用户提交top3Count + */ + ApiResponse submitNoteTop3Count(); +} diff --git a/backend/src/main/java/com/kama/notes/service/QuestionListItemService.java b/backend/src/main/java/com/kama/notes/service/QuestionListItemService.java new file mode 100644 index 0000000..a34d67d --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/QuestionListItemService.java @@ -0,0 +1,59 @@ +package com.kama.notes.service; + +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.entity.QuestionListItem; +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 org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Transactional +public interface QuestionListItemService { + + /** + * 获取题单项(用户端) + * + * @param queryParams 查询参数 + * @return 返回一个包含题单项的ApiResponse对象 + */ + ApiResponse> userGetQuestionListItems(QuestionListItemQueryParams queryParams); + + /** + * 获取题单项(管理端) + * + * @param questionListId 题单的ID,用于指定获取哪个题单的项 + * @return 返回一个包含题单项的ApiResponse对象 + */ + ApiResponse> getQuestionListItems(Integer questionListId); + + /** + * 创建题单项 + * + * @param body 包含创建题单项所需信息的请求体 + * @return 返回一个包含创建题单项结果的ApiResponse对象 + */ + ApiResponse createQuestionListItem(CreateQuestionListItemBody body); + + /** + * 删除题单项 + * + * @param questionListId 题单的ID,用于指定从哪个题单中删除项 + * @param questionId 要删除的问题的ID + * @return 返回一个表示删除操作结果的ApiResponse对象 + */ + ApiResponse deleteQuestionListItem(Integer questionListId, Integer questionId); + + /** + * 对题单项进行排序 + * + * @param body 包含排序信息,包括题单ID和题单项ID列表 + * @return 返回一个表示排序操作结果的ApiResponse对象 + */ + ApiResponse sortQuestionListItem(SortQuestionListItemBody body); +} diff --git a/backend/src/main/java/com/kama/notes/service/QuestionListService.java b/backend/src/main/java/com/kama/notes/service/QuestionListService.java new file mode 100644 index 0000000..b8a491e --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/QuestionListService.java @@ -0,0 +1,52 @@ +package com.kama.notes.service; + +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 org.springframework.transaction.annotation.Transactional; + +import java.util.List; +@Transactional +public interface QuestionListService { + /** + * 获取题单 + * + * @return ApiResponse 包含题单的响应对象 + */ + ApiResponse getQuestionList(Integer questionListId); + + /** + * 获取题单列表 + * + * @return ApiResponse 包含题单的响应对象 + */ + ApiResponse> getQuestionLists(); + + /** + * 创建新的题单 + * + * @param body 包含创建题单所需信息的请求体 + * @return ApiResponse 包含新创建的题单信息的响应对象 + */ + ApiResponse createQuestionList(CreateQuestionListBody body); + + /** + * 删除题单 + * + * @param questionListId 要删除的题单的ID + * @return ApiResponse 表示删除操作结果的响应对象 + */ + ApiResponse deleteQuestionList(Integer questionListId); + + /** + * 更新题单信息 + * + * @param questionListId 要更新的题单的ID + * @param body 包含要更新的题单信息的请求体 + * @return ApiResponse 表示更新操作结果的响应对象 + */ + ApiResponse updateQuestionList(Integer questionListId, UpdateQuestionListBody body); +} diff --git a/backend/src/main/java/com/kama/notes/service/QuestionService.java b/backend/src/main/java/com/kama/notes/service/QuestionService.java new file mode 100644 index 0000000..7b0b035 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/QuestionService.java @@ -0,0 +1,95 @@ +package com.kama.notes.service; + +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.entity.Question; +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 org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; + +@Transactional +public interface QuestionService { + /** + * 根据问题 ID 获取问题信息 + * @param questionId 问题 ID + * @return 问题信息 + */ + Question findById(Integer questionId); + + /** + * 根据问题 ID 批量获取问题信息 + * + * @param questionIds 问题 ID 列表 + * @return 问题信息 + */ + Map getQuestionMapByIds(List questionIds); + + /** + * 根据查询参数获取问题列表 + * + * @param queryParams 问题查询参数对象,包含各种查询条件 + * @return 返回一个包含问题列表的ApiResponse对象 + */ + ApiResponse> getQuestions(QuestionQueryParam queryParams); + + /** + * 创建问题接口 + * 该方法用于提交一个新的问题,以便在系统中创建问题记录 + * + * @param createQuestionBody 包含要创建的问题的所有必要信息的请求体 + * @return 返回一个包含创建问题结果的ApiResponse对象,包括新创建问题的 ID + */ + ApiResponse createQuestion(CreateQuestionBody createQuestionBody); + + /** + * 更新问题信息 + * 该方法通过提供的问题 ID 和更新内容来修改现有问题的信息 + * + * @param questionId 问题的唯一标识符,用于定位哪个问题需要被更新 + * @param updateQuestionBody 包含了需要更新的问题信息的对象 + * @return 返回一个ApiResponse对象 + */ + ApiResponse updateQuestion(Integer questionId, UpdateQuestionBody updateQuestionBody); + + /** + * 删除问题 + * 该方法通过提供的问题 ID 来删除问题记录 + * + * @param questionId 问题的唯一标识符,用于定位要删除的问题 + * @return 返回一个ApiResponse对象 + */ + ApiResponse deleteQuestion(Integer questionId); + + /** + * 用户获取问题列表 + * + * @param queryParams 问题查询参数对象,包含各种查询条件如用户 ID + * @return 返回一个携带用户相关信息的题目列表的 ApiResponse 对象 + */ + ApiResponse> userGetQuestions(QuestionQueryParam queryParams); + + /** + * 用户获取单个问题 + * + * @param questionId 问题的唯一标识符,用于定位要获取的问题 + * @return 返回一个携带用户相关信息的题目的 ApiResponse 对象 + */ + ApiResponse userGetQuestion(Integer questionId); + + /** + * 搜索问题 + * + * @param body 包含搜索问题的请求体 + * @return 返回一个携带搜索结果的 ApiResponse 对象 + */ + ApiResponse> searchQuestions(SearchQuestionBody body); +} diff --git a/backend/src/main/java/com/kama/notes/service/RedisService.java b/backend/src/main/java/com/kama/notes/service/RedisService.java new file mode 100644 index 0000000..7c4be2b --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/RedisService.java @@ -0,0 +1,73 @@ +package com.kama.notes.service; + +/** + * Redis服务接口,定义了对Redis数据库的基本操作 + */ +public interface RedisService { + /** + * 保存数据到Redis + * + * @param key 数据的键 + * @param value 数据的值 + */ + void set(String key, Object value); + + /** + * 保存数据到Redis并设置过期时间 + * + * @param key 数据的键 + * @param value 数据的值 + * @param timeout 数据的过期时间,单位秒 + */ + void setWithExpiry(String key, Object value, long timeout); + + /** + * 从Redis获取数据 + * + * @param key 数据的键 + * @return 数据的值,如果键不存在则返回null + */ + Object get(String key); + + /** + * 从Redis删除数据 + * + * @param key 数据的键 + */ + void delete(String key); + + /** + * 判断Redis中是否存在指定的键 + * + * @param key 数据的键 + * @return 如果键存在返回true,否则返回false + */ + boolean exists(String key); + + /** + * 增加计数 + * + * @param key 数据的键 + * @param delta 增加的数值 + * @return 增加后的数值 + */ + Long increment(String key, long delta); + + /** + * 获取Hash类型数据的值 + * + * @param hashKey Hash的键 + * @param key 数据的键 + * @return 数据的值,如果键不存在则返回null + */ + Object getHashValue(String hashKey, String key); + + /** + * 设置Hash类型数据的值 + * + * @param hashKey Hash的键 + * @param key 数据的键 + * @param value 数据的值 + */ + void setHashValue(String hashKey, String key, Object value); +} diff --git a/backend/src/main/java/com/kama/notes/service/StatisticService.java b/backend/src/main/java/com/kama/notes/service/StatisticService.java new file mode 100644 index 0000000..87ea65a --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/StatisticService.java @@ -0,0 +1,18 @@ +package com.kama.notes.service; + +import com.kama.notes.model.base.ApiResponse; +import com.kama.notes.model.dto.statistic.StatisticQueryParam; +import com.kama.notes.model.entity.Statistic; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Transactional +public interface StatisticService { + /** + * 获取统计信息 + * @param queryParam 查询参数,用于指定统计条件 + * @return 返回一个ApiResponse对象,其中包含符合查询条件的统计信息列表 + */ + ApiResponse> getStatistic(StatisticQueryParam queryParam); +} diff --git a/backend/src/main/java/com/kama/notes/service/UploadService.java b/backend/src/main/java/com/kama/notes/service/UploadService.java new file mode 100644 index 0000000..91a04db --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/UploadService.java @@ -0,0 +1,12 @@ +package com.kama.notes.service; + +import com.kama.notes.model.base.ApiResponse; +import com.kama.notes.model.vo.upload.ImageVO; +import org.springframework.web.multipart.MultipartFile; + +public interface UploadService { + /** + * 上传图片 + */ + ApiResponse uploadImage(MultipartFile file); +} \ No newline at end of file diff --git a/backend/src/main/java/com/kama/notes/service/UserService.java b/backend/src/main/java/com/kama/notes/service/UserService.java new file mode 100644 index 0000000..482ebff --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/UserService.java @@ -0,0 +1,83 @@ +package com.kama.notes.service; + +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.RegisterVO; +import com.kama.notes.model.vo.user.LoginUserVO; +import com.kama.notes.model.vo.user.UserVO; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.Map; + +@Transactional +public interface UserService { + /** + * 用户注册服务 + * + * @param request 包含用户账号、密码、邮箱等注册信息的请求对象 + * @return 包含注册成功用户信息的响应对象 + */ + ApiResponse register(RegisterRequest request); + + /** + * 用户登录服务 + * + * @param request 包含用户账号和密码的登录请求对象 + * @return 包含登录成功后的用户信息(包括 token)的响应对象 + */ + ApiResponse login(LoginRequest request); + + /** + * 自动登录服务 + * + * @return 当前登录用户的信息(基于 token 验证的自动登录) + */ + ApiResponse whoami(); + + /** + * 查询用户信息服务 + * + * @param userId 需要查询的用户的唯一标识 ID + * @return 包含用户详细信息的响应对象 + */ + ApiResponse getUserInfo(Long userId); + + /** + * 更新用户信息服务 + * + * @param request 包含需要更新的用户信息(如用户名、头像、签名等)的请求对象 + * @return 更新后的用户信息响应对象 + */ + ApiResponse updateUserInfo(UpdateUserRequest request); + + /** + * 根据用户 ID 列表查询并转换为 Map 格式 + * + * @param authorIds 包含多个用户 ID 的列表 + * @return 一个 Map,其中键是 userId,值是对应的 User 对象 + */ + Map getUserMapByIds(List authorIds); + + /** + * 获取用户列表 + * + * @param userQueryParam 用户查询参数,包含查询用户列表的条件 + * @return 包含用户列表的 ApiResponse 对象 + */ + ApiResponse> getUserList(UserQueryParam userQueryParam); + + /** + * 上传用户头像 + * + * @param file 文件对象 + * @return 包含上传成功头像的 URL 的响应对象 + */ + ApiResponse uploadAvatar(MultipartFile file); +} diff --git a/backend/src/main/java/com/kama/notes/service/impl/CategoryServiceImpl.java b/backend/src/main/java/com/kama/notes/service/impl/CategoryServiceImpl.java new file mode 100644 index 0000000..0a097a5 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/impl/CategoryServiceImpl.java @@ -0,0 +1,144 @@ +package com.kama.notes.service.impl; + +import com.kama.notes.mapper.CategoryMapper; +import com.kama.notes.mapper.QuestionMapper; +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.entity.Category; +import com.kama.notes.model.vo.category.CategoryVO; +import com.kama.notes.model.vo.category.CreateCategoryVO; +import com.kama.notes.service.CategoryService; +import com.kama.notes.utils.ApiResponseUtil; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class CategoryServiceImpl implements CategoryService { + + @Autowired + private CategoryMapper categoryMapper; + + @Autowired + private QuestionMapper QuestionMapper; + + public List buildCategoryTree() { + // 获取所有分类 + List categories = categoryMapper.categoryList(); + + // 构建父分类的 Map,用于快速查找 + Map categoryMap = new HashMap<>(); + + // 初始化父分类和子分类 + categories.forEach(category -> { + if (category.getParentCategoryId() == 0) { + // 父分类 + CategoryVO categoryVO = new CategoryVO(); + BeanUtils.copyProperties(category, categoryVO); + categoryVO.setChildren(new ArrayList<>()); + categoryMap.put(category.getCategoryId(), categoryVO); + } else { + // 子分类 + CategoryVO.ChildrenCategoryVO childrenCategoryVO = new CategoryVO.ChildrenCategoryVO(); + BeanUtils.copyProperties(category, childrenCategoryVO); + + // 将子分类加入对应父分类的 children 列表 + CategoryVO parentCategory = categoryMap.get(category.getParentCategoryId()); + if (parentCategory != null) { + parentCategory.getChildren().add(childrenCategoryVO); + } + } + }); + // 构建根分类列表 + return new ArrayList<>(categoryMap.values()); + } + + @Override + public ApiResponse> categoryList() { + return ApiResponseUtil.success("获取分类列表成功", buildCategoryTree()); + } + + @Override + @Transactional + public ApiResponse deleteCategory(Integer categoryId) throws RuntimeException { + // 找出分类 Id = categoryId + // 或者 parentCategoryId 是 categoryId 的分类 + List categories = categoryMapper.findByIdOrParentId(categoryId); + + if (categories.isEmpty()) { + return ApiResponseUtil.error("分类 Id 非法"); + } + + // 获取这些分类的 Ids + List categoryIds = categories.stream() + .map(Category::getCategoryId) + .toList(); + + // 批量删除所有分类 + try { + int deleteCount = categoryMapper.deleteByIdBatch(categoryIds); + if (deleteCount != categoryIds.size()) { + throw new RuntimeException("删除分类失败"); + } + // 删除这些分类下的所有题目 + // TODO: 如果用户做了笔记,笔记和问题是对应的,删除了问题,笔记对应的问题就不存在了 + // 需要额外考虑讨论在删除分类的时候是否需要删除对应的笔记信息 + QuestionMapper.deleteByCategoryIdBatch(categoryIds); + return ApiResponseUtil.success("删除分类成功"); + } catch (Exception e) { + // 这里不能处理异常,需要抛出异常,让事务自动回滚 + throw new RuntimeException("删除分类失败"); + } + } + + @Override + public ApiResponse createCategory(CreateCategoryBody categoryBody) { + + if (categoryBody.getParentCategoryId() != 0) { + Category parent = categoryMapper.findById(categoryBody.getParentCategoryId()); + if (parent == null) { + return ApiResponseUtil.error("父分类 Id 不存在"); + } + } + + Category category = new Category(); + BeanUtils.copyProperties(categoryBody, category); + + // 插入分类 + try { + categoryMapper.insert(category); + CreateCategoryVO createCategoryVO = new CreateCategoryVO(); + createCategoryVO.setCategoryId(category.getCategoryId()); + return ApiResponseUtil.success("创建分类成功", createCategoryVO); + } catch (Exception e) { + return ApiResponseUtil.error("创建分类失败"); + } + } + + @Override + public ApiResponse updateCategory(Integer categoryId, UpdateCategoryBody categoryBody) { + + Category category = categoryMapper.findById(categoryId); + + if (category == null) { + return ApiResponseUtil.error("分类 Id 不存在"); + } + + category.setName(categoryBody.getName()); + + try { + categoryMapper.update(category); + return ApiResponseUtil.success("更新分类成功"); + } catch (Exception e) { + return ApiResponseUtil.error("更新分类失败"); + } + } +} diff --git a/backend/src/main/java/com/kama/notes/service/impl/CollectionNoteServiceImpl.java b/backend/src/main/java/com/kama/notes/service/impl/CollectionNoteServiceImpl.java new file mode 100644 index 0000000..7e9074d --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/impl/CollectionNoteServiceImpl.java @@ -0,0 +1,24 @@ +package com.kama.notes.service.impl; + +import com.kama.notes.mapper.CollectionNoteMapper; +import com.kama.notes.service.CollectionNoteService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Service +public class CollectionNoteServiceImpl implements CollectionNoteService { + + @Autowired + private CollectionNoteMapper collectionNoteMapper; + + @Override + public Set findUserCollectedNoteIds(Long userId, List noteIds) { + List userCollectedNoteIds + = collectionNoteMapper.findUserCollectedNoteIds(userId, noteIds); + return new HashSet<>(userCollectedNoteIds); + } +} diff --git a/backend/src/main/java/com/kama/notes/service/impl/CollectionServiceImpl.java b/backend/src/main/java/com/kama/notes/service/impl/CollectionServiceImpl.java new file mode 100644 index 0000000..7d172f0 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/impl/CollectionServiceImpl.java @@ -0,0 +1,173 @@ +package com.kama.notes.service.impl; + +import com.kama.notes.annotation.NeedLogin; +import com.kama.notes.mapper.CollectionMapper; +import com.kama.notes.mapper.CollectionNoteMapper; +import com.kama.notes.mapper.NoteMapper; +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.entity.Collection; +import com.kama.notes.model.entity.CollectionNote; +import com.kama.notes.model.vo.collection.CollectionVO; +import com.kama.notes.model.vo.collection.CreateCollectionVO; +import com.kama.notes.scope.RequestScopeData; +import com.kama.notes.service.CollectionService; +import com.kama.notes.utils.ApiResponseUtil; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +@Service +public class CollectionServiceImpl implements CollectionService { + @Autowired + private RequestScopeData requestScopeData; + + @Autowired + private CollectionMapper collectionMapper; + + @Autowired + private CollectionNoteMapper collectionNoteMapper; + + @Autowired + private NoteMapper noteMapper; + + @Override + public ApiResponse> getCollections(CollectionQueryParams queryParams) { + // 收藏夹列表 + List collections = collectionMapper.findByCreatorId(queryParams.getCreatorId()); + List collectionIds = collections.stream().map(Collection::getCollectionId).toList(); + final Set collectedNoteIdCollectionIds; + + // 查看是否传入了 noteId + if (queryParams.getNoteId() != null) { + // 收藏了 noteId 的收藏夹列表 + collectedNoteIdCollectionIds = collectionNoteMapper.filterCollectionIdsByNoteId(queryParams.getNoteId(), collectionIds); + } else { + collectedNoteIdCollectionIds = Collections.emptySet(); + } + + // 将 collections 映射为 CollectionVOList + List collectionVOList = collections.stream().map(collection -> { + CollectionVO collectionVO = new CollectionVO(); + BeanUtils.copyProperties(collection, collectionVO); + + // 检查是否传入了 noteId 参数并且当前收藏夹收藏了该 note + if (queryParams.getNoteId() == null) return collectionVO; + + // 设置收藏夹收藏笔记状态 + CollectionVO.NoteStatus noteStatus = new CollectionVO.NoteStatus(); + + noteStatus.setIsCollected(collectedNoteIdCollectionIds.contains(collection.getCollectionId())); + noteStatus.setNoteId(queryParams.getNoteId()); + collectionVO.setNoteStatus(noteStatus); + + return collectionVO; + }).toList(); + + return ApiResponseUtil.success("获取收藏夹列表成功", collectionVOList); + } + + @Override + @NeedLogin + public ApiResponse createCollection(CreateCollectionBody requestBody) { + + Long creatorId = requestScopeData.getUserId(); + + Collection collection = new Collection(); + BeanUtils.copyProperties(requestBody, collection); + collection.setCreatorId(creatorId); + + try { + collectionMapper.insert(collection); + CreateCollectionVO createCollectionVO = new CreateCollectionVO(); + + createCollectionVO.setCollectionId(collection.getCollectionId()); + return ApiResponseUtil.success("创建成功", createCollectionVO); + } catch (Exception e) { + return ApiResponseUtil.error("创建失败"); + } + } + + @Override + @NeedLogin + @Transactional + public ApiResponse deleteCollection(Integer collectionId) { + // 校验是否是收藏夹创建者 + Long creatorId = requestScopeData.getUserId(); + Collection collection = collectionMapper.findByIdAndCreatorId(collectionId, creatorId); + + if (collection == null) { + return ApiResponseUtil.error("收藏夹不存在或者无权限删除"); + } + + try { + // 删除收藏夹 + collectionMapper.deleteById(collectionId); + // 删除收藏夹中的所有的笔记 + collectionNoteMapper.deleteByCollectionId(collectionId); + return ApiResponseUtil.success("删除成功"); + } catch (Exception e) { + return ApiResponseUtil.error("删除失败"); + } + } + + @Override + @NeedLogin + @Transactional + public ApiResponse batchModifyCollection(UpdateCollectionBody requestBody) { + + Long userId = requestScopeData.getUserId(); + Integer noteId = requestBody.getNoteId(); + + UpdateCollectionBody.UpdateItem[] collections = requestBody.getCollections(); + + for (UpdateCollectionBody.UpdateItem collection : collections) { + Integer collectionId = collection.getCollectionId(); + String action = collection.getAction(); + + // 校验是否是收藏夹创建者 + Collection collectionEntity = collectionMapper.findByIdAndCreatorId(collectionId, userId); + + if (collectionEntity == null) { + return ApiResponseUtil.error("收藏夹不存在或者无权限操作"); + } + + if ("create".equals(action)) { + try { + // 获取用户是否收藏过该笔记 + if (collectionMapper.countByCreatorIdAndNoteId(userId, noteId) == 0) { + // 笔记不存在,给笔记增加收藏量 + noteMapper.collectNote(noteId); + } + CollectionNote collectionNote = new CollectionNote(); + collectionNote.setCollectionId(collectionId); + collectionNote.setNoteId(noteId); + collectionNoteMapper.insert(collectionNote); + } catch (Exception e) { + return ApiResponseUtil.error("收藏失败"); + } + } + + if ("delete".equals(action)) { + try { + collectionNoteMapper.deleteByCollectionIdAndNoteId(collectionId, noteId); + if (collectionMapper.countByCreatorIdAndNoteId(userId, noteId) == 0) { + // 笔记不存在,给笔记减少收藏量 + noteMapper.unCollectNote(noteId); + } + } catch (Exception e) { + return ApiResponseUtil.error("取消收藏失败"); + } + } + } + return ApiResponseUtil.success("操作成功"); + } +} diff --git a/backend/src/main/java/com/kama/notes/service/impl/LocalFileServiceImpl.java b/backend/src/main/java/com/kama/notes/service/impl/LocalFileServiceImpl.java new file mode 100644 index 0000000..016117d --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/impl/LocalFileServiceImpl.java @@ -0,0 +1,113 @@ +package com.kama.notes.service.impl; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import com.kama.notes.service.FileService; + +@Service +public class LocalFileServiceImpl implements FileService { + + /** + * 基础上传路径(本地存储的绝对或相对路径) + */ + @Value("${upload.path}") + private String uploadBasePath; + + /** + * 返回给前端的地址前缀 (可配合CDN/Nginx等) + */ + @Value("${upload.url-prefix}") + private String urlPrefix; + + /** + * 允许上传的图片后缀名(小写形式) + */ + private static final List ALLOWED_IMAGE_EXTENSIONS + = Arrays.asList(".jpg", ".jpeg", ".png", ".webp"); + + /** + * 单个图片最大尺寸 (10MB) + */ + private static final long MAX_IMAGE_SIZE = 10 * 1024 * 1024; + + @Override + public String uploadImage(MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new IllegalArgumentException("上传的图片文件为空"); + } + + // 校验文件大小 + if (file.getSize() > MAX_IMAGE_SIZE) { + throw new IllegalArgumentException("图片大小不能超过 10MB"); + } + + // 获取原始文件名 + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null || !originalFilename.contains(".")) { + throw new IllegalArgumentException("图片文件名无效"); + } + + // 校验后缀(小写判断) + String lowerCaseExtension = originalFilename + .substring(originalFilename.lastIndexOf(".")) + .toLowerCase(); + + if (!ALLOWED_IMAGE_EXTENSIONS.contains(lowerCaseExtension)) { + throw new IllegalArgumentException( + "只支持 " + ALLOWED_IMAGE_EXTENSIONS + " 等格式图片"); + } + return doUpload(file); + } + + @Override + public String uploadFile(MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new IllegalArgumentException("文件为空"); + } + return doUpload(file); + } + + /** + * 实际执行文件上传的公共方法 + * + * @param file MultipartFile + * @return 上传后可访问的URL + */ + private String doUpload(MultipartFile file) { + + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null || !originalFilename.contains(".")) { + throw new IllegalArgumentException("文件名不合法"); + } + + // 统一生成新文件名 + String fileExtension = originalFilename + .substring(originalFilename.lastIndexOf(".")) + .toLowerCase(); + String newFileName = UUID.randomUUID() + fileExtension; + + // 确保目录存在 + File uploadDir = new File(uploadBasePath); + if (!uploadDir.exists() && !uploadDir.mkdirs()) { + throw new IllegalStateException("无法创建上传目录: " + uploadBasePath); + } + + // 保存文件 + File destFile = new File(uploadDir, newFileName); + try { + file.transferTo(destFile); + } catch (IOException e) { + throw new IllegalStateException("文件保存失败: " + e.getMessage(), e); + } + // 返回访问URL + return urlPrefix + "/" + newFileName; + } +} diff --git a/backend/src/main/java/com/kama/notes/service/impl/NoteLikeServiceImpl.java b/backend/src/main/java/com/kama/notes/service/impl/NoteLikeServiceImpl.java new file mode 100644 index 0000000..a9051a0 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/impl/NoteLikeServiceImpl.java @@ -0,0 +1,76 @@ +package com.kama.notes.service.impl; + +import com.kama.notes.annotation.NeedLogin; +import com.kama.notes.mapper.NoteLikeMapper; +import com.kama.notes.mapper.NoteMapper; +import com.kama.notes.model.base.ApiResponse; +import com.kama.notes.model.base.EmptyVO; +import com.kama.notes.model.entity.NoteLike; +import com.kama.notes.scope.RequestScopeData; +import com.kama.notes.service.NoteLikeService; +import com.kama.notes.utils.ApiResponseUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Service +public class NoteLikeServiceImpl implements NoteLikeService { + + @Autowired + private NoteLikeMapper noteLikeMapper; + + @Autowired + private NoteMapper noteMapper; + + @Autowired + private RequestScopeData requestScopeData; + + @Override + public Set findUserLikedNoteIds(Long userId, List noteIds) { + List userLikedNoteIds = noteLikeMapper.findUserLikedNoteIds(userId, noteIds); + return new HashSet<>(userLikedNoteIds); + } + + @Override + @NeedLogin + public ApiResponse likeNote(Integer noteId) { + + Long userId = requestScopeData.getUserId(); + NoteLike noteLike = noteLikeMapper.findByUserIdAndNoteId(userId, noteId); + + if (noteLike != null) { + return ApiResponseUtil.success("已经点赞过了"); + } + + noteLike = new NoteLike(); + noteLike.setUserId(userId); + noteLike.setNoteId(noteId); + noteLikeMapper.insert(noteLike); + + // 更新笔记点赞数 + noteMapper.likeNote(noteId); + + return ApiResponseUtil.success("点赞成功"); + } + + @Override + @NeedLogin + public ApiResponse unlikeNote(Integer noteId) { + Long userId = requestScopeData.getUserId(); + + NoteLike noteLike = noteLikeMapper.findByUserIdAndNoteId(userId, noteId); + + if (noteLike == null) { + return ApiResponseUtil.success("已经取消点赞过了"); + } + + noteLikeMapper.delete(noteLike); + + // 更新笔记点赞数 + noteMapper.unlikeNote(noteId); + return ApiResponseUtil.success("取消点赞成功"); + } +} diff --git a/backend/src/main/java/com/kama/notes/service/impl/NoteServiceImpl.java b/backend/src/main/java/com/kama/notes/service/impl/NoteServiceImpl.java new file mode 100644 index 0000000..3d18f2d --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/impl/NoteServiceImpl.java @@ -0,0 +1,333 @@ +package com.kama.notes.service.impl; + +import com.kama.notes.annotation.NeedLogin; +import com.kama.notes.mapper.QuestionMapper; +import com.kama.notes.model.base.ApiResponse; +import com.kama.notes.model.base.EmptyVO; +import com.kama.notes.model.base.Pagination; +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.entity.Note; +import com.kama.notes.mapper.NoteMapper; +import com.kama.notes.model.entity.Question; +import com.kama.notes.model.entity.User; +import com.kama.notes.model.vo.category.CategoryVO; +import com.kama.notes.model.vo.note.*; +import com.kama.notes.scope.RequestScopeData; +import com.kama.notes.service.*; +import com.kama.notes.utils.ApiResponseUtil; +import com.kama.notes.utils.MarkdownUtil; +import com.kama.notes.utils.PaginationUtils; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +@Log4j2 +@Service +public class NoteServiceImpl implements NoteService { + + @Autowired + private NoteMapper noteMapper; + + @Autowired + private UserService userService; + + @Autowired + private QuestionService questionService; + + @Autowired + private NoteLikeService noteLikeService; + + @Autowired + private CollectionNoteService collectionNoteService; + + @Autowired + private RequestScopeData requestScopeData; + + @Autowired + private CategoryService categoryService; + + @Autowired + private QuestionMapper questionMapper; + + @Override + public ApiResponse> getNotes(NoteQueryParams params) { + + // 计算分页参数 + int offset = PaginationUtils.calculateOffset(params.getPage(), params.getPageSize()); + + // 查询当前查询条件下的笔记总数 + int total = noteMapper.countNotes(params); + + Pagination pagination = new Pagination(params.getPage(), params.getPageSize(), total); + + // 获取笔记列表 + List notes = noteMapper.findByQueryParams(params, offset, params.getPageSize()); + + // 从 笔记列表 中提取 questionIds 和 authorIds,并去重 + List questionIds = notes.stream().map(Note::getQuestionId).distinct().toList(); + List authorIds = notes.stream().map(Note::getAuthorId).distinct().toList(); + List noteIds = notes.stream().map(Note::getNoteId).toList(); + + // 笔记的作者信息 + Map userMapByIds = userService.getUserMapByIds(authorIds); + // 笔记的问题信息 + Map questionMapByIds = questionService.getQuestionMapByIds(questionIds); + + // 当前登录用户点赞的笔记列表和收藏的笔记列表 + Set userLikedNoteIds; + Set userCollectedNoteIds; + + // 如果是登录状态,则对当前查询的笔记列表进行是否点赞过 / 收藏过的判断 + if (requestScopeData.isLogin() && requestScopeData.getUserId() != null) { + Long currentUserId = requestScopeData.getUserId(); + userLikedNoteIds = noteLikeService.findUserLikedNoteIds(currentUserId, noteIds); + userCollectedNoteIds = collectionNoteService.findUserCollectedNoteIds(currentUserId, noteIds); + } else { // 未登录状态直接设置为空集合 + userLikedNoteIds = Collections.emptySet(); + userCollectedNoteIds = Collections.emptySet(); + } + + // 用户的点赞信息 + // 用户的收藏信息 + try { + List noteVOs = notes.stream().map(note -> { + NoteVO noteVO = new NoteVO(); + BeanUtils.copyProperties(note, noteVO); + + // 填充作者信息 + User author = userMapByIds.get(note.getAuthorId()); + if (author != null) { + NoteVO.SimpleAuthorVO authorVO = new NoteVO.SimpleAuthorVO(); + BeanUtils.copyProperties(author, authorVO); + noteVO.setAuthor(authorVO); + } + + // 填充问题信息 + Question question = questionMapByIds.get(note.getQuestionId()); + if (question != null) { + NoteVO.SimpleQuestionVO questionVO = new NoteVO.SimpleQuestionVO(); + BeanUtils.copyProperties(question, questionVO); + noteVO.setQuestion(questionVO); + } + + // 填充用户行为信息 + NoteVO.UserActionsVO userActionsVO = new NoteVO.UserActionsVO(); + if (userLikedNoteIds != null && userLikedNoteIds.contains(note.getNoteId())) { + userActionsVO.setIsLiked(true); + } + if (userCollectedNoteIds != null && userCollectedNoteIds.contains(note.getNoteId())) { + userActionsVO.setIsCollected(true); + } + + // 处理笔记内容折叠内容 + if (MarkdownUtil.needCollapsed(note.getContent())) { + noteVO.setNeedCollapsed(true); + noteVO.setDisplayContent(MarkdownUtil.extractIntroduction(note.getContent())); + } else { + noteVO.setNeedCollapsed(false); + } + + noteVO.setUserActions(userActionsVO); + return noteVO; + }).toList(); + + return ApiResponseUtil.success("获取笔记列表成功", noteVOs, pagination); + } catch (Exception e) { + // TODO: 打印日志 + System.out.println(Arrays.toString(e.getStackTrace())); + return ApiResponseUtil.error("获取笔记列表失败"); + } + } + + @Override + @NeedLogin + public ApiResponse createNote(CreateNoteRequest request) { + Long userId = requestScopeData.getUserId(); + Integer questionId = request.getQuestionId(); + + // 判断问题指定的问题是否存在 + Question question = questionService.findById(questionId); + + if (question == null) { // 对应的问题不存在 + return ApiResponseUtil.error("questionId 对应的问题不存在"); + } + + Note note = new Note(); + BeanUtils.copyProperties(request, note); + note.setAuthorId(userId); + + try { + noteMapper.insert(note); + CreateNoteVO createNoteVO = new CreateNoteVO(); + createNoteVO.setNoteId(note.getNoteId()); + return ApiResponseUtil.success("创建笔记成功", createNoteVO); + } catch (Exception e) { + return ApiResponseUtil.error("创建笔记失败"); + } + } + + @Override + @NeedLogin + public ApiResponse updateNote(Integer noteId, UpdateNoteRequest request) { + + Long userId = requestScopeData.getUserId(); + + // 查询笔记 + Note note = noteMapper.findById(noteId); + if (note == null) { + return ApiResponseUtil.error("笔记不存在"); + } + + if (!Objects.equals(userId, note.getAuthorId())) { + return ApiResponseUtil.error("没有权限修改别人的笔记"); + } + + try { + note.setContent(request.getContent()); + noteMapper.update(note); + return ApiResponseUtil.success("更新笔记成功"); + } catch (Exception e) { + return ApiResponseUtil.error("更新笔记失败"); + } + } + + @Override + @NeedLogin + public ApiResponse deleteNote(Integer noteId) { + + Long userId = requestScopeData.getUserId(); + + Note note = noteMapper.findById(noteId); + + if (note == null) { + return ApiResponseUtil.error("笔记不存在"); + } + + if (!Objects.equals(userId, note.getAuthorId())) { + // 没有权限删除别人的笔记 + return ApiResponseUtil.error("没有权限删除别人的笔记"); + } + + try { + noteMapper.deleteById(noteId); + return ApiResponseUtil.success("删除笔记成功"); + } catch (Exception e) { + return ApiResponseUtil.error("删除笔记失败"); + } + } + + // 下载笔记 + @Override + @NeedLogin + public ApiResponse downloadNote() { + + Long userId = requestScopeData.getUserId(); + + // 获取所有笔记 + List userNotes = noteMapper.findByAuthorId(userId); + + // 将笔记转为 key = questionId, value = note 的 map 对象 + Map questionNoteMap = userNotes.stream() + .collect(Collectors.toMap(Note::getQuestionId, note -> note)); + + if (userNotes.isEmpty()) { + return ApiResponseUtil.error("不存在任何笔记"); + } + + // 获取分类树 + List categoryTree = categoryService.buildCategoryTree(); + + // 根据分类树,创建 markdown 文件 + StringBuilder markdownContent = new StringBuilder(); + + // 将 note 中的所有 questionId 提取出来 + List questionIds = userNotes.stream() + .map(Note::getQuestionId) + .toList(); + + List questions = questionMapper.findByIdBatch(questionIds); + + for (CategoryVO categoryVO : categoryTree) { + + boolean hasTopLevelToc = false; + + if (categoryVO.getChildren().isEmpty()) { + continue; + } + + for (CategoryVO.ChildrenCategoryVO childrenCategoryVO : categoryVO.getChildren()) { + + boolean hasSubLevelToc = false; + Integer categoryId = childrenCategoryVO.getCategoryId(); + + // 用户在该分类下的笔记对应的所有问题 + List categoryQuestionList = questions.stream() + .filter(question -> question.getCategoryId().equals(categoryId)) + .toList(); + + if (categoryQuestionList.isEmpty()) { + continue; + } + + for (Question question : categoryQuestionList) { + + if (!hasTopLevelToc) { // 设置一级标题 + markdownContent.append("# ").append(categoryVO.getName()).append("\n"); + hasTopLevelToc = true; + } + + if (!hasSubLevelToc) { // 设置二级标题 + markdownContent.append("## ").append(childrenCategoryVO.getName()).append("\n"); + hasSubLevelToc = true; + } + + markdownContent.append("### [") + .append(question.getTitle()) + .append("]") + .append("(https://notes.kamacoder.com/questions/") + .append(question.getQuestionId()) + .append(")\n"); + + Note note = questionNoteMap.get(question.getQuestionId()); + + markdownContent.append(note.getContent()).append("\n"); + } + } + } + + // 设置笔记内容 + DownloadNoteVO downloadNoteVO = new DownloadNoteVO(); + downloadNoteVO.setMarkdown(markdownContent.toString()); + + return ApiResponseUtil.success("生成笔记成功", downloadNoteVO); + } + + @Override + public ApiResponse> submitNoteRank() { + return ApiResponseUtil.success("获取笔记排行榜成功", noteMapper.submitNoteRank()); + } + + @Override + @NeedLogin + public ApiResponse> submitNoteHeatMap() { + Long userId = requestScopeData.getUserId(); + return ApiResponseUtil.success("获取笔记热力图成功", noteMapper.submitNoteHeatMap(userId)); + } + + @Override + @NeedLogin + public ApiResponse submitNoteTop3Count() { + + Long userId = requestScopeData.getUserId(); + + Top3Count top3Count = noteMapper.submitNoteTop3Count(userId); + + return ApiResponseUtil.success("获取笔记top3成功", top3Count); + } +} diff --git a/backend/src/main/java/com/kama/notes/service/impl/QuestionListItemServiceImpl.java b/backend/src/main/java/com/kama/notes/service/impl/QuestionListItemServiceImpl.java new file mode 100644 index 0000000..812ece3 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/impl/QuestionListItemServiceImpl.java @@ -0,0 +1,167 @@ +package com.kama.notes.service.impl; + +import com.kama.notes.mapper.NoteMapper; +import com.kama.notes.mapper.QuestionListItemMapper; +import com.kama.notes.mapper.QuestionListMapper; +import com.kama.notes.mapper.UserMapper; +import com.kama.notes.model.base.ApiResponse; +import com.kama.notes.model.base.EmptyVO; +import com.kama.notes.model.base.Pagination; +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.entity.QuestionList; +import com.kama.notes.model.entity.QuestionListItem; +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.scope.RequestScopeData; +import com.kama.notes.service.QuestionListItemService; +import com.kama.notes.utils.ApiResponseUtil; +import com.kama.notes.utils.PaginationUtils; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +@Log4j2 +@Service +public class QuestionListItemServiceImpl implements QuestionListItemService { + + @Autowired + private QuestionListItemMapper questionListItemMapper; + + @Autowired + private QuestionListMapper questionListMapper; + + @Autowired + private RequestScopeData requestScopeData; + + @Autowired + private NoteMapper noteMapper; + + @Autowired + private UserMapper userMapper; + + @Override + public ApiResponse> userGetQuestionListItems(QuestionListItemQueryParams queryParams) { + // 需要获取题单信息 + int offset = PaginationUtils.calculateOffset(queryParams.getPage(), queryParams.getPageSize()); + + int total = questionListItemMapper.countByQuestionListId(queryParams.getQuestionListId()); + + Pagination pagination = new Pagination(queryParams.getPage(), queryParams.getPageSize(), total); + + // 获取题单 + Integer questionListId = queryParams.getQuestionListId(); + + QuestionList questionList = questionListMapper.findById(questionListId); + + // 获取题单项列表 + List questionListItems = + questionListItemMapper.findByQuestionListIdPage( + queryParams.getQuestionListId(), + queryParams.getPageSize(), + offset + ); + + // 获取这些题单的所有的题目 ID + List questionIds = questionListItems.stream() + .map(questionListItemVO -> questionListItemVO.getQuestion().getQuestionId()) + .toList(); + + final Set userFinishedQuestionIds; + + // 如果是登录状态,则筛选出当前题单中的用户完成的题目 ID + if (requestScopeData.isLogin()) { + userFinishedQuestionIds = + noteMapper.filterFinishedQuestionIdsByUser(requestScopeData.getUserId(), questionIds); + } else { + userFinishedQuestionIds = Collections.emptySet(); + } + + // 将 QuestionListItemVO 映射为带用户状态的 QuestionListItemUserVO + List list = questionListItems.stream().map(questionListItemVO -> { + + QuestionListItemUserVO questionListItemUserVO = new QuestionListItemUserVO(); + BeanUtils.copyProperties(questionListItemVO, questionListItemUserVO); + + QuestionListItemUserVO.UserQuestionStatus userQuestionStatus = + new QuestionListItemUserVO.UserQuestionStatus(); + + if (requestScopeData.isLogin()) { // 当前是登录状态 + userQuestionStatus.setFinished(userFinishedQuestionIds.contains(questionListItemVO.getQuestion().getQuestionId())); + } else { + userQuestionStatus.setFinished(false); + } + + questionListItemUserVO.setUserQuestionStatus(userQuestionStatus); + + return questionListItemUserVO; + }).toList(); + + return ApiResponseUtil.success("获取用户题单项列表成功", list, pagination); + } + + @Override + public ApiResponse> getQuestionListItems(Integer questionListId) { + + List byQuestionListId = questionListItemMapper.findByQuestionListId(questionListId); + + return ApiResponseUtil.success("获取题单项列表成功", byQuestionListId); + } + + @Override + public ApiResponse createQuestionListItem(CreateQuestionListItemBody body) { + + QuestionListItem questionListItem = new QuestionListItem(); + BeanUtils.copyProperties(body, questionListItem); + + try { + // 生成题单项的 rank + int rank = questionListItemMapper.nextRank(body.getQuestionListId()); + questionListItem.setRank(rank); + + questionListItemMapper.insert(questionListItem); + CreateQuestionListItemVO createQuestionListItemVO = new CreateQuestionListItemVO(); + createQuestionListItemVO.setRank(questionListItem.getRank()); + return ApiResponseUtil.success("创建题单项成功", createQuestionListItemVO); + } catch (Exception e) { + return ApiResponseUtil.error("创建题单项失败"); + } + } + + @Override + public ApiResponse deleteQuestionListItem(Integer questionListId, Integer questionId) { + try { + questionListItemMapper.deleteByQuestionListIdAndQuestionId(questionListId, questionId); + return ApiResponseUtil.success("删除题单项成功"); + } catch (Exception e) { + return ApiResponseUtil.error("删除题单项失败"); + } + } + + @Override + public ApiResponse sortQuestionListItem(SortQuestionListItemBody body) { + // TODO: 待优化 + List questionIds = body.getQuestionIds(); + Integer questionListId = body.getQuestionListId(); + + try { + for (int i = 0; i < questionIds.size(); i++) { + QuestionListItem questionListItem = new QuestionListItem(); + questionListItem.setQuestionId(questionIds.get(i)); + questionListItem.setQuestionListId(questionListId); + questionListItem.setRank(i + 1); + questionListItemMapper.updateQuestionRank(questionListItem); + } + return ApiResponseUtil.success("题单项排序成功"); + } catch (Exception e) { + return ApiResponseUtil.error("题单项排序失败"); + } + } +} diff --git a/backend/src/main/java/com/kama/notes/service/impl/QuestionListServiceImpl.java b/backend/src/main/java/com/kama/notes/service/impl/QuestionListServiceImpl.java new file mode 100644 index 0000000..e60bab8 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/impl/QuestionListServiceImpl.java @@ -0,0 +1,90 @@ +package com.kama.notes.service.impl; + +import com.kama.notes.mapper.QuestionListItemMapper; +import com.kama.notes.mapper.QuestionListMapper; +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; +import com.kama.notes.utils.ApiResponseUtil; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class QuestionListServiceImpl implements QuestionListService { + + @Autowired + private QuestionListMapper questionListMapper; + + @Autowired + private QuestionListItemMapper questionListItemMapper; + + @Override + public ApiResponse getQuestionList(Integer questionListId) { + return ApiResponseUtil.success("获取题单成功", questionListMapper.findById(questionListId)); + } + + @Override + public ApiResponse> getQuestionLists() { + return ApiResponseUtil.success("获取题单成功", questionListMapper.findAll()); + } + + @Override + public ApiResponse createQuestionList(CreateQuestionListBody body) { + + QuestionList questionList = new QuestionList(); + BeanUtils.copyProperties(body, questionList); + + // 创建题单 + try { + questionListMapper.insert(questionList); + CreateQuestionListVO questionListVO = new CreateQuestionListVO(); + questionListVO.setQuestionListId(questionList.getQuestionListId()); + return ApiResponseUtil.success("创建题单成功", questionListVO); + } catch (Exception e) { + return ApiResponseUtil.error("创建题单失败"); + } + } + + @Override + public ApiResponse deleteQuestionList(Integer questionListId) { + // 删除题单,还需要删除题单对应的题单项目 + QuestionList questionList = questionListMapper.findById(questionListId); + + if (questionList == null) { + return ApiResponseUtil.error("题单不存在"); + } + + try { + questionListMapper.deleteById(questionListId); + // 删除题单对应的所有题单项 + questionListItemMapper.deleteByQuestionListId(questionListId); + return ApiResponseUtil.success("删除题单成功"); + } catch (Exception e) { + return ApiResponseUtil.error("删除题单失败"); + } + } + + @Override + public ApiResponse updateQuestionList(Integer questionListId, UpdateQuestionListBody body) { + + QuestionList questionList = new QuestionList(); + BeanUtils.copyProperties(body, questionList); + questionList.setQuestionListId(questionListId); + + System.out.println(questionList); + + try { + questionListMapper.update(questionList); + return ApiResponseUtil.success("更新题单成功"); + } catch (Exception e) { + return ApiResponseUtil.error("更新题单失败"); + } + } +} diff --git a/backend/src/main/java/com/kama/notes/service/impl/QuestionServiceImpl.java b/backend/src/main/java/com/kama/notes/service/impl/QuestionServiceImpl.java new file mode 100644 index 0000000..d13de67 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/impl/QuestionServiceImpl.java @@ -0,0 +1,218 @@ +package com.kama.notes.service.impl; + +import com.kama.notes.mapper.CategoryMapper; +import com.kama.notes.mapper.NoteMapper; +import com.kama.notes.mapper.QuestionMapper; +import com.kama.notes.model.base.ApiResponse; +import com.kama.notes.model.base.EmptyVO; +import com.kama.notes.model.base.Pagination; +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.entity.Category; +import com.kama.notes.model.entity.Note; +import com.kama.notes.model.entity.Question; +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.scope.RequestScopeData; +import com.kama.notes.service.QuestionService; +import com.kama.notes.utils.ApiResponseUtil; +import com.kama.notes.utils.PaginationUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class QuestionServiceImpl implements QuestionService { + + @Autowired + private QuestionMapper questionMapper; + + @Autowired + private CategoryMapper categoryMapper; + + @Autowired + private RequestScopeData requestScopeData; + + @Autowired + private NoteMapper noteMapper; + + @Override + public Question findById(Integer questionId) { + return questionMapper.findById(questionId); + } + + @Override + public Map getQuestionMapByIds(List questionIds) { + + // 处理空数组的情况 + if (questionIds.isEmpty()) return Collections.emptyMap(); + + List questions = questionMapper.findByIdBatch(questionIds); + return questions.stream().collect(Collectors.toMap(Question::getQuestionId, question -> question)); + } + + @Override + public ApiResponse> getQuestions(QuestionQueryParam queryParams) { + + int offset = PaginationUtils.calculateOffset(queryParams.getPage(), queryParams.getPageSize()); + int total = questionMapper.countByQueryParam(queryParams); + + Pagination pagination = new Pagination(queryParams.getPage(), queryParams.getPageSize(), total); + List questions = questionMapper.findByQueryParam(queryParams, offset, queryParams.getPageSize()); + + List questionVOs = questions.stream().map(question -> { + QuestionVO questionVO = new QuestionVO(); + BeanUtils.copyProperties(question, questionVO); + return questionVO; + }).toList(); + + return ApiResponseUtil.success("获取问题列表成功", questionVOs, pagination); + } + + @Override + public ApiResponse createQuestion(CreateQuestionBody createQuestionBody) { + + // 校验分类 Id 是否合法 + Category category = categoryMapper.findById(createQuestionBody.getCategoryId()); + if (category == null) { + return ApiResponseUtil.error("分类 Id 非法"); + } + + Question question = new Question(); + BeanUtils.copyProperties(createQuestionBody, question); + + try { + questionMapper.insert(question); + CreateQuestionVO createQuestionVO = new CreateQuestionVO(); + createQuestionVO.setQuestionId(question.getQuestionId()); + return ApiResponseUtil.success("创建问题成功", createQuestionVO); + } catch (Exception e) { + return ApiResponseUtil.error("创建问题失败"); + } + } + + @Override + public ApiResponse updateQuestion(Integer questionId, UpdateQuestionBody updateQuestionBody) { + Question question = new Question(); + BeanUtils.copyProperties(updateQuestionBody, question); + question.setQuestionId(questionId); + // 更新问题 + try { + questionMapper.update(question); + return ApiResponseUtil.success("更新问题成功"); + } catch (Exception e) { + return ApiResponseUtil.error("更新问题失败"); + } + } + + @Override + public ApiResponse deleteQuestion(Integer questionId) { + if (questionMapper.deleteById(questionId) > 0) { + return ApiResponseUtil.success("删除问题成功"); + } else { + return ApiResponseUtil.error("删除问题失败"); + } + } + + // 同样是获取笔记列表,userGetQuestions 会携带用户是否完成该道题目的信息 + @Override + public ApiResponse> userGetQuestions(QuestionQueryParam queryParams) { + + // 分页相关信息 + int offset = PaginationUtils.calculateOffset(queryParams.getPage(), queryParams.getPageSize()); + int total = questionMapper.countByQueryParam(queryParams); + Pagination pagination = new Pagination(queryParams.getPage(), queryParams.getPageSize(), total); + + // 根据 queryParams 查询出符合条件的问题列表 + List questions = questionMapper.findByQueryParam(queryParams, offset, queryParams.getPageSize()); + + // 提取出 questionId + List questionIds = questions.stream().map(Question::getQuestionId).toList(); + + // 存放用户完成的题目 Id 集合 + Set userFinishedQuestionIds; + + // 如果是登录状态,则查询出当前用户完成的题目 Id 集合 + if (requestScopeData.isLogin() && requestScopeData.getUserId() != null) { + userFinishedQuestionIds = noteMapper.filterFinishedQuestionIdsByUser(requestScopeData.getUserId(), questionIds); + } else { + userFinishedQuestionIds = Collections.emptySet(); + } + + List questionUserVOs = questions.stream().map(question -> { + QuestionUserVO questionUserVO = new QuestionUserVO(); + QuestionUserVO.UserQuestionStatus userQuestionStatus = new QuestionUserVO.UserQuestionStatus(); + + // 判断用户是否完成该道题目 + if (userFinishedQuestionIds != null && userFinishedQuestionIds.contains(question.getQuestionId())) { + userQuestionStatus.setFinished(true); // 用户完成了该道题目 + } + + BeanUtils.copyProperties(question, questionUserVO); + + // 设置用户完成状态 + questionUserVO.setUserQuestionStatus(userQuestionStatus); + return questionUserVO; + }).toList(); + + return ApiResponseUtil.success("获取用户问题列表成功", questionUserVOs, pagination); + } + + @Override + public ApiResponse userGetQuestion(Integer questionId) { + + // 验证 question 是否存在 + Question question = questionMapper.findById(questionId); + if (question == null) { + return ApiResponseUtil.error("questionId 非法"); + } + + QuestionNoteVO questionNoteVO = new QuestionNoteVO(); + QuestionNoteVO.UserNote userNote = new QuestionNoteVO.UserNote(); + + // 如果是登录状态,则查询出当前用户笔记 + if (requestScopeData.isLogin() && requestScopeData.getUserId() != null) { + Note note = noteMapper.findByAuthorIdAndQuestionId(requestScopeData.getUserId(), questionId); + if (note != null) { + userNote.setFinished(true); + BeanUtils.copyProperties(note, userNote); + } + } + + BeanUtils.copyProperties(question, questionNoteVO); + questionNoteVO.setUserNote(userNote); + + // 增加问题的点击量 + // TODO: 有待优化 + questionMapper.incrementViewCount(questionId); + + return ApiResponseUtil.success("获取问题成功", questionNoteVO); + } + + @Override + public ApiResponse> searchQuestions(SearchQuestionBody body) { + String keyword = body.getKeyword(); + + // TODO: 简单实现搜索问题功能,后续需要优化 + List questionList = questionMapper.findByKeyword(keyword); + + List questionVOList = questionList.stream().map(question -> { + QuestionVO questionVO = new QuestionVO(); + BeanUtils.copyProperties(question, questionVO); + return questionVO; + }).toList(); + + return ApiResponseUtil.success("搜索问题成功", questionVOList); + } +} diff --git a/backend/src/main/java/com/kama/notes/service/impl/RedisServiceImpl.java b/backend/src/main/java/com/kama/notes/service/impl/RedisServiceImpl.java new file mode 100644 index 0000000..6b19699 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/impl/RedisServiceImpl.java @@ -0,0 +1,55 @@ +package com.kama.notes.service.impl; + +import com.kama.notes.service.RedisService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +@Service +public class RedisServiceImpl implements RedisService { + + @Autowired + private RedisTemplate redisTemplate; + + // 保存数据 + public void set(String key, Object value) { + redisTemplate.opsForValue().set(key, value); + } + + // 设置过期时间 + public void setWithExpiry(String key, Object value, long timeout) { + redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); + } + + // 获取数据 + public Object get(String key) { + return redisTemplate.opsForValue().get(key); + } + + // 删除数据 + public void delete(String key) { + redisTemplate.delete(key); + } + + // 判断键是否存在 + public boolean exists(String key) { + return Boolean.TRUE.equals(redisTemplate.hasKey(key)); + } + + // 增加计数 + public Long increment(String key, long delta) { + return redisTemplate.opsForValue().increment(key, delta); + } + + // 获取 Hash 值 + public Object getHashValue(String hashKey, String key) { + return redisTemplate.opsForHash().get(hashKey, key); + } + + // 设置 Hash 值 + public void setHashValue(String hashKey, String key, Object value) { + redisTemplate.opsForHash().put(hashKey, key, value); + } +} diff --git a/backend/src/main/java/com/kama/notes/service/impl/StatisticServiceImpl.java b/backend/src/main/java/com/kama/notes/service/impl/StatisticServiceImpl.java new file mode 100644 index 0000000..bc471bd --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/impl/StatisticServiceImpl.java @@ -0,0 +1,38 @@ +package com.kama.notes.service.impl; + +import com.kama.notes.mapper.StatisticMapper; +import com.kama.notes.model.base.ApiResponse; +import com.kama.notes.model.base.Pagination; +import com.kama.notes.model.dto.statistic.StatisticQueryParam; +import com.kama.notes.model.entity.Statistic; +import com.kama.notes.service.StatisticService; +import com.kama.notes.utils.ApiResponseUtil; +import com.kama.notes.utils.PaginationUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class StatisticServiceImpl implements StatisticService { + @Autowired + private StatisticMapper statisticMapper; + + @Override + public ApiResponse> getStatistic(StatisticQueryParam queryParam) { + + Integer page = queryParam.getPage(); + Integer pageSize = queryParam.getPageSize(); + int offset = PaginationUtils.calculateOffset(page, pageSize); + int total = statisticMapper.countStatistic(); + + Pagination pagination = new Pagination(page, pageSize, total); + + try { + List statistics = statisticMapper.findByPage(pageSize, offset); + return ApiResponseUtil.success("获取统计列表成功", statistics, pagination); + } catch (Exception e) { + return ApiResponseUtil.error(e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/kama/notes/service/impl/UploadServiceImpl.java b/backend/src/main/java/com/kama/notes/service/impl/UploadServiceImpl.java new file mode 100644 index 0000000..6113a64 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/impl/UploadServiceImpl.java @@ -0,0 +1,25 @@ +package com.kama.notes.service.impl; + +import com.kama.notes.model.base.ApiResponse; +import com.kama.notes.model.vo.upload.ImageVO; +import com.kama.notes.service.FileService; +import com.kama.notes.service.UploadService; +import com.kama.notes.utils.ApiResponseUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +@Service +public class UploadServiceImpl implements UploadService { + + @Autowired + FileService fileService; + + @Override + public ApiResponse uploadImage(MultipartFile file) { + String url = fileService.uploadImage(file); + ImageVO imageVO = new ImageVO(); + imageVO.setUrl(url); + return ApiResponseUtil.success("上传成功", imageVO); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/kama/notes/service/impl/UserServiceImpl.java b/backend/src/main/java/com/kama/notes/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..3593d29 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/service/impl/UserServiceImpl.java @@ -0,0 +1,224 @@ +package com.kama.notes.service.impl; + +import com.kama.notes.annotation.NeedLogin; +import com.kama.notes.model.base.ApiResponse; +import com.kama.notes.model.base.Pagination; +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.mapper.UserMapper; +import com.kama.notes.model.vo.user.AvatarVO; +import com.kama.notes.model.vo.user.RegisterVO; +import com.kama.notes.model.vo.user.LoginUserVO; +import com.kama.notes.model.vo.user.UserVO; +import com.kama.notes.scope.RequestScopeData; +import com.kama.notes.service.FileService; +import com.kama.notes.service.UserService; +import com.kama.notes.utils.ApiResponseUtil; +import com.kama.notes.utils.JwtUtil; +import com.kama.notes.utils.PaginationUtils; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.util.*; +import java.util.stream.Collectors; + + +@Log4j2 +@Service +public class UserServiceImpl implements UserService { + + @Autowired + private UserMapper userMapper; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private FileService fileService; + + @Autowired + private RequestScopeData requestScopeData; + + + @Override + @Transactional(rollbackFor = Exception.class) + public ApiResponse register(RegisterRequest request) { + // 检查账号是否已存在 + // TODO: 可以优化 + User existingUser = userMapper.findByAccount(request.getAccount()); + if (existingUser != null) { + return ApiResponseUtil.error("账号重复"); + } + + // 创建新用户 + User user = new User(); + + BeanUtils.copyProperties(request, user); + user.setPassword(passwordEncoder.encode(request.getPassword())); + + try { + // 保存用户 + userMapper.insert(user); + + String token = jwtUtil.generateToken(user.getUserId()); + + RegisterVO registerVO = new RegisterVO(); + BeanUtils.copyProperties(user, registerVO); + + userMapper.updateLastLoginAt(user.getUserId()); + + return ApiResponseUtil.success("注册成功", registerVO, token); + } catch (Exception e) { + log.error("注册失败", e); + return ApiResponseUtil.error("注册失败,请稍后再试"); + } + } + + @Override + public ApiResponse login(LoginRequest request) { + + User user = userMapper.findByAccount(request.getAccount()); + + // 验证账号以及密码 + if (user == null) { + return ApiResponseUtil.error("账号不存在"); + } + + if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { + return ApiResponseUtil.error("密码错误"); + } + + // 生成JWT + String token = jwtUtil.generateToken(user.getUserId()); + + LoginUserVO userVO = new LoginUserVO(); + BeanUtils.copyProperties(user, userVO); + + // 更新登录时间 + userMapper.updateLastLoginAt(user.getUserId()); + + return ApiResponseUtil.success("登录成功", userVO, token); + } + + + @Override + public ApiResponse whoami() { + Long userId = requestScopeData.getUserId(); + + if (userId == null) { + return ApiResponseUtil.error("用户 ID 异常"); + } + + try { + // 查询用户信息 + User user = userMapper.findById(userId); + if (user == null) { + return ApiResponseUtil.error("用户不存在"); + } + + // 生成新的 JWT + String newToken = jwtUtil.generateToken(userId); + if (newToken == null) { + return ApiResponseUtil.error("系统错误"); + } + + // 映射用户信息到 VO + LoginUserVO userVO = new LoginUserVO(); + BeanUtils.copyProperties(user, userVO); + + // 更新登录时间并返回响应 + userMapper.updateLastLoginAt(userId); + return ApiResponseUtil.success("自动登录成功", userVO, newToken); + } catch (Exception e) { + return ApiResponseUtil.error("系统错误"); + } + } + + @Override + public ApiResponse getUserInfo(Long userId) { + + User user = userMapper.findById(userId); + + if (user == null) { + return ApiResponseUtil.error("用户不存在"); + } + + UserVO userVO = new UserVO(); + BeanUtils.copyProperties(user, userVO); + + return ApiResponseUtil.success("获取用户信息成功", userVO); + } + + @Override + @Transactional + @NeedLogin + public ApiResponse updateUserInfo(UpdateUserRequest request) { + Long userId = requestScopeData.getUserId(); + + User user = new User(); + BeanUtils.copyProperties(request, user); + user.setUserId(userId); + + System.out.println(user); + + try { + userMapper.update(user); + return ApiResponseUtil.success("更新成功"); + } catch (Exception e) { + return ApiResponseUtil.error("更新失败"); + } + } + + @Override + public Map getUserMapByIds(List authorIds) { + + // 处理空数组的情况 + if (authorIds.isEmpty()) return Collections.emptyMap(); + + // 查询用户列表 + List users = userMapper.findByIdBatch(authorIds); + + return users.stream() + .collect(Collectors.toMap(User::getUserId, user -> user)); + } + + @Override + public ApiResponse> getUserList(UserQueryParam userQueryParam) { + + // 分页数据 + int total = userMapper.countByQueryParam(userQueryParam); + int offset = PaginationUtils.calculateOffset(userQueryParam.getPage(), userQueryParam.getPageSize()); + Pagination pagination = new Pagination(userQueryParam.getPage(), userQueryParam.getPageSize(), total); + + try { + List users = userMapper.findByQueryParam(userQueryParam, userQueryParam.getPageSize(), offset); + + return ApiResponseUtil.success("获取用户列表成功", users, pagination); + } catch (Exception e) { + return ApiResponseUtil.error(e.getMessage()); + } + } + + @Override + public ApiResponse uploadAvatar(MultipartFile file) { + try { + String url = fileService.uploadImage(file); + AvatarVO avatarVO = new AvatarVO(); + avatarVO.setUrl(url); + return ApiResponseUtil.success("上传成功", avatarVO); + } catch (Exception e) { + return ApiResponseUtil.error(e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/kama/notes/task/DailyStatistics.java b/backend/src/main/java/com/kama/notes/task/DailyStatistics.java new file mode 100644 index 0000000..81c168a --- /dev/null +++ b/backend/src/main/java/com/kama/notes/task/DailyStatistics.java @@ -0,0 +1,68 @@ +package com.kama.notes.task; + +import com.kama.notes.mapper.StatisticMapper; +import com.kama.notes.mapper.NoteMapper; +import com.kama.notes.mapper.UserMapper; +import com.kama.notes.model.entity.Statistic; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; + +@Log4j2 +@Component +public class DailyStatistics { + + @Autowired + UserMapper userMapper; + + @Autowired + NoteMapper noteMapper; + + @Autowired + StatisticMapper statisticMapper; + + /** + * 统计日常提交笔记、登录数据 + */ + @Scheduled(cron = "0 59 23 * * ?") + public void dailyStatistics() { + + Statistic statistic = new Statistic(); + /** + * 获取统计数据 + */ + // 用户 + int todayLoginCount = userMapper.getTodayLoginCount(); + int todayRegisterCount = userMapper.getTodayRegisterCount(); + int totalRegisterCount = userMapper.getTotalRegisterCount(); + + // 笔记 + int todayNoteCount = noteMapper.getTodayNoteCount(); + int todaySubmitNoteUserCount = noteMapper.getTodaySubmitNoteUserCount(); + int totalNoteCount = noteMapper.getTotalNoteCount(); + + /** + * 设置统计数据 + */ + statistic.setLoginCount(todayLoginCount); + statistic.setRegisterCount(todayRegisterCount); + statistic.setTotalRegisterCount(totalRegisterCount); + + statistic.setNoteCount(todayNoteCount); + statistic.setSubmitNoteCount(todaySubmitNoteUserCount); + statistic.setTotalNoteCount(totalNoteCount); + + statistic.setDate(LocalDate.now()); + + try { + statisticMapper.insert(statistic); + log.info("[定时任务]统计每日数据, 插入数据成功, statistic={}", statistic); + } catch (Exception e) { + log.error("[定时任务]统计每日数据, 插入数据失败, statistic={}, 错误详情={}", statistic, e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/backend/src/main/java/com/kama/notes/utils/ApiResponseUtil.java b/backend/src/main/java/com/kama/notes/utils/ApiResponseUtil.java new file mode 100644 index 0000000..911dff9 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/utils/ApiResponseUtil.java @@ -0,0 +1,44 @@ +package com.kama.notes.utils; + +import com.kama.notes.model.base.ApiResponse; +import com.kama.notes.model.base.Pagination; +import com.kama.notes.model.base.PaginationApiResponse; +import com.kama.notes.model.base.TokenApiResponse; +import org.springframework.http.HttpStatus; + +public class ApiResponseUtil { + /** + * 构建成功的响应 + * + * @param message 响应消息 + * @return ApiResponse + */ + public static ApiResponse success(String message) { + return new ApiResponse<>(HttpStatus.OK.value(), message, null); + } + + public static ApiResponse success(String message, T data) { + return new ApiResponse<>(HttpStatus.OK.value(), message, data); + } + + /** + * 构建参数错误的响应 + */ + public static ApiResponse error(String msg) { + return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), msg, null); + } + + /** + * 构建 TokenApiResponse + */ + public static TokenApiResponse success(String msg, T data, String token) { + return new TokenApiResponse<>(HttpStatus.OK.value(), msg, data, token); + } + + /** + * 构建 PaginationApiResponse + */ + public static PaginationApiResponse success(String msg, T data, Pagination pagination) { + return new PaginationApiResponse<>(HttpStatus.OK.value(), msg, data, pagination); + } +} diff --git a/backend/src/main/java/com/kama/notes/utils/JwtUtil.java b/backend/src/main/java/com/kama/notes/utils/JwtUtil.java new file mode 100644 index 0000000..85e1301 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/utils/JwtUtil.java @@ -0,0 +1,71 @@ +package com.kama.notes.utils; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Component +public class JwtUtil { + + @Value("${jwt.secret}") + private String secret; + + @Value("${jwt.expiration}") + private Long expiration; + + /** + * 生成JWT令牌 + */ + public String generateToken(Long userId) { + Map claims = new HashMap<>(); + claims.put("userId", userId); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)) + .signWith(SignatureAlgorithm.HS512, secret) + .compact(); + } + + /** + * 从token中获取用户ID + */ + public Long getUserIdFromToken(String token) { + try { + Claims claims = Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + + return Long.valueOf(claims.get("userId").toString()); + } catch (Exception e) { + return null; + } + } + + /** + * 验证token是否有效 + */ + public boolean validateToken(String token) { + try { + Jwts.parser().setSigningKey(secret).parseClaimsJws(token); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * 刷新JWT令牌 + */ + public String refreshToken(Long userId) { + return generateToken(userId); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/kama/notes/utils/MarkdownAST.java b/backend/src/main/java/com/kama/notes/utils/MarkdownAST.java new file mode 100644 index 0000000..e388dee --- /dev/null +++ b/backend/src/main/java/com/kama/notes/utils/MarkdownAST.java @@ -0,0 +1,94 @@ +package com.kama.notes.utils; + +import com.vladsch.flexmark.ast.*; +import com.vladsch.flexmark.parser.Parser; +import com.vladsch.flexmark.util.ast.Document; +import com.vladsch.flexmark.util.ast.Node; + +import java.util.ArrayList; +import java.util.List; + +public class MarkdownAST { + private final Document markdownAST; + private final String markdownText; + + // 构造函数:初始化并解析 Markdown 文本 + public MarkdownAST(String markdownText) { + this.markdownText = markdownText; + + // 创建解析器实例 + Parser parser = Parser.builder().build(); + // 解析 Markdown 文本生成 AST + this.markdownAST = parser.parse(markdownText); + } + + // 提取简介,包含 heading 和段落,字数进行限制 + public String extractIntroduction(int maxChars) { + StringBuilder introText = new StringBuilder(); + // 遍历 AST 节点 + for (Node node : markdownAST.getChildren()) { + if (node instanceof Heading || node instanceof Paragraph) { + // 获取节点的文本内容 + String renderedText = getNodeText(node); + + // 截取文本到指定字符数 + int remainingChars = maxChars - introText.length(); + introText.append(renderedText, 0, Math.min(remainingChars, renderedText.length())); + + // 如果达到字符限制,停止提取 + if (introText.length() >= maxChars) { + break; + } + } + } + + return introText.toString().trim() + "..."; + } + + // 检查是否包含图片,并返回图片地址 + public List extractImages() { + List imageUrls = new ArrayList<>(); + + // 遍历 AST 节点 + for (Node node : markdownAST.getChildren()) { + if (node instanceof Image imageNode) { + // 获取图片地址 + imageUrls.add(imageNode.getUrl().toString()); + } + } + + return imageUrls; + } + + // 判断 Markdown 文本是否需要收起(是否包含图片或超过字符数) + public boolean shouldCollapse(int maxChars) { + return hasImages() || markdownText.length() > maxChars; + } + + // 获取精简后的 Markdown 文本 + public String getCollapsedMarkdown() { + String introText = extractIntroduction(150); + return introText + "..."; // 返回精简后的 Markdown,添加省略号 + } + + // 获取节点的文本内容 + private String getNodeText(Node node) { + StringBuilder text = new StringBuilder(); + + // 处理 Text 类型节点 + if (node instanceof Text) { + text.append(((Text) node).getChars()); + } + + // 处理其他类型节点,如强加粗、斜体等 + for (Node child : node.getChildren()) { + text.append(getNodeText(child)); + } + return text.toString(); + } + + // 判断 Markdown 文本中是否包含图片 + private boolean hasImages() { + return !extractImages().isEmpty(); + } +} diff --git a/backend/src/main/java/com/kama/notes/utils/MarkdownUtil.java b/backend/src/main/java/com/kama/notes/utils/MarkdownUtil.java new file mode 100644 index 0000000..4e45884 --- /dev/null +++ b/backend/src/main/java/com/kama/notes/utils/MarkdownUtil.java @@ -0,0 +1,14 @@ +package com.kama.notes.utils; + +public class MarkdownUtil { + + public static boolean needCollapsed(String markdown) { + MarkdownAST ast = new MarkdownAST(markdown); + return ast.shouldCollapse(250); + } + + public static String extractIntroduction(String markdown) { + MarkdownAST ast = new MarkdownAST(markdown); + return ast.extractIntroduction(250); + } +} diff --git a/backend/src/main/java/com/kama/notes/utils/PaginationUtils.java b/backend/src/main/java/com/kama/notes/utils/PaginationUtils.java new file mode 100644 index 0000000..4aa404f --- /dev/null +++ b/backend/src/main/java/com/kama/notes/utils/PaginationUtils.java @@ -0,0 +1,21 @@ +package com.kama.notes.utils; + +public class PaginationUtils { + /** + * 根据页码和每页大小计算偏移量 + * + * @param page 当前页码,从 1 开始 + * @param pageSize 每页大小,必须大于 0 + * @return 计算出的偏移量 + * @throws IllegalArgumentException 如果 page 小于 1 或 pageSize 小于 1 + */ + public static int calculateOffset(int page, int pageSize) { + if (page < 1) { + throw new IllegalArgumentException("页码 (page) 必须大于或等于 1"); + } + if (pageSize < 1) { + throw new IllegalArgumentException("每页大小 (pageSize) 必须大于 0"); + } + return (page - 1) * pageSize; + } +} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml new file mode 100644 index 0000000..68e4b24 --- /dev/null +++ b/backend/src/main/resources/application.yml @@ -0,0 +1,35 @@ +spring: + application: + name: kama-notes + servlet: + multipart: + max-file-size: 50MB + max-request-size: 50MB + profiles: + active: dev + redis: + host: localhost + port: 6379 + database: 0 + timeout: 2000 + jedis: + pool: + enabled: on + max-active: 8 + max-idle: 8 + min-idle: 0 + max-wait: 2000 +mybatis: + mapper-locations: classpath:mapper/*.xml + configuration: + map-underscore-to-camel-case: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + type-aliases-package: com.kama.notes.entity.custom + type-handlers-package: com.kama.notes.typehandler + +jwt: + secret: abc123 + expiration: 2592000 + +server: + port: 8080 diff --git a/backend/src/main/resources/log4j2-spring.xml b/backend/src/main/resources/log4j2-spring.xml new file mode 100644 index 0000000..1601c8b --- /dev/null +++ b/backend/src/main/resources/log4j2-spring.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/mapper/CategoryMapper.xml b/backend/src/main/resources/mapper/CategoryMapper.xml new file mode 100644 index 0000000..0203182 --- /dev/null +++ b/backend/src/main/resources/mapper/CategoryMapper.xml @@ -0,0 +1,84 @@ + + + + + + INSERT INTO category (name, parent_category_id) + VALUES (#{name}, #{parentCategoryId}) + + + + + INSERT INTO category (name, parent_category_id) + VALUES + + (#{category.name}, #{category.parentCategoryId}) + + + + SELECT 0 + + + + + + + + + + + + + DELETE + FROM category + WHERE category_id = #{categoryId} + + + + + DELETE + FROM category + WHERE category_id IN + + #{categoryId} + + + + DELETE FROM category WHERE 1 = 0 + + + + + UPDATE category + + + name = #{name}, + + + WHERE category_id = #{categoryId} + + diff --git a/backend/src/main/resources/mapper/CollectionMapper.xml b/backend/src/main/resources/mapper/CollectionMapper.xml new file mode 100644 index 0000000..a042cff --- /dev/null +++ b/backend/src/main/resources/mapper/CollectionMapper.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + INSERT INTO collection (name, description, creator_id) + VALUES (#{name}, #{description}, #{creatorId}) + + + + UPDATE collection + SET name = #{name}, + description = #{description} + WHERE collection_id = #{collectionId} + + + + DELETE + FROM collection + WHERE collection_id = #{collectionId} + + diff --git a/backend/src/main/resources/mapper/CollectionNoteMapper.xml b/backend/src/main/resources/mapper/CollectionNoteMapper.xml new file mode 100644 index 0000000..1b68d9b --- /dev/null +++ b/backend/src/main/resources/mapper/CollectionNoteMapper.xml @@ -0,0 +1,55 @@ + + + + + + + + + INSERT INTO collection_note (collection_id, note_id) + VALUES (#{collectionId}, #{noteId}) + + + + DELETE + FROM collection_note + WHERE collection_id = #{collectionId} + + + + DELETE + FROM collection_note + WHERE collection_id = #{collectionId} + AND note_id = #{noteId} + + diff --git a/backend/src/main/resources/mapper/NoteLikeMapper.xml b/backend/src/main/resources/mapper/NoteLikeMapper.xml new file mode 100644 index 0000000..6db4488 --- /dev/null +++ b/backend/src/main/resources/mapper/NoteLikeMapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + INSERT INTO note_like (user_id, note_id) + VALUES (#{userId}, #{noteId}) + + + + DELETE + FROM note_like + WHERE user_id = #{userId} + AND note_id = #{noteId} + + diff --git a/backend/src/main/resources/mapper/NoteMapper.xml b/backend/src/main/resources/mapper/NoteMapper.xml new file mode 100644 index 0000000..33b43a1 --- /dev/null +++ b/backend/src/main/resources/mapper/NoteMapper.xml @@ -0,0 +1,243 @@ + + + + + + + + AND question_id = #{params.questionId} + + + AND author_id = #{params.authorId} + + + AND note_id IN (SELECT note_id FROM collection_note WHERE collection_id = #{params.collectionId}) + + + AND created_at >= DATE_SUB(CURRENT_DATE, INTERVAL #{params.recentDays} DAY) + + + + + + + + + + + + + + + + INSERT INTO note (question_id, author_id, content) + VALUES (#{questionId}, #{authorId}, #{content}) + + + + + + UPDATE note + SET content = #{content} + WHERE note_id = #{noteId} + + + + UPDATE note + SET like_count = like_count + 1 + WHERE note_id = #{noteId} + + + + UPDATE note + SET like_count = like_count - 1 + WHERE note_id = #{noteId} + + + + UPDATE note + SET collect_count = collect_count + 1 + WHERE note_id = #{noteId} + + + + UPDATE note + SET collect_count = collect_count - 1 + WHERE note_id = #{noteId} + + + + DELETE + FROM note + WHERE note_id = #{noteId} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/mapper/QuestionListItemMapper.xml b/backend/src/main/resources/mapper/QuestionListItemMapper.xml new file mode 100644 index 0000000..9e891e1 --- /dev/null +++ b/backend/src/main/resources/mapper/QuestionListItemMapper.xml @@ -0,0 +1,80 @@ + + + + + INSERT INTO question_list_item (question_list_id, question_id, `rank`) + VALUES (#{questionListId}, #{questionId}, #{rank}) + + + + + + + + + + + + + + + + + SELECT qli.question_list_id, + qli.rank, + q.question_id AS "question.question_id", + q.title AS "question.title", + q.view_count AS "question.view_count", + q.exam_point AS "question.exam_point", + q.difficulty AS "question.difficulty", + q.category_id AS "question.category_id" + FROM question_list_item qli + LEFT JOIN question q + ON qli.question_id = q.question_id + WHERE qli.question_list_id = #{questionListId} + ORDER BY qli.rank + + + + + + + + + + DELETE + FROM question_list_item + WHERE question_list_id = #{questionListId} + AND question_id = #{questionId} + + + + DELETE + FROM question_list_item + WHERE question_list_id = #{questionListId} + + + + + + UPDATE `question_list_item` + SET `rank` = #{rank} + WHERE `question_list_id` = #{questionListId} + AND `question_id` = #{questionId} + + + diff --git a/backend/src/main/resources/mapper/QuestionListMapper.xml b/backend/src/main/resources/mapper/QuestionListMapper.xml new file mode 100644 index 0000000..1a3223c --- /dev/null +++ b/backend/src/main/resources/mapper/QuestionListMapper.xml @@ -0,0 +1,37 @@ + + + + + + INSERT INTO question_list (name, type, description) + VALUES (#{name}, #{type}, #{description}) + + + + UPDATE question_list + + + name = #{name}, + + + type = #{type}, + + + description = #{description}, + + + WHERE question_list_id = #{questionListId} + + + + DELETE FROM question_list WHERE question_list_id = #{questionListId} + + + + + + diff --git a/backend/src/main/resources/mapper/QuestionMapper.xml b/backend/src/main/resources/mapper/QuestionMapper.xml new file mode 100644 index 0000000..0e548c8 --- /dev/null +++ b/backend/src/main/resources/mapper/QuestionMapper.xml @@ -0,0 +1,140 @@ + + + + + + INSERT INTO question (category_id, title, difficulty, exam_point) + VALUES (#{categoryId}, #{title}, #{difficulty}, #{examPoint}) + + + + + + + + + + (category_id = #{queryParam.categoryId} + OR category_id IN ( + SELECT category_id + FROM category + WHERE parent_category_id = #{queryParam.categoryId} + )) + + + + + + + + + + UPDATE question + + + title = #{question.title}, + + + difficulty = #{question.difficulty}, + + + exam_point = #{question.examPoint}, + + + WHERE question_id = #{question.questionId} + + + + UPDATE question + SET view_count = view_count + 1 + WHERE question_id = #{questionId} + + + + + + DELETE + FROM question + WHERE category_id = #{categoryId} + + + + + DELETE + FROM question + WHERE question_id = #{questionId} + + + + + DELETE + FROM question + WHERE category_id IN + + #{categoryId} + + + + DELETE FROM question WHERE 1 = 0 + + + diff --git a/backend/src/main/resources/mapper/StatisticMapper.xml b/backend/src/main/resources/mapper/StatisticMapper.xml new file mode 100644 index 0000000..0e945ba --- /dev/null +++ b/backend/src/main/resources/mapper/StatisticMapper.xml @@ -0,0 +1,22 @@ + + + + + INSERT INTO statistic (login_count, register_count, total_register_count, note_count, submit_note_count, + total_note_count, date) + VALUES (#{loginCount}, #{registerCount}, #{totalRegisterCount}, #{noteCount}, #{submitNoteCount}, + #{totalNoteCount}, #{date}) + + + + + + diff --git a/backend/src/main/resources/mapper/UserMapper.xml b/backend/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..7fe599b --- /dev/null +++ b/backend/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,106 @@ + + + + + + + + INSERT INTO user (account, username, password) + VALUES (#{account}, #{username}, #{password}) + + + + + + + + + + AND user_id = #{queryParams.userId} + + + AND `account` LIKE CONCAT('%', #{queryParams.account}, '%') + + + AND username LIKE CONCAT('%', #{queryParams.username}, '%') + + + AND is_admin = #{queryParams.isAdmin} + + + AND is_banned = #{queryParams.isBanned} + + + + + + + + + + UPDATE user + + username = #{username}, + gender = #{gender}, + birthday = #{birthday}, + avatar_url = #{avatarUrl}, + email = #{email}, + school = #{school}, + signature = #{signature} + + WHERE user_id = #{userId} + + + + UPDATE user + SET last_login_at = CURRENT_TIMESTAMP + WHERE user_id = #{userId} + + + + + + + + diff --git a/backend/src/test/java/com/kama/notes/NotesApplicationTests.java b/backend/src/test/java/com/kama/notes/NotesApplicationTests.java new file mode 100644 index 0000000..d3a9560 --- /dev/null +++ b/backend/src/test/java/com/kama/notes/NotesApplicationTests.java @@ -0,0 +1,40 @@ +package com.kama.notes; + +import com.kama.notes.service.RedisService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @ClassName NotesApplication + * @Description 测试类 + * @Author Tong + * @LastChangeDate 2024-12-16 14:21 + * @Version v1.0 + */ +@RunWith(SpringRunner.class) +@SpringBootTest +@ContextConfiguration(classes = NotesApplication.class) +public class NotesApplicationTests { + @Autowired + RedisService redisService; + + @Test + public void Test() { + redisService.set("testKey", "testValue"); + } + + @Test + public void Test2() { + Object o = redisService.get("testKey"); + } + + @Test + public void Test3() { + Object o = redisService.get("random"); + System.out.println(o); + } +} diff --git a/frontend/.env b/frontend/.env new file mode 100644 index 0000000..e69de29 diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 0000000..ef5bc8c --- /dev/null +++ b/frontend/.env.development @@ -0,0 +1,2 @@ +VITE_API_BASE_URL=http://127.0.0.1:8080 +VITE_AVATAR_BASE_URL=http://127.0.0.1:8080/api/users/avatar diff --git a/frontend/.env.production b/frontend/.env.production new file mode 100644 index 0000000..df8c29d --- /dev/null +++ b/frontend/.env.production @@ -0,0 +1,2 @@ +VITE_API_BASE_URL=http://43.136.59.48 +VITE_AVATAR_BASE_URL=http://43.136.59.48/api/users/avatar diff --git a/frontend/.husky/pre-commit b/frontend/.husky/pre-commit new file mode 100644 index 0000000..a213f93 --- /dev/null +++ b/frontend/.husky/pre-commit @@ -0,0 +1 @@ +cd ./frontend && npx lint-staged \ No newline at end of file diff --git a/frontend/.nvmrc b/frontend/.nvmrc new file mode 100644 index 0000000..016efd8 --- /dev/null +++ b/frontend/.nvmrc @@ -0,0 +1 @@ +v20.10.0 \ No newline at end of file diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..b61adcf --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,5 @@ +{ + "semi": false, + "singleQuote": true, + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..43f9010 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,21 @@ +FROM node:20.18-alpine AS build + +WORKDIR /app + +COPY package.json package-lock.json ./ + +RUN npm install + +COPY . . + +CMD ["npm", "run", "build"] + +FROM nginx:stable-alpine + +COPY --from=build /app/dist /usr/share/nginx/html + +COPY nginx/default.conf /etc/nginx/conf.d/nginx.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..caf6a4b --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,29 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + '@typescript-eslint/no-explicit-any': 'off', + }, + }, +) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..b3135e0 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,33 @@ + + + + + + + 卡码笔记 + + + + + + + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..ca3f13f --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,11956 @@ +{ + "name": "kamanotes-tech", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "kamanotes-tech", + "version": "0.0.1", + "dependencies": { + "@ant-design/colors": "^7.1.0", + "@ant-design/icons": "^5.5.1", + "@ant-design/pro-components": "^2.8.2", + "@icon-park/react": "^1.4.2", + "@reduxjs/toolkit": "^2.4.0", + "antd": "^5.22.2", + "antd-img-crop": "^4.24.0", + "cherry-markdown": "^0.8.55", + "dayjs": "^1.11.13", + "highlight.js": "^11.11.0", + "lodash.debounce": "^4.0.8", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-error-boundary": "^4.1.2", + "react-icons": "^5.4.0", + "react-markdown": "^8.0.3", + "react-redux": "^9.2.0", + "react-router-dom": "^6.28.0", + "rehype-raw": "^6.1.1", + "rehype-react": "^8.0.0", + "remark": "^15.0.1", + "remark-gfm": "^3.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^9.1.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/lodash.debounce": "^4.0.9", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "eslint": "^9.15.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.12.0", + "husky": "^9.1.7", + "lint-staged": "^15.2.10", + "postcss": "^8.4.49", + "prettier": "^3.4.1", + "prettier-plugin-tailwindcss": "^0.6.9", + "tailwindcss": "^3.4.15", + "typescript": "~5.6.2", + "typescript-eslint": "^8.15.0", + "vite": "^6.0.1" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ant-design/colors": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-7.1.0.tgz", + "integrity": "sha512-MMoDGWn1y9LdQJQSHiCC20x3uZ3CwQnv9QMz6pCmJOrqdgM9YxsoVVY0wtrdXbmfSgnV0KNk6zi09NAhMR2jvg==", + "dependencies": { + "@ctrl/tinycolor": "^3.6.1" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "1.22.0", + "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-1.22.0.tgz", + "integrity": "sha512-W9XSFeRPR0mAN3OuxfuS/xhENCYKf+8s+QyNNER0FSWoK9OpISTag6CCweg6lq0hASQ/2Vcza0Z8/kGivCP0Ng==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "classnames": "^2.3.1", + "csstype": "^3.1.3", + "rc-util": "^5.35.0", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.1.tgz", + "integrity": "sha512-2HAiyGGGnM0es40SxdszeQAU5iWp41wBIInq+ONTCKjlSKOrzQfnw4JDtB8IBmqE6tQaEKwmzTP2LGdt5DSwYQ==", + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "5.5.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.5.1.tgz", + "integrity": "sha512-0UrM02MA2iDIgvLatWrj6YTCYe0F/cwXvVE0E2SqGrL7PZireQwgEKTKBisWpZyal5eXZLvuM98kju6YtYne8w==", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==" + }, + "node_modules/@ant-design/pro-card": { + "version": "2.9.2", + "resolved": "https://registry.npmmirror.com/@ant-design/pro-card/-/pro-card-2.9.2.tgz", + "integrity": "sha512-mKOmNb7jc3Pz41RrPY7EFKRWBjLdN4tp9yzmRkS2g8K7P3pW435f7Ip6rc+58FWDzbZa8lElTGPxAoFB/dq7LA==", + "dependencies": { + "@ant-design/cssinjs": "^1.21.1", + "@ant-design/icons": "^5.0.0", + "@ant-design/pro-provider": "2.15.2", + "@ant-design/pro-utils": "2.16.2", + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "omit.js": "^2.0.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.4.0" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-components": { + "version": "2.8.2", + "resolved": "https://registry.npmmirror.com/@ant-design/pro-components/-/pro-components-2.8.2.tgz", + "integrity": "sha512-gSzt/Pw1ayZeHhxh5yaeP7pGpk0V2ZsB4PZab0s6V88O15Ql3w5ciYTObxbxGXMPc+A72AwVThoYLv2ZIl3cMA==", + "dependencies": { + "@ant-design/pro-card": "2.9.2", + "@ant-design/pro-descriptions": "2.6.2", + "@ant-design/pro-field": "2.17.2", + "@ant-design/pro-form": "2.31.2", + "@ant-design/pro-layout": "7.21.2", + "@ant-design/pro-list": "2.6.2", + "@ant-design/pro-provider": "2.15.2", + "@ant-design/pro-skeleton": "2.2.1", + "@ant-design/pro-table": "3.18.2", + "@ant-design/pro-utils": "2.16.2", + "@babel/runtime": "^7.16.3" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-descriptions": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/@ant-design/pro-descriptions/-/pro-descriptions-2.6.2.tgz", + "integrity": "sha512-IrXf4qNMyaypEhO54oZDOFNJ9jrQgg2ovARY7hHRZCChC+I2xVGFCFWXrmtyS82kusxHb6OlLw20ahm+TLZ71w==", + "dependencies": { + "@ant-design/pro-field": "2.17.2", + "@ant-design/pro-form": "2.31.2", + "@ant-design/pro-provider": "2.15.2", + "@ant-design/pro-skeleton": "2.2.1", + "@ant-design/pro-utils": "2.16.2", + "@babel/runtime": "^7.18.0", + "rc-resize-observer": "^0.2.3", + "rc-util": "^5.0.6" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-descriptions/node_modules/rc-resize-observer": { + "version": "0.2.6", + "resolved": "https://registry.npmmirror.com/rc-resize-observer/-/rc-resize-observer-0.2.6.tgz", + "integrity": "sha512-YX6nYnd6fk7zbuvT6oSDMKiZjyngjHoy+fz+vL3Tez38d/G5iGdaDJa2yE7345G6sc4Mm1IGRUIwclvltddhmA==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-util": "^5.0.0", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/pro-field": { + "version": "2.17.2", + "resolved": "https://registry.npmmirror.com/@ant-design/pro-field/-/pro-field-2.17.2.tgz", + "integrity": "sha512-cebfWGaE6MYwfchXpU9xA6jPETZOvk3i9+1IvebjSEKKVXecXuA+muZorpwYzORmkgGBmSPyR0KW+6Ttgtmg9Q==", + "dependencies": { + "@ant-design/icons": "^5.0.0", + "@ant-design/pro-provider": "2.15.2", + "@ant-design/pro-utils": "2.16.2", + "@babel/runtime": "^7.18.0", + "@chenshuai2144/sketch-color": "^1.0.8", + "classnames": "^2.3.2", + "dayjs": "^1.11.10", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "omit.js": "^2.0.2", + "rc-util": "^5.4.0", + "swr": "^2.0.0" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-form": { + "version": "2.31.2", + "resolved": "https://registry.npmmirror.com/@ant-design/pro-form/-/pro-form-2.31.2.tgz", + "integrity": "sha512-fzchlk+vGi8rCpmC62/SrikuwC2ZpyKnvNVAyihPCNe9oyyv+LD2TZAD0fbshfifP/1aHOOtS4fb7ptYq+LarQ==", + "dependencies": { + "@ant-design/icons": "^5.0.0", + "@ant-design/pro-field": "2.17.2", + "@ant-design/pro-provider": "2.15.2", + "@ant-design/pro-utils": "2.16.2", + "@babel/runtime": "^7.18.0", + "@chenshuai2144/sketch-color": "^1.0.7", + "@umijs/use-params": "^1.0.9", + "classnames": "^2.3.2", + "dayjs": "^1.11.10", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "omit.js": "^2.0.2", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.0.6" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "rc-field-form": ">=1.22.0", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-layout": { + "version": "7.21.2", + "resolved": "https://registry.npmmirror.com/@ant-design/pro-layout/-/pro-layout-7.21.2.tgz", + "integrity": "sha512-dtqap5YNDrxUWxhi43QJQSv1JLHYPCV4/h4cFM10HNiX/86Cxw37DiCOMdIM/ZwWk619BiwN7CJNgL5Q8obrAQ==", + "dependencies": { + "@ant-design/cssinjs": "^1.21.1", + "@ant-design/icons": "^5.0.0", + "@ant-design/pro-provider": "2.15.2", + "@ant-design/pro-utils": "2.16.2", + "@babel/runtime": "^7.18.0", + "@umijs/route-utils": "^4.0.0", + "@umijs/use-params": "^1.0.9", + "classnames": "^2.3.2", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "omit.js": "^2.0.2", + "path-to-regexp": "8.0.0", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.0.6", + "swr": "^2.0.0", + "warning": "^4.0.3" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-list": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/@ant-design/pro-list/-/pro-list-2.6.2.tgz", + "integrity": "sha512-BEM/WFe8vj4TCdsxa1JDQwl87Xb7oj+3bxA8yLDjRWWwX+D9UuxdYyB2lZsFfSEnphau/mccDE3K/Lbtim6yJg==", + "dependencies": { + "@ant-design/cssinjs": "^1.21.1", + "@ant-design/icons": "^5.0.0", + "@ant-design/pro-card": "2.9.2", + "@ant-design/pro-field": "2.17.2", + "@ant-design/pro-table": "3.18.2", + "@ant-design/pro-utils": "2.16.2", + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "dayjs": "^1.11.10", + "rc-resize-observer": "^1.0.0", + "rc-util": "^4.19.0" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-list/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/@ant-design/pro-list/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/@ant-design/pro-provider": { + "version": "2.15.2", + "resolved": "https://registry.npmmirror.com/@ant-design/pro-provider/-/pro-provider-2.15.2.tgz", + "integrity": "sha512-7WSJcjYIuLwco1YiiSgEEJnrqvg7x/YZap8pxOChRnyNh9S3HuV1D5HTc18kfHTpWqZWTAUcS66b0kMP96uKrw==", + "dependencies": { + "@ant-design/cssinjs": "^1.21.1", + "@babel/runtime": "^7.18.0", + "@ctrl/tinycolor": "^3.4.0", + "dayjs": "^1.11.10", + "rc-util": "^5.0.1", + "swr": "^2.0.0" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-skeleton": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/@ant-design/pro-skeleton/-/pro-skeleton-2.2.1.tgz", + "integrity": "sha512-3M2jNOZQZWEDR8pheY00OkHREfb0rquvFZLCa6DypGmiksiuuYuR9Y4iA82ZF+mva2FmpHekdwbje/GpbxqBeg==", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-table": { + "version": "3.18.2", + "resolved": "https://registry.npmmirror.com/@ant-design/pro-table/-/pro-table-3.18.2.tgz", + "integrity": "sha512-IIhWXvpBfdy1hqh0qYQOou6tDawrisFYwFhYdiMwuCnvy7UvaHi/JS4yikMe+KG0XVdh6xxfrF1Ad39SR8CrxQ==", + "dependencies": { + "@ant-design/cssinjs": "^1.21.1", + "@ant-design/icons": "^5.0.0", + "@ant-design/pro-card": "2.9.2", + "@ant-design/pro-field": "2.17.2", + "@ant-design/pro-form": "2.31.2", + "@ant-design/pro-provider": "2.15.2", + "@ant-design/pro-utils": "2.16.2", + "@babel/runtime": "^7.18.0", + "@dnd-kit/core": "^6.0.8", + "@dnd-kit/modifiers": "^6.0.1", + "@dnd-kit/sortable": "^7.0.2", + "@dnd-kit/utilities": "^3.2.1", + "classnames": "^2.3.2", + "dayjs": "^1.11.10", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "omit.js": "^2.0.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.0.1" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "rc-field-form": ">=1.22.0", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/pro-utils": { + "version": "2.16.2", + "resolved": "https://registry.npmmirror.com/@ant-design/pro-utils/-/pro-utils-2.16.2.tgz", + "integrity": "sha512-ama73ZSzz9O6Qz6DvHd6cnyUA3vI7N+AAl5BV5plijujtnXpNC8KJMXl9jOI1K7QuUVJgJIKbZ2DVm8LnBcTAQ==", + "dependencies": { + "@ant-design/icons": "^5.0.0", + "@ant-design/pro-provider": "2.15.2", + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "dayjs": "^1.11.10", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "rc-util": "^5.0.6", + "safe-stable-stringify": "^2.4.3", + "swr": "^2.0.0" + }, + "peerDependencies": { + "antd": "^4.24.15 || ^5.11.2", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@ant-design/react-slick": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "resize-observer-polyfill": "^1.5.1", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.2", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.26.2.tgz", + "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.2", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.26.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "6.0.4", + "resolved": "https://registry.npmmirror.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", + "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==", + "optional": true + }, + "node_modules/@chenshuai2144/sketch-color": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@chenshuai2144/sketch-color/-/sketch-color-1.0.9.tgz", + "integrity": "sha512-obzSy26cb7Pm7OprWyVpgMpIlrZpZ0B7vbrU0RMbvRg0YAI890S5Xy02Aj1Nhl4+KTbi1lVYHt6HQP8Hm9s+1w==", + "dependencies": { + "reactcss": "^1.2.3", + "tinycolor2": "^1.4.2" + }, + "peerDependencies": { + "react": ">=16.12.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/modifiers": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/@dnd-kit/modifiers/-/modifiers-6.0.1.tgz", + "integrity": "sha512-rbxcsg3HhzlcMHVHWDuh9LCjpOVAgqbV78wLGI8tziXY3+qcMQ61qVXIvNKQFuhj75dSfD+o+PYZQ/NUk2A23A==", + "dependencies": { + "@dnd-kit/utilities": "^3.2.1", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.0.6", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/@dnd-kit/sortable/-/sortable-7.0.2.tgz", + "integrity": "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==", + "dependencies": { + "@dnd-kit/utilities": "^3.2.0", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.0.7", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", + "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz", + "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", + "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz", + "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", + "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", + "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", + "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", + "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", + "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", + "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", + "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", + "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", + "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", + "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", + "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", + "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", + "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", + "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", + "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", + "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", + "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", + "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", + "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", + "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.0", + "resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.19.0.tgz", + "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.9.0", + "resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.9.0.tgz", + "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.15.0", + "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.15.0.tgz", + "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.3", + "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", + "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "dev": true, + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmmirror.com/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@icon-park/react": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/@icon-park/react/-/react-1.4.2.tgz", + "integrity": "sha512-+MtQLjNiRuia3fC/NfpSCTIy5KH5b+NkMB9zYd7p3R4aAIK61AjK0OSraaICJdkKooU9jpzk8m0fY4g9A3JqhQ==", + "engines": { + "node": ">= 8.0.0", + "npm": ">= 5.0.0" + }, + "peerDependencies": { + "react": ">=16.9", + "react-dom": ">=16.9" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.0.4.tgz", + "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "dependencies": { + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", + "classnames": "^2.2.6", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@rc-component/context/-/context-1.4.0.tgz", + "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", + "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", + "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@rc-component/qrcode/-/qrcode-1.0.0.tgz", + "integrity": "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==", + "dependencies": { + "@babel/runtime": "^7.24.7", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "1.15.1", + "resolved": "https://registry.npmmirror.com/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/portal": "^1.0.0-9", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/trigger": { + "version": "2.2.5", + "resolved": "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-2.2.5.tgz", + "integrity": "sha512-F1EJ4KjFpGAHAjuKvOyZB/6IZDkVx0bHl0M4fQM5wXcmm7lgTgVSSnR3bXwdmS6jOJGHOqfDxIJW3WUvwMIXhQ==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.38.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/@reduxjs/toolkit/-/toolkit-2.4.0.tgz", + "integrity": "sha512-wJZEuSKj14tvNfxiIiJws0tQN77/rDqucBq528ApebMIRHyWpCanJVQRxQ8WWZC19iCDKxDsGlbAir3F1layxA==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@remix-run/router": { + "version": "1.21.0", + "resolved": "https://registry.npmmirror.com/@remix-run/router/-/router-1.21.0.tgz", + "integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz", + "integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz", + "integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz", + "integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz", + "integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz", + "integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz", + "integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz", + "integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz", + "integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz", + "integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz", + "integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz", + "integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz", + "integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz", + "integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz", + "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz", + "integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz", + "integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz", + "integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz", + "integrity": "sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/codemirror": { + "version": "0.0.108", + "resolved": "https://registry.npmmirror.com/@types/codemirror/-/codemirror-0.0.108.tgz", + "integrity": "sha512-3FGFcus0P7C2UOGCNUVENqObEb4SFk+S8Dnxq7K6aIsLVs/vDtlangl3PEO0ykaKXyK56swVF6Nho7VsA44uhw==", + "dependencies": { + "@types/tern": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/dompurify": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/@types/dompurify/-/dompurify-2.4.0.tgz", + "integrity": "sha512-IDBwO5IZhrKvHFUl+clZxgf3hn2b/lU6H1KaBShPkQyGJUQ0xwebezIPSuiyGwfz1UzJWQl4M7BDxtHtCCPlTg==", + "dependencies": { + "@types/trusted-types": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/hast/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.17.13", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.13.tgz", + "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", + "dev": true + }, + "node_modules/@types/lodash.debounce": { + "version": "4.0.9", + "resolved": "https://registry.npmmirror.com/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", + "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/@types/mdurl/-/mdurl-1.0.5.tgz", + "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==" + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmmirror.com/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, + "node_modules/@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" + }, + "node_modules/@types/react": { + "version": "18.3.12", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/tern": { + "version": "0.23.9", + "resolved": "https://registry.npmmirror.com/@types/tern/-/tern-0.23.9.tgz", + "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmmirror.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz", + "integrity": "sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/type-utils": "8.16.0", + "@typescript-eslint/utils": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.16.0.tgz", + "integrity": "sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", + "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz", + "integrity": "sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/utils": "8.16.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.16.0.tgz", + "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", + "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.16.0.tgz", + "integrity": "sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", + "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.16.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@umijs/route-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/@umijs/route-utils/-/route-utils-4.0.1.tgz", + "integrity": "sha512-+1ixf1BTOLuH+ORb4x8vYMPeIt38n9q0fJDwhv9nSxrV46mxbLF0nmELIo9CKQB2gHfuC4+hww6xejJ6VYnBHQ==" + }, + "node_modules/@umijs/use-params": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@umijs/use-params/-/use-params-1.0.9.tgz", + "integrity": "sha512-QlN0RJSBVQBwLRNxbxjQ5qzqYIGn+K7USppMoIOVlf7fxXHsnQZ2bEsa6Pm74bt6DVQxpUE8HqvdStn6Y9FV1w==", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dev": true, + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead" + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/add-dom-event-listener": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz", + "integrity": "sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==", + "dependencies": { + "object-assign": "4.x" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antd": { + "version": "5.22.2", + "resolved": "https://registry.npmmirror.com/antd/-/antd-5.22.2.tgz", + "integrity": "sha512-vihhiJbm9VG3d6boUeD1q2MXMax+qBrXhgqCEC+45v8iGUF6m4Ct+lFiCW4oWaN3EABOsbVA6Svy3Rj/QkQFKw==", + "dependencies": { + "@ant-design/colors": "^7.1.0", + "@ant-design/cssinjs": "^1.21.1", + "@ant-design/cssinjs-utils": "^1.1.1", + "@ant-design/icons": "^5.5.1", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.25.7", + "@ctrl/tinycolor": "^3.6.1", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.0.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.2.5", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.30.0", + "rc-checkbox": "~3.3.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.2.0", + "rc-dropdown": "~4.2.0", + "rc-field-form": "~2.5.1", + "rc-image": "~7.11.0", + "rc-input": "~1.6.3", + "rc-input-number": "~9.3.0", + "rc-mentions": "~2.17.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.9.3", + "rc-notification": "~5.6.2", + "rc-pagination": "~4.3.0", + "rc-picker": "~4.8.1", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.0", + "rc-resize-observer": "^1.4.0", + "rc-segmented": "~2.5.0", + "rc-select": "~14.16.3", + "rc-slider": "~11.1.7", + "rc-steps": "~6.0.1", + "rc-switch": "~4.1.0", + "rc-table": "~7.48.1", + "rc-tabs": "~15.4.0", + "rc-textarea": "~1.8.2", + "rc-tooltip": "~6.2.1", + "rc-tree": "~5.10.1", + "rc-tree-select": "~5.24.4", + "rc-upload": "~4.8.1", + "rc-util": "^5.43.0", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd-img-crop": { + "version": "4.24.0", + "resolved": "https://registry.npmmirror.com/antd-img-crop/-/antd-img-crop-4.24.0.tgz", + "integrity": "sha512-RqY/XqvmUnHlj7oLV2kN/ytdZdHUFAZyM3TN+QlTlLrze1Q74isAePgG+QTkLAbrkR9L/IgVRpjsuH/nevpU7Q==", + "dependencies": { + "react-easy-crop": "^5.2.0", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "antd": ">=4.0.0", + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001684", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz", + "integrity": "sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cherry-markdown": { + "version": "0.8.55", + "resolved": "https://registry.npmmirror.com/cherry-markdown/-/cherry-markdown-0.8.55.tgz", + "integrity": "sha512-lA47p68ySktjens8CfH1W53PpozRU311yw2WX1So2VyVK8CEb5YlW/X4PcPQeCUMIQK+2EDBwdgn9TgKYUDLag==", + "dependencies": { + "@types/codemirror": "^0.0.108", + "@types/dompurify": "^2.2.3", + "crypto-js": "^4.2.0", + "jsdom": "~19.0.0" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "mermaid": "9.4.3" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", + "integrity": "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "optional": true, + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmmirror.com/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/cytoscape": { + "version": "3.30.4", + "resolved": "https://registry.npmmirror.com/cytoscape/-/cytoscape-3.30.4.tgz", + "integrity": "sha512-OxtlZwQl1WbwMmLiyPSEBuzeTIQnwZhJYYWFzZ2PhEHVFwpeaqNIkUzSiso00D98qk60l8Gwon2RP304d3BJ1A==", + "optional": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "optional": true, + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "optional": true, + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "optional": true, + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "optional": true + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmmirror.com/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "optional": true, + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "optional": true, + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "optional": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "optional": true, + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "optional": true, + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmmirror.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "optional": true, + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "optional": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "optional": true, + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "optional": true, + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "optional": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "optional": true, + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "optional": true, + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "optional": true, + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "optional": true, + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "optional": true, + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "optional": true, + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "optional": true, + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "optional": true, + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "optional": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.9", + "resolved": "https://registry.npmmirror.com/dagre-d3-es/-/dagre-d3-es-7.0.9.tgz", + "integrity": "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==", + "optional": true, + "dependencies": { + "d3": "^7.8.2", + "lodash-es": "^4.17.21" + } + }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "optional": true, + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dompurify": { + "version": "2.4.3", + "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-2.4.3.tgz", + "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==", + "optional": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.66", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.66.tgz", + "integrity": "sha512-pI2QF6+i+zjPbqRzJwkMvtvkdI7MjVbSh2g8dlMguDJIXEPw+kwasS1Jl+YGPEBfGVxsVgGUratAKymPdPo2vQ==", + "dev": true + }, + "node_modules/elkjs": { + "version": "0.8.2", + "resolved": "https://registry.npmmirror.com/elkjs/-/elkjs-0.8.2.tgz", + "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==", + "optional": true + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esbuild": { + "version": "0.24.0", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.24.0.tgz", + "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.0", + "@esbuild/android-arm": "0.24.0", + "@esbuild/android-arm64": "0.24.0", + "@esbuild/android-x64": "0.24.0", + "@esbuild/darwin-arm64": "0.24.0", + "@esbuild/darwin-x64": "0.24.0", + "@esbuild/freebsd-arm64": "0.24.0", + "@esbuild/freebsd-x64": "0.24.0", + "@esbuild/linux-arm": "0.24.0", + "@esbuild/linux-arm64": "0.24.0", + "@esbuild/linux-ia32": "0.24.0", + "@esbuild/linux-loong64": "0.24.0", + "@esbuild/linux-mips64el": "0.24.0", + "@esbuild/linux-ppc64": "0.24.0", + "@esbuild/linux-riscv64": "0.24.0", + "@esbuild/linux-s390x": "0.24.0", + "@esbuild/linux-x64": "0.24.0", + "@esbuild/netbsd-x64": "0.24.0", + "@esbuild/openbsd-arm64": "0.24.0", + "@esbuild/openbsd-x64": "0.24.0", + "@esbuild/sunos-x64": "0.24.0", + "@esbuild/win32-arm64": "0.24.0", + "@esbuild/win32-ia32": "0.24.0", + "@esbuild/win32-x64": "0.24.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "9.15.0", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.15.0.tgz", + "integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.15.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.5", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz", + "integrity": "sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.14", + "resolved": "https://registry.npmmirror.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.14.tgz", + "integrity": "sha512-aXvzCTK7ZBv1e7fahFuR3Z/fyQQSIQ711yPgYRj+Oj64tyTgO4iQIDmYXDBqvSWQ/FA4OSCsXOStlF+noU0/NA==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmmirror.com/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmmirror.com/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "15.12.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-15.12.0.tgz", + "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", + "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/hast-util-from-parse5/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmmirror.com/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/hast-util-raw/-/hast-util-raw-7.2.3.tgz", + "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/parse5": "^6.0.0", + "hast-util-from-parse5": "^7.0.0", + "hast-util-to-parse5": "^7.0.0", + "html-void-elements": "^2.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/hast-util-raw/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmmirror.com/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz", + "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==" + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", + "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/highlight.js": { + "version": "11.11.0", + "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.11.0.tgz", + "integrity": "sha512-6ErL7JlGu2CNFHyRQEuDogOyGPNiqcuWdt4iSSFUPyferNTGlNTPFqeV36Y/XwA4V/TJ8l0sxp6FTnxud/mf8g==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmmirror.com/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "19.0.0", + "resolved": "https://registry.npmmirror.com/jsdom/-/jsdom-19.0.0.tgz", + "integrity": "sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==", + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.5.0", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.1", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^10.0.0", + "ws": "^8.2.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==", + "optional": true + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmmirror.com/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "optional": true + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lint-staged": { + "version": "15.2.10", + "resolved": "https://registry.npmmirror.com/lint-staged/-/lint-staged-15.2.10.tgz", + "integrity": "sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==", + "dev": true, + "dependencies": { + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.3.6", + "execa": "~8.0.1", + "lilconfig": "~3.1.2", + "listr2": "~8.2.4", + "micromatch": "~4.0.8", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.5.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/listr2": { + "version": "8.2.5", + "resolved": "https://registry.npmmirror.com/listr2/-/listr2-8.2.5.tgz", + "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-definitions/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mdast-util-definitions/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/mdast-util-find-and-replace": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz", + "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mdast-util-from-markdown/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/mdast-util-gfm": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz", + "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==", + "dependencies": { + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-gfm-autolink-literal": "^1.0.0", + "mdast-util-gfm-footnote": "^1.0.0", + "mdast-util-gfm-strikethrough": "^1.0.0", + "mdast-util-gfm-table": "^1.0.0", + "mdast-util-gfm-task-list-item": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==", + "dependencies": { + "@types/mdast": "^3.0.0", + "ccount": "^2.0.0", + "mdast-util-find-and-replace": "^2.0.0", + "micromark-util-character": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mdast-util-gfm-autolink-literal/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz", + "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0", + "micromark-util-normalize-identifier": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mdast-util-gfm-footnote/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz", + "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mdast-util-gfm-strikethrough/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/mdast-util-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz", + "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==", + "dependencies": { + "@types/mdast": "^3.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mdast-util-gfm-table/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz", + "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mdast-util-gfm-task-list-item/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-subtokenize": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.3.tgz", + "integrity": "sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-mdx-expression/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz", + "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-subtokenize": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.3.tgz", + "integrity": "sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-mdx-jsx/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-subtokenize": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.3.tgz", + "integrity": "sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", + "dependencies": { + "@types/mdast": "^3.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mdast-util-phrasing/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/mdast-util-to-hast": { + "version": "11.3.0", + "resolved": "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-11.3.0.tgz", + "integrity": "sha512-4o3Cli3hXPmm1LhB+6rqhfsIUBjnKFlIUZvudaermXB+4/KONdd/W4saWWkC+LBLbPMqhFSSTSRgafHsT5fVJw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "@types/mdurl": "^1.0.0", + "mdast-util-definitions": "^5.0.0", + "mdurl": "^1.0.0", + "unist-builder": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mdast-util-to-hast/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/mdast-util-to-markdown": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^3.0.0", + "mdast-util-to-string": "^3.0.0", + "micromark-util-decode-string": "^1.0.0", + "unist-util-visit": "^4.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mdast-util-to-string/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mermaid": { + "version": "9.4.3", + "resolved": "https://registry.npmmirror.com/mermaid/-/mermaid-9.4.3.tgz", + "integrity": "sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw==", + "optional": true, + "dependencies": { + "@braintree/sanitize-url": "^6.0.0", + "cytoscape": "^3.23.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.1.0", + "d3": "^7.4.0", + "dagre-d3-es": "7.0.9", + "dayjs": "^1.11.7", + "dompurify": "2.4.3", + "elkjs": "^0.8.2", + "khroma": "^2.0.0", + "lodash-es": "^4.17.21", + "non-layered-tidy-tree-layout": "^2.0.2", + "stylis": "^4.1.2", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.0", + "web-worker": "^1.2.0" + } + }, + "node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz", + "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^1.0.0", + "micromark-extension-gfm-footnote": "^1.0.0", + "micromark-extension-gfm-strikethrough": "^1.0.0", + "micromark-extension-gfm-table": "^1.0.0", + "micromark-extension-gfm-tagfilter": "^1.0.0", + "micromark-extension-gfm-task-list-item": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz", + "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz", + "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==", + "dependencies": { + "micromark-core-commonmark": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz", + "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz", + "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz", + "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==", + "dependencies": { + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz", + "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/non-layered-tidy-tree-layout": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", + "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==", + "optional": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-wheel": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz", + "integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==" + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nwsapi": { + "version": "2.2.16", + "resolved": "https://registry.npmmirror.com/nwsapi/-/nwsapi-2.2.16.tgz", + "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/omit.js": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/omit.js/-/omit.js-2.0.2.tgz", + "integrity": "sha512-hJmu9D+bNB40YpL9jYebQl4lsTW6yEHRTroJzNLqQJYHm7c+NQnJGfZmIWh8S3q3KoaxV1aLhV6B3+0N0/kyJg==" + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.0.0.tgz", + "integrity": "sha512-GAWaqWlTjYK/7SVpIUA6CTxmcg65SP30sbjdCvyYReosRkk7Z/LyHWwkK3Vu0FcIi0FNTADUs4eh1AsU5s10cg==", + "engines": { + "node": ">=16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.9", + "resolved": "https://registry.npmmirror.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.9.tgz", + "integrity": "sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==", + "dev": true, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig-melody": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig-melody": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmmirror.com/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/rc-cascader": { + "version": "3.30.0", + "resolved": "https://registry.npmmirror.com/rc-cascader/-/rc-cascader-3.30.0.tgz", + "integrity": "sha512-rrzSbk1Bdqbu+pDwiLCLHu72+lwX9BZ28+JKzoi0DWZ4N29QYFeip8Gctl33QVd2Xg3Rf14D3yAOG76ElJw16w==", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "^2.3.1", + "rc-select": "~14.16.2", + "rc-tree": "~5.10.1", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/rc-checkbox/-/rc-checkbox-3.3.0.tgz", + "integrity": "sha512-Ih3ZaAcoAiFKJjifzwsGiT/f/quIkxJoklW4yKGho14Olulwn8gN7hOBve0/WGDg5o/l/5mL0w7ff7/YGvefVw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.25.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.9.0", + "resolved": "https://registry.npmmirror.com/rc-collapse/-/rc-collapse-3.9.0.tgz", + "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "resolved": "https://registry.npmmirror.com/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/rc-drawer/-/rc-drawer-7.2.0.tgz", + "integrity": "sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@rc-component/portal": "^1.1.1", + "classnames": "^2.2.6", + "rc-motion": "^2.6.1", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/rc-dropdown/-/rc-dropdown-4.2.0.tgz", + "integrity": "sha512-odM8Ove+gSh0zU27DUj5cG1gNKg7mLWBYzB5E4nNLrLwBmYEgYP43vHKDGOVZcJSVElQBI0+jTQgjnq0NfLjng==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-field-form": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-2.5.1.tgz", + "integrity": "sha512-33hunXwynQJyeae7LS3hMGTXNeRBjiPyPYgB0824EbmLHiXC1EBGyUwRh6xjLRy9c+en5WARYN0gJz5+JAqwig==", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/async-validator": "^5.0.3", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image": { + "version": "7.11.0", + "resolved": "https://registry.npmmirror.com/rc-image/-/rc-image-7.11.0.tgz", + "integrity": "sha512-aZkTEZXqeqfPZtnSdNUnKQA0N/3MbgR7nUnZ+/4MfSFWPFHZau4p5r5ShaI0KPEMnNjv4kijSCFq/9wtJpwykw==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.6.3", + "resolved": "https://registry.npmmirror.com/rc-input/-/rc-input-1.6.3.tgz", + "integrity": "sha512-wI4NzuqBS8vvKr8cljsvnTUqItMfG1QbJoxovCgL+DX4eVUcHIjVwharwevIxyy7H/jbLryh+K7ysnJr23aWIA==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.3.0", + "resolved": "https://registry.npmmirror.com/rc-input-number/-/rc-input-number-9.3.0.tgz", + "integrity": "sha512-JQ363ywqRyxwgVxpg2z2kja3CehTpYdqR7emJ/6yJjRdbvo+RvfE83fcpBCIJRq3zLp8SakmEXq60qzWyZ7Usw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.6.0", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "2.17.0", + "resolved": "https://registry.npmmirror.com/rc-mentions/-/rc-mentions-2.17.0.tgz", + "integrity": "sha512-sfHy+qLvc+p8jx8GUsujZWXDOIlIimp6YQz7N5ONQ6bHsa2kyG+BLa5k2wuxgebBbH97is33wxiyq5UkiXRpHA==", + "dependencies": { + "@babel/runtime": "^7.22.5", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-input": "~1.6.0", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.8.0", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.0", + "resolved": "https://registry.npmmirror.com/rc-menu/-/rc-menu-9.16.0.tgz", + "integrity": "sha512-vAL0yqPkmXWk3+YKRkmIR8TYj3RVdEt3ptG2jCJXWNAvQbT0VJJdRyHZ7kG/l1JsZlB+VJq/VcYOo69VR4oD+w==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.3", + "resolved": "https://registry.npmmirror.com/rc-motion/-/rc-motion-2.9.3.tgz", + "integrity": "sha512-rkW47ABVkic7WEB0EKJqzySpvDqwl60/tdkY7hWP7dYnh5pm0SzJpo54oW3TDUGXV5wfxXFmMkxrzRRbotQ0+w==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification": { + "version": "5.6.2", + "resolved": "https://registry.npmmirror.com/rc-notification/-/rc-notification-5.6.2.tgz", + "integrity": "sha512-Id4IYMoii3zzrG0lB0gD6dPgJx4Iu95Xu0BQrhHIbp7ZnAZbLqdqQ73aIWH0d0UFcElxwaKjnzNovTjo7kXz7g==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.9.0", + "rc-util": "^5.20.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/rc-overflow/-/rc-overflow-1.3.2.tgz", + "integrity": "sha512-nsUm78jkYAoPygDAcGZeC2VwIg/IBGSodtOY3pMof4W3M9qRJgqaDYm03ZayHlde3I6ipliAxbN0RUcGf5KOzw==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/rc-pagination/-/rc-pagination-4.3.0.tgz", + "integrity": "sha512-UubEWA0ShnroQ1tDa291Fzw6kj0iOeF26IsUObxYTpimgj4/qPCWVFl18RLZE+0Up1IZg0IK4pMn6nB3mjvB7g==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "4.8.2", + "resolved": "https://registry.npmmirror.com/rc-picker/-/rc-picker-4.8.2.tgz", + "integrity": "sha512-I6Nn4ngkRskSD//rsXDvjlEQ8CzX9kPQrUIb7+qTY49erJaa3/oKJWmi6JIxo/A7gy59phNmPTdhKosAa/NrQQ==", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/rc-progress": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.13.0", + "resolved": "https://registry.npmmirror.com/rc-rate/-/rc-rate-2.13.0.tgz", + "integrity": "sha512-oxvx1Q5k5wD30sjN5tqAyWTvJfLNNJn7Oq3IeS4HxWfAiC4BOXMITNAsw7u/fzdtO4MS8Ki8uRLOzcnEuoQiAw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/rc-resize-observer/-/rc-resize-observer-1.4.0.tgz", + "integrity": "sha512-PnMVyRid9JLxFavTjeDXEXo65HCRqbmLBw9xX9gfC4BZiSzbLXKzW3jPz+J0P71pLbD5tBMTT+mkstV5gD0c9Q==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.38.0", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/rc-segmented/-/rc-segmented-2.5.0.tgz", + "integrity": "sha512-B28Fe3J9iUFOhFJET3RoXAPFJ2u47QvLSYcZWC4tFYNGPEjug5LAxEasZlA/PpAxhdOPqGWsGbSj7ftneukJnw==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-select": { + "version": "14.16.3", + "resolved": "https://registry.npmmirror.com/rc-select/-/rc-select-14.16.3.tgz", + "integrity": "sha512-51+j6s3fJJJXB7E+B6W1hM4Tjzv1B/Decooz9ilgegDBt3ZAth1b/xMwYCTrT5BbG2e53XACQsyDib2+3Ro1fg==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.1.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.3.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-slider": { + "version": "11.1.7", + "resolved": "https://registry.npmmirror.com/rc-slider/-/rc-slider-11.1.7.tgz", + "integrity": "sha512-ytYbZei81TX7otdC0QvoYD72XSlxvTihNth5OeZ6PMXyEDq/vHdWFulQmfDGyXK1NwKwSlKgpvINOa88uT5g2A==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/rc-steps/-/rc-steps-6.0.1.tgz", + "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "dependencies": { + "@babel/runtime": "^7.16.7", + "classnames": "^2.2.3", + "rc-util": "^5.16.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/rc-switch/-/rc-switch-4.1.0.tgz", + "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "classnames": "^2.2.1", + "rc-util": "^5.30.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.48.1", + "resolved": "https://registry.npmmirror.com/rc-table/-/rc-table-7.48.1.tgz", + "integrity": "sha512-Z4mDKjWg+xz/Ezdw6ivWcbqRpaJ0QfCORRoRrlrw65KSGZLK8OcTdacH22/fyGb8L4It/0/9qcMm8VrVAk/WBw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/context": "^1.4.0", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.41.0", + "rc-virtual-list": "^3.14.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "15.4.0", + "resolved": "https://registry.npmmirror.com/rc-tabs/-/rc-tabs-15.4.0.tgz", + "integrity": "sha512-llKuyiAVqmXm2z7OrmhX5cNb2ueZaL8ZyA2P4R+6/72NYYcbEgOXibwHiQCFY2RiN3swXl53SIABi2CumUS02g==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.6.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.34.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "1.8.2", + "resolved": "https://registry.npmmirror.com/rc-textarea/-/rc-textarea-1.8.2.tgz", + "integrity": "sha512-UFAezAqltyR00a8Lf0IPAyTd29Jj9ee8wt8DqXyDMal7r/Cg/nDt3e1OOv3Th4W6mKaZijjgwuPXhAfVNTN8sw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-input": "~1.6.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/rc-tooltip/-/rc-tooltip-6.2.1.tgz", + "integrity": "sha512-rws0duD/3sHHsD905Nex7FvoUGy2UBQRhTkKxeEvr2FB+r21HsOxcDJI0TzyO8NHhnAA8ILr8pfbSBg5Jj5KBg==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree": { + "version": "5.10.1", + "resolved": "https://registry.npmmirror.com/rc-tree/-/rc-tree-5.10.1.tgz", + "integrity": "sha512-FPXb3tT/u39mgjr6JNlHaUTYfHkVGW56XaGDahDpEFLGsnPxGcVLNTjcqoQb/GNbSCycl7tD7EvIymwOTP0+Yw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select": { + "version": "5.24.5", + "resolved": "https://registry.npmmirror.com/rc-tree-select/-/rc-tree-select-5.24.5.tgz", + "integrity": "sha512-PnyR8LZJWaiEFw0SHRqo4MNQWyyZsyMs8eNmo68uXZWjxc7QqeWcjPPoONN0rc90c3HZqGF9z+Roz+GLzY5GXA==", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "2.x", + "rc-select": "~14.16.2", + "rc-tree": "~5.10.1", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-upload": { + "version": "4.8.1", + "resolved": "https://registry.npmmirror.com/rc-upload/-/rc-upload-4.8.1.tgz", + "integrity": "sha512-toEAhwl4hjLAI1u8/CgKWt30BR06ulPa4iGQSMvSXoHzO88gPCslxqV/mnn4gJU7PDoltGIC9Eh+wkeudqgHyw==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.43.0", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.43.0.tgz", + "integrity": "sha512-AzC7KKOXFqAdIBqdGWepL9Xn7cm3vnAmjlHqUnoQaTMZYhM4VlXGLkkHHxj/BZ7Td0+SOPKB4RGPboBVKT9htw==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-virtual-list": { + "version": "3.15.0", + "resolved": "https://registry.npmmirror.com/rc-virtual-list/-/rc-virtual-list-3.15.0.tgz", + "integrity": "sha512-dF2YQztqrU3ijAeWOqscTshCEr7vpimzSqAVjO1AyAmaqcHulaXpnGR0ptK5PXfxTUy48VkJOiglMIxlkYGs0w==", + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-easy-crop": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/react-easy-crop/-/react-easy-crop-5.2.0.tgz", + "integrity": "sha512-gjb7jN+WnwfgpbNUI2jSwyoIxF1sJ0PVSNVgEysAgF1rj8AqR75fqmdvqZ6PFVgEX3rT1G4HJELesiQXr2ZvAg==", + "dependencies": { + "normalize-wheel": "^1.0.1", + "tslib": "^2.0.1" + }, + "peerDependencies": { + "react": ">=16.4.0", + "react-dom": ">=16.4.0" + } + }, + "node_modules/react-error-boundary": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/react-error-boundary/-/react-error-boundary-4.1.2.tgz", + "integrity": "sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, + "node_modules/react-icons": { + "version": "5.4.0", + "resolved": "https://registry.npmmirror.com/react-icons/-/react-icons-5.4.0.tgz", + "integrity": "sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-markdown": { + "version": "8.0.7", + "resolved": "https://registry.npmmirror.com/react-markdown/-/react-markdown-8.0.7.tgz", + "integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/prop-types": "^15.0.0", + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "prop-types": "^15.0.0", + "property-information": "^6.0.0", + "react-is": "^18.0.0", + "remark-parse": "^10.0.0", + "remark-rehype": "^10.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/react-markdown/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/react-markdown/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/react-markdown/node_modules/mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/remark-parse": { + "version": "10.0.2", + "resolved": "https://registry.npmmirror.com/remark-parse/-/remark-parse-10.0.2.tgz", + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmmirror.com/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmmirror.com/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmmirror.com/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.28.0", + "resolved": "https://registry.npmmirror.com/react-router/-/react-router-6.28.0.tgz", + "integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==", + "dependencies": { + "@remix-run/router": "1.21.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.28.0", + "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.28.0.tgz", + "integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==", + "dependencies": { + "@remix-run/router": "1.21.0", + "react-router": "6.28.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "dependencies": { + "lodash": "^4.0.1" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/rehype-raw": { + "version": "6.1.1", + "resolved": "https://registry.npmmirror.com/rehype-raw/-/rehype-raw-6.1.1.tgz", + "integrity": "sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==", + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-raw": "^7.2.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/rehype-raw/node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmmirror.com/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmmirror.com/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-react": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/rehype-react/-/rehype-react-8.0.0.tgz", + "integrity": "sha512-vzo0YxYbB2HE+36+9HWXVdxNoNDubx63r5LBzpxBGVWM8s9mdnMdbmuJBAX6TTyuGdZjZix6qU3GcSuKCIWivw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-react/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/remark": { + "version": "15.0.1", + "resolved": "https://registry.npmmirror.com/remark/-/remark-15.0.1.tgz", + "integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==", + "dependencies": { + "@types/mdast": "^4.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/remark-gfm/-/remark-gfm-3.0.1.tgz", + "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-gfm": "^2.0.0", + "micromark-extension-gfm": "^2.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/remark-gfm/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/remark-gfm/node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmmirror.com/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmmirror.com/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmmirror.com/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse/node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse/node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/remark-parse/node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/remark-parse/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-subtokenize": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.3.tgz", + "integrity": "sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/remark-parse/node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/remark-parse/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-9.1.0.tgz", + "integrity": "sha512-oLa6YmgAYg19zb0ZrBACh40hpBLteYROaPLhBXzLgjqyHQrN+gVP9N/FJvfzuNNuzCutktkroXEZBrxAxKhh7Q==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^11.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/remark-rehype/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/remark-rehype/node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmmirror.com/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmmirror.com/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmmirror.com/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify/node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify/node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-stringify/node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-stringify/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-stringify/node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-stringify/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/remark-stringify/node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/remark-stringify/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "optional": true + }, + "node_modules/rollup": { + "version": "4.27.4", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.27.4.tgz", + "integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.27.4", + "@rollup/rollup-android-arm64": "4.27.4", + "@rollup/rollup-darwin-arm64": "4.27.4", + "@rollup/rollup-darwin-x64": "4.27.4", + "@rollup/rollup-freebsd-arm64": "4.27.4", + "@rollup/rollup-freebsd-x64": "4.27.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.27.4", + "@rollup/rollup-linux-arm-musleabihf": "4.27.4", + "@rollup/rollup-linux-arm64-gnu": "4.27.4", + "@rollup/rollup-linux-arm64-musl": "4.27.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.27.4", + "@rollup/rollup-linux-riscv64-gnu": "4.27.4", + "@rollup/rollup-linux-s390x-gnu": "4.27.4", + "@rollup/rollup-linux-x64-gnu": "4.27.4", + "@rollup/rollup-linux-x64-musl": "4.27.4", + "@rollup/rollup-win32-arm64-msvc": "4.27.4", + "@rollup/rollup-win32-ia32-msvc": "4.27.4", + "@rollup/rollup-win32-x64-msvc": "4.27.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "optional": true + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==" + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-to-object": { + "version": "0.4.4", + "resolved": "https://registry.npmmirror.com/style-to-object/-/style-to-object-0.4.4.tgz", + "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, + "node_modules/stylis": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.4.tgz", + "integrity": "sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==" + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swr": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/swr/-/swr-2.3.0.tgz", + "integrity": "sha512-NyZ76wA4yElZWBHzSgEJc28a0u6QZvhb6w0azeL2k7+Q1gAzVK+IqQYXhVOC/mzi+HZIozrZvBVeSeOZNR2bqA==", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "node_modules/tailwindcss": { + "version": "3.4.15", + "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.15.tgz", + "integrity": "sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-1.4.2.tgz", + "integrity": "sha512-ZF5gQIQa/UmzfvxbHZI3JXN0/Jt+vnAfAviNRAMc491laiK6YCLpCW9ft8oaCRFOTxCZtUTE6XB0ZQAe3olntw==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "optional": true, + "engines": { + "node": ">=6.10" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.16.0.tgz", + "integrity": "sha512-wDkVmlY6O2do4V+lZd0GtRfbtXbeD0q9WygwXXSJnC1xorE8eqyC2L1tJimqpSeFrOzRlYtWnUp/uzgHQOgfBQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.16.0", + "@typescript-eslint/parser": "8.16.0", + "@typescript-eslint/utils": "8.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmmirror.com/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-builder": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/unist-builder/-/unist-builder-3.0.1.tgz", + "integrity": "sha512-gnpOw7DIpCA0vpr6NqdPvTWnlPTApCTRzr+38E6hCWx3rz/cjo83SsKIlS1Z+L5ttScQ2AwutNnb8+tAvpb6qQ==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-builder/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/unist-util-visit/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmmirror.com/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmmirror.com/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/vfile-location/-/vfile-location-4.1.0.tgz", + "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", + "dependencies": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/vfile-location/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmmirror.com/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.0.1.tgz", + "integrity": "sha512-Ldn6gorLGr4mCdFnmeAOLweJxZ34HjKnDm4HGo6P66IEqTxQb36VEdFJQENKxWjupNfoIjvRUnswjn1hpYEpjQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.24.0", + "postcss": "^8.4.49", + "rollup": "^4.23.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", + "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/web-worker": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/web-worker/-/web-worker-1.3.0.tgz", + "integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==", + "optional": true + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-10.0.0.tgz", + "integrity": "sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..bf858f2 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,68 @@ +{ + "name": "kamanotes-tech", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview", + "format": "prettier --write .", + "format:check": "prettier --check .", + "prepare": "cd .. && husky frontend/.husky", + "lint-staged": "lint-staged" + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx,json,css,md}": [ + "prettier --write" + ] + }, + "dependencies": { + "@ant-design/colors": "^7.1.0", + "@ant-design/icons": "^5.5.1", + "@ant-design/pro-components": "^2.8.2", + "@icon-park/react": "^1.4.2", + "@reduxjs/toolkit": "^2.4.0", + "antd": "^5.22.2", + "antd-img-crop": "^4.24.0", + "cherry-markdown": "^0.8.55", + "dayjs": "^1.11.13", + "highlight.js": "^11.11.0", + "lodash.debounce": "^4.0.8", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-error-boundary": "^4.1.2", + "react-icons": "^5.4.0", + "react-markdown": "^8.0.3", + "react-redux": "^9.2.0", + "react-router-dom": "^6.28.0", + "rehype-raw": "^6.1.1", + "rehype-react": "^8.0.0", + "remark": "^15.0.1", + "remark-gfm": "^3.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^9.1.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/lodash.debounce": "^4.0.9", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "eslint": "^9.15.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.12.0", + "husky": "^9.1.7", + "lint-staged": "^15.2.10", + "postcss": "^8.4.49", + "prettier": "^3.4.1", + "prettier-plugin-tailwindcss": "^0.6.9", + "tailwindcss": "^3.4.15", + "typescript": "~5.6.2", + "typescript-eslint": "^8.15.0", + "vite": "^6.0.1" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000..2a27ce1 Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..8e1c663 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,33 @@ +import './App.css' +import { Routes } from 'react-router-dom' +import { AdminRouteConfig } from './apps/admin/router' +import { UserRouteConfig } from './apps/user/router' +import { ErrorBoundary } from 'react-error-boundary' +import { ErrorFallback } from './base/components' +import { useLogin } from './domain/user' +import { useEffect } from 'react' +import './base/styles/github-markdown.css' +import './base/styles/github-markdown-light.css' + +function App() { + /** + * 自动登录功能 + */ + const { whoAmIHandle } = useLogin() + + useEffect(() => { + whoAmIHandle().then() + }, [whoAmIHandle]) + + return ( + + + {AdminRouteConfig} + {UserRouteConfig} + + {/**/} + + ) +} + +export default App diff --git a/frontend/src/apps/admin/AdminApp.css b/frontend/src/apps/admin/AdminApp.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/apps/admin/AdminApp.tsx b/frontend/src/apps/admin/AdminApp.tsx new file mode 100644 index 0000000..0d054d8 --- /dev/null +++ b/frontend/src/apps/admin/AdminApp.tsx @@ -0,0 +1,163 @@ +import React, { useEffect, useState } from 'react' +import { + AppstoreOutlined, + BarChartOutlined, + CloudOutlined, + ShopOutlined, + UserOutlined, +} from '@ant-design/icons' +import type { MenuProps } from 'antd' +import { Layout, Menu, theme } from 'antd' +import { Link, Outlet, useLocation } from 'react-router-dom' +import { + ADMIN_HOME, + CATEGORY_MANAGE, + NOTIFICATION_MANAGE, + QUESTION_LIST_MANAGE, + QUESTION_MANAGE, + USER_MANAGE, +} from './router/config.ts' +import { useDispatch } from 'react-redux' +import { intoAdminApp } from '../../store/appSlice.ts' +import { UserAvatarMenu } from '../../domain/user' +import { useUser } from '../../domain/user/hooks/useUser.ts' + +const { Header, Content, Sider } = Layout + +const siderStyle: React.CSSProperties = { + overflow: 'auto', + height: '100vh', + position: 'fixed', + insetInlineStart: 0, + top: 0, + bottom: 0, + scrollbarWidth: 'thin', + scrollbarGutter: 'stable', +} + +const items: MenuProps['items'] = [ + { + key: ADMIN_HOME, + label: 仪表盘, + icon: , + }, + { + key: USER_MANAGE, + label: 用户管理, + icon: , + }, + { + key: CATEGORY_MANAGE, + label: 分类管理, + icon: , + }, + { + key: NOTIFICATION_MANAGE, + label: 通知管理, + icon: , + }, + { + key: QUESTION_MANAGE, + label: 问题管理, + icon: , + }, + { + key: QUESTION_LIST_MANAGE, + label: 题单管理, + icon: , + }, +] + +const AdminApp: React.FC = () => { + const [collapsed, setCollapsed] = useState(false) + + const { + token: { colorBgContainer, borderRadiusLG }, + } = theme.useToken() + + /** + * 监听路由变化,设置选中菜单项 + */ + const location = useLocation() + const [selectedKeys, setSelectedKeys] = useState(['']) + + // TODO: 检测路由变化时需要处理题单模块的特殊情况 + useEffect(() => { + if (location.pathname.startsWith(QUESTION_LIST_MANAGE)) { + setSelectedKeys([QUESTION_LIST_MANAGE]) + } else { + setSelectedKeys([location.pathname]) + } + }, [location.pathname]) + + /** + * 设置 Redux 中 isAdminApp + */ + const dispatch = useDispatch() + + useEffect(() => { + dispatch(intoAdminApp()) + }, [dispatch]) + + const user = useUser() + + if (!user.isAdmin) { + return
无权限
+ } + + return ( + + setCollapsed(value)} + > + + + +
+
+ +
+
+ +
+ +
+
+
+ + ) +} + +export default AdminApp diff --git a/frontend/src/apps/admin/pages/adminCategory/AdminCategory.tsx b/frontend/src/apps/admin/pages/adminCategory/AdminCategory.tsx new file mode 100644 index 0000000..bee9d09 --- /dev/null +++ b/frontend/src/apps/admin/pages/adminCategory/AdminCategory.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { CategoryList } from '../../../../domain/category' + +const AdminCategory: React.FC = () => { + return ( +
+ +
+ ) +} + +export default AdminCategory diff --git a/frontend/src/apps/admin/pages/adminNotification/AdminNotification.tsx b/frontend/src/apps/admin/pages/adminNotification/AdminNotification.tsx new file mode 100644 index 0000000..ccfa99d --- /dev/null +++ b/frontend/src/apps/admin/pages/adminNotification/AdminNotification.tsx @@ -0,0 +1,42 @@ +import React, { useEffect, useState } from 'react' +import { useNotification } from '../../../../domain/notification' +import { MarkdownEditor } from '../../../../base/components' +import { Button, message } from 'antd' + +const AdminNotification: React.FC = () => { + const { notification, setNotificationService } = useNotification() + + const [value, setValue] = useState(notification?.content ?? '') + + const [loading, setLoading] = useState(false) + + const setValueHandle = (newValue: string) => { + setValue(newValue) + } + + useEffect(() => { + setValue(notification?.content ?? '') + }, [notification, notification?.content]) + + const clickHandle = async () => { + setLoading(true) + await setNotificationService({ content: value }) + setLoading(false) + message.success('更新成功') + } + + return ( +
+
+ +
+
+ +
+
+ ) +} + +export default AdminNotification diff --git a/frontend/src/apps/admin/pages/adminQuestion/AdminQuestion.tsx b/frontend/src/apps/admin/pages/adminQuestion/AdminQuestion.tsx new file mode 100644 index 0000000..433f1b5 --- /dev/null +++ b/frontend/src/apps/admin/pages/adminQuestion/AdminQuestion.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { QuestionList } from '../../../../domain/question' + +const AdminQuestion: React.FC = () => { + return ( +
+ +
+ ) +} + +export default AdminQuestion diff --git a/frontend/src/apps/admin/pages/adminQuestionList/AdminQuestionList.tsx b/frontend/src/apps/admin/pages/adminQuestionList/AdminQuestionList.tsx new file mode 100644 index 0000000..cc16908 --- /dev/null +++ b/frontend/src/apps/admin/pages/adminQuestionList/AdminQuestionList.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import QuestionListTable from '../../../../domain/questionList/components/QuestionListTable.tsx' + +const AdminQuestionList: React.FC = () => { + return ( +
+ +
+ ) +} + +export default AdminQuestionList diff --git a/frontend/src/apps/admin/pages/adminQuestionListDetail/AdminQuestionListDetail.tsx b/frontend/src/apps/admin/pages/adminQuestionListDetail/AdminQuestionListDetail.tsx new file mode 100644 index 0000000..d31a085 --- /dev/null +++ b/frontend/src/apps/admin/pages/adminQuestionListDetail/AdminQuestionListDetail.tsx @@ -0,0 +1,141 @@ +import React, { useState } from 'react' +import { useParams } from 'react-router-dom' +import { useQuestionListItem } from '../../../../domain/questionList' +import { useQuestionList2 } from '../../../../domain/questionList' +import { DragSortTable, ProColumns } from '@ant-design/pro-components' +import { Button, Descriptions, message, Popconfirm } from 'antd' +import { FaRegTrashAlt } from 'react-icons/fa' +import { Add } from '@icon-park/react' +import { QuestionVO, SearchQuestionModal } from '../../../../domain/question' +import { QuestionListItemVO } from '../../../../domain/questionList/types/types.ts' + +const AdminQuestionListDetail: React.FC = () => { + /** + * 题单 ID + */ + const { questionListId } = useParams() + + /** + * Table 配置 + */ + const sortedColumns: ProColumns[] = [ + { + title: '排序', + dataIndex: 'rank', + width: '5%', + className: 'drag-visible', + }, + { + title: '标题', + dataIndex: 'question', + className: 'drag-visible', + renderText: (_, record) => { + return
{record.question.title}
+ }, + width: '80%', + }, + { + title: '操作', + dataIndex: 'operation', + renderText: (_, record) => { + return ( +
+ { + await deleteQuestionListItem( + Number(questionListId), + record.question.questionId, + ) + }} + > + + +
+ ) + }, + width: '15%', + }, + ] + + /** + * 题单详细信息 + */ + const { questionList } = useQuestionList2(Number(questionListId)) + + /** + * 题单项列表 + */ + const { + questionListItems, + createQuestionListItem, + deleteQuestionListItem, + sortListItemVO, + } = useQuestionListItem(Number(questionListId)) + + /** + * 题单描述信息 + */ + const items = [ + { + label: '题集描述', + span: 3, + children: questionList?.description, + }, + { + label: '题集类型', + span: 3, + children: questionList?.type, + }, + ] + + const [isModalOpen, setIsModalOpen] = useState(false) + + const toggleIsModalOpen = () => { + setIsModalOpen(!isModalOpen) + } + + const handleDragSortEnd = async ( + _: number, + __: number, + newDataSource: QuestionListItemVO[], + ) => { + await sortListItemVO(newDataSource) + message.success('排序成功') + } + + return ( +
+ +
+ +
+ + { + await createQuestionListItem(Number(questionListId), item) + }} + /> +
+ ) +} + +export default AdminQuestionListDetail diff --git a/frontend/src/apps/admin/pages/adminUser/AdminUser.tsx b/frontend/src/apps/admin/pages/adminUser/AdminUser.tsx new file mode 100644 index 0000000..d5ec5b4 --- /dev/null +++ b/frontend/src/apps/admin/pages/adminUser/AdminUser.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { UserList } from '../../../../domain/user' + +const AdminUser: React.FC = () => { + return ( +
+ +
+ ) +} + +export default AdminUser diff --git a/frontend/src/apps/admin/pages/dashBroad/DashBroad.tsx b/frontend/src/apps/admin/pages/dashBroad/DashBroad.tsx new file mode 100644 index 0000000..9d18a40 --- /dev/null +++ b/frontend/src/apps/admin/pages/dashBroad/DashBroad.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { StatisticTable } from '../../../../domain/statistic' + +const DashBroad: React.FC = () => { + return ( +
+ +
+ ) +} + +export default DashBroad diff --git a/frontend/src/apps/admin/router/config.ts b/frontend/src/apps/admin/router/config.ts new file mode 100644 index 0000000..8096dfc --- /dev/null +++ b/frontend/src/apps/admin/router/config.ts @@ -0,0 +1,29 @@ +/** + * 管理员主页 + */ +export const ADMIN_HOME = '/admin' + +/** + * 用户管理页面 + */ +export const USER_MANAGE = '/admin/user' + +/** + * 题目管理页面 + */ +export const QUESTION_MANAGE = '/admin/question' + +/** + * 分类管理 + */ +export const CATEGORY_MANAGE = '/admin/category' + +/** + * 题单管理 + */ +export const QUESTION_LIST_MANAGE = '/admin/question-list' + +/** + * 通知管理界面 + */ +export const NOTIFICATION_MANAGE = '/admin/notification' diff --git a/frontend/src/apps/admin/router/index.tsx b/frontend/src/apps/admin/router/index.tsx new file mode 100644 index 0000000..70a5a82 --- /dev/null +++ b/frontend/src/apps/admin/router/index.tsx @@ -0,0 +1,94 @@ +import { Route } from 'react-router-dom' +import { lazy, Suspense } from 'react' +import { NotFound } from '../../../base/components' +import AdminApp from '../../admin/AdminApp.tsx' +import { + ADMIN_HOME, + CATEGORY_MANAGE, + NOTIFICATION_MANAGE, + QUESTION_LIST_MANAGE, + QUESTION_MANAGE, + USER_MANAGE, +} from './config.ts' + +// 使用 React.lazy, 可显著减少打包后的 index.js 体积 +const DashBroad = lazy(() => import('../pages/dashBroad/DashBroad.tsx')) +const AdminUser = lazy(() => import('../pages/adminUser/AdminUser.tsx')) +const AdminQuestion = lazy( + () => import('../pages/adminQuestion/AdminQuestion.tsx'), +) +const AdminCategory = lazy( + () => import('../pages/adminCategory/AdminCategory.tsx'), +) +const AdminQuestionList = lazy( + () => import('../pages/adminQuestionList/AdminQuestionList.tsx'), +) +const AdminQuestionListDetail = lazy( + () => import('../pages/adminQuestionListDetail/AdminQuestionListDetail.tsx'), +) +const AdminNotification = lazy( + () => import('../pages/adminNotification/AdminNotification.tsx'), +) + +// 用 Suspense 包裹懒加载的组件,提供加载时的回退 UI +export const AdminRouteConfig = ( + }> + Loading...}> + + + } + /> + Loading...}> + + + } + /> + Loading...}> + + + } + /> + Loading...}> + + + } + /> + Loading...}> + + + } + /> + Loading...}> + + + } + /> + Loading...}> + + + } + /> + } /> + +) diff --git a/frontend/src/apps/user/UserApp.css b/frontend/src/apps/user/UserApp.css new file mode 100644 index 0000000..a6add23 --- /dev/null +++ b/frontend/src/apps/user/UserApp.css @@ -0,0 +1,22 @@ +:root { + /* 头部区域高度 */ + --header-height: 50px; +} + +/* 头部区域高度*/ +.header-height { + height: var(--header-height); +} + +/* 内容区域高度 */ +.content-height { + height: calc(100% - var(--header-height)); +} + +.ant-menu-horizontal { + border-bottom: none; +} + +.ant-menu.ant-menu-root.ant-menu-vertical { + border-inline-end: none; +} diff --git a/frontend/src/apps/user/UserApp.tsx b/frontend/src/apps/user/UserApp.tsx new file mode 100644 index 0000000..99bf59f --- /dev/null +++ b/frontend/src/apps/user/UserApp.tsx @@ -0,0 +1,32 @@ +import React, { useEffect } from 'react' +import { Outlet } from 'react-router-dom' +import './UserApp.css' +import Layout from './layout/Layout.tsx' +import Header from './layout/Header.tsx' +import Content from './layout/Content.tsx' +import NavBar from './components/navBar/NavBar.tsx' +import { useDispatch } from 'react-redux' +import { outAdminApp } from '../../store/appSlice.ts' +const UserApp: React.FC = () => { + /** + * 初始化 appState 中的 isAdminApp + */ + const dispatch = useDispatch() + + useEffect(() => { + dispatch(outAdminApp()) + }, [dispatch]) + + return ( + +
+ +
+ + + +
+ ) +} + +export default UserApp diff --git a/frontend/src/apps/user/components/logo/Logo.tsx b/frontend/src/apps/user/components/logo/Logo.tsx new file mode 100644 index 0000000..4f85ccf --- /dev/null +++ b/frontend/src/apps/user/components/logo/Logo.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +/** + * LOGO 可自定义 + */ +const Logo: React.FC = () => { + return ( +
+ 卡码笔记 +
+ ) +} + +export default Logo diff --git a/frontend/src/apps/user/components/navBar/NavBar.tsx b/frontend/src/apps/user/components/navBar/NavBar.tsx new file mode 100644 index 0000000..859c554 --- /dev/null +++ b/frontend/src/apps/user/components/navBar/NavBar.tsx @@ -0,0 +1,73 @@ +import React, { useEffect, useState } from 'react' +import { Menu } from 'antd' +import { NavLink, useLocation } from 'react-router-dom' +import { MenuProps } from 'antd' +import { HOME_PAGE, QUESTION_LIST, QUESTION_SET } from '../../router/config.ts' +import Logo from '../logo/Logo.tsx' +import { useApp } from '../../../../base/hooks' +import { LoginModal, UserAvatarMenu } from '../../../../domain/user' +import SearchInput from '../searchInput/SearchInput.tsx' +import { ColumnDivider } from '../../../../base/components' +import DownloadNoteItem from '../../../../domain/note/components/DownloadNoteItem.tsx' + +type MenuItem = Required['items'][number] + +const items: MenuItem[] = [ + { + label: 首页, + key: 'home', + }, + { + label: 题库, + key: 'question-set', + }, + { + label: 题单, + key: 'question-list', + }, +] + +const NavBar: React.FC = () => { + /** + * 监听路由变化,设置选中菜单项 + */ + const [selectedMenuItem, setSelectedMenuItem] = useState() + const location = useLocation() + + useEffect(() => { + if (location.pathname === '/') { + setSelectedMenuItem(['home']) + } else { + setSelectedMenuItem([location.pathname.split('/')[1]]) + } + }, [location.pathname]) + + /** + * 获取 app 信息 + */ + const app = useApp() + + return ( + + ) +} + +export default NavBar diff --git a/frontend/src/apps/user/components/searchInput/SearchInput.tsx b/frontend/src/apps/user/components/searchInput/SearchInput.tsx new file mode 100644 index 0000000..dd58add --- /dev/null +++ b/frontend/src/apps/user/components/searchInput/SearchInput.tsx @@ -0,0 +1,44 @@ +import React, { useState } from 'react' +import Search from 'antd/es/input/Search' +import { InputRef } from 'antd' +import { QuestionVO, SearchQuestionModal } from '../../../../domain/question' +import { useNavigate } from 'react-router-dom' +import { QUESTION } from '../../router/config.ts' + +const SearchInput: React.FC = () => { + const [keyword, setKeyword] = useState('') + const searchInputRef = React.createRef() + + const [isModalOpen, setIsModalOpen] = useState(false) + + const toggleIsModalOpen = () => { + setIsModalOpen(!isModalOpen) + } + + const navigate = useNavigate() + + return ( + <> + setKeyword(value)} + onChange={(e) => setKeyword(e.target.value)} + onFocus={() => { + toggleIsModalOpen() + searchInputRef.current?.blur() + }} + width={450} + > + { + navigate(`${QUESTION}/${item.questionId}`) + }} + /> + + ) +} + +export default SearchInput diff --git a/frontend/src/apps/user/layout/Content.tsx b/frontend/src/apps/user/layout/Content.tsx new file mode 100644 index 0000000..598a745 --- /dev/null +++ b/frontend/src/apps/user/layout/Content.tsx @@ -0,0 +1,15 @@ +import React from 'react' + +interface ContentProps { + children: React.ReactNode +} + +const Content: React.FC = ({ children }) => { + return ( +
+
{children}
+
+ ) +} + +export default Content diff --git a/frontend/src/apps/user/layout/Header.tsx b/frontend/src/apps/user/layout/Header.tsx new file mode 100644 index 0000000..6f3d6e3 --- /dev/null +++ b/frontend/src/apps/user/layout/Header.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +interface HeaderProps { + children?: React.ReactNode +} + +const Header: React.FC = ({ children }) => { + return
{children}
+} + +export default Header diff --git a/frontend/src/apps/user/layout/Layout.tsx b/frontend/src/apps/user/layout/Layout.tsx new file mode 100644 index 0000000..710eb4b --- /dev/null +++ b/frontend/src/apps/user/layout/Layout.tsx @@ -0,0 +1,15 @@ +import React from 'react' + +interface LayoutProps { + children: React.ReactNode +} + +const Layout: React.FC = ({ children }) => { + return ( +
+ {children} +
+ ) +} + +export default Layout diff --git a/frontend/src/apps/user/pages/home/HomePage.tsx b/frontend/src/apps/user/pages/home/HomePage.tsx new file mode 100644 index 0000000..725b1cb --- /dev/null +++ b/frontend/src/apps/user/pages/home/HomePage.tsx @@ -0,0 +1,63 @@ +import React, { useState } from 'react' +import { NoteList, NoteQueryParams, useNotes } from '../../../../domain/note' +import { Panel } from '../../../../base/components' +import { Divider, Skeleton } from 'antd' +import RankList from './components/RankList.tsx' +import { NoteHeatMap } from '../../../../domain/note' +import { Top3Count } from '../../../../domain/note' +import { useApp } from '../../../../base/hooks' + +const HomePage: React.FC = () => { + const [searchParams, setSearchParams] = useState({ + page: 1, + pageSize: 10, + sort: 'create', + order: 'desc', + }) + + const setSearchParamsHandle = (params: NoteQueryParams) => { + setSearchParams((prev) => ({ ...prev, ...params })) + } + + const { + noteList, + pagination, + setNoteLikeStatusHandle, + setNoteCollectStatusHandle, + loading, + } = useNotes(searchParams) + + const app = useApp() + + return ( +
+
+ +
近期笔记
+ + + + +
+
+
+ + {app.isLogin && ( + + + + + )} +
+
+ ) +} + +export default HomePage diff --git a/frontend/src/apps/user/pages/home/components/RankList.tsx b/frontend/src/apps/user/pages/home/components/RankList.tsx new file mode 100644 index 0000000..ba4f224 --- /dev/null +++ b/frontend/src/apps/user/pages/home/components/RankList.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import { Divider } from 'antd' +import { NoteRankList } from '../../../../../domain/note' +import { Panel } from '../../../../../base/components' + +const RankList: React.FC = () => { + return ( + +
+ 今日笔记排行榜 +
+ + +
+ ) +} + +export default RankList diff --git a/frontend/src/apps/user/pages/question/QuestionPage.tsx b/frontend/src/apps/user/pages/question/QuestionPage.tsx new file mode 100644 index 0000000..9a3546b --- /dev/null +++ b/frontend/src/apps/user/pages/question/QuestionPage.tsx @@ -0,0 +1,194 @@ +import React, { Suspense, useEffect, useState } from 'react' +import { useParams } from 'react-router-dom' +import { QuestionView, useQuestion } from '../../../../domain/question' +import { + MarkdownEditor, + MarkdownRender, + Panel, +} from '../../../../base/components' +import { Button, message, Modal, Spin } from 'antd' +import { Upload } from '@icon-park/react' +import { EyeOutlined } from '@ant-design/icons' +import { NoteList, NoteQueryParams, useNotes } from '../../../../domain/note' +import { useApp } from '../../../../base/hooks' + +const QuestionPage: React.FC = () => { + /** + * 地址栏参数 + */ + const { questionId } = useParams() + + /** + * 获取问题携带用户相关笔记的问题详情 + */ + const { question, userFinishedQuestion } = useQuestion(Number(questionId)) + + /** + * 笔记内容 + */ + const [value, setValue] = useState(question?.userNote.content ?? '') + const setValueHandle = (value: string) => { + setValue(value) + } + + useEffect(() => { + if (question?.userNote) { + if (question?.userNote.finished) { + setValueHandle(question?.userNote.content) + } + } + }, [question]) + + /** + * 控制编辑器显示隐藏功能 + */ + const [isEditorVisible, setIsEditorVisible] = useState(false) + const toggleEditorVisible = () => { + setIsEditorVisible(!isEditorVisible) + } + + /** + * 写笔记 / 编辑笔记按钮点击事件 + */ + function writeOrEditButtonHandle() { + toggleEditorVisible() + } + + /** + * 获取和问题相关的笔记列表 + */ + const [noteQueryParams, setNoteQueryParams] = useState({ + page: 1, + pageSize: 10, + questionId: Number(questionId), + }) + + const { + noteList, + pagination, + createNoteHandle, + updateNoteHandle, + setNoteLikeStatusHandle, + setNoteCollectStatusHandle, + } = useNotes(noteQueryParams) + + /** + * 提交笔记处理事件 + */ + const [createBtnLoading, setCreateBtnLoading] = useState(false) + + /** + * 用户信息 + * app 信息 + */ + const app = useApp() + + const createOrUpdateNoteClickHandle = async () => { + if (!app.isLogin) { + message.info('请先登录') + return + } + + setCreateBtnLoading(true) + + try { + if (!question?.userNote.finished) { + const noteId = await createNoteHandle(Number(questionId), value) + toggleEditorVisible() + // 校验一下 noteId + if (noteId) { + userFinishedQuestion(noteId, value) + } + message.success('笔记已提交') + } else { + // 修改笔记操作 + if (!question?.userNote) return + await updateNoteHandle(question?.userNote.noteId, { + content: value, + questionId: Number(questionId), + }) + message.success('笔记已修改') + toggleEditorVisible() + } + } catch (e: any) { + console.log(e.message) + message.error(e.message) + } finally { + setCreateBtnLoading(false) + } + } + + const [isShowPreview, setIsShowPreview] = useState(false) + + return ( + <> + + {/* 编辑器 */} + {isEditorVisible && ( +
+
+
+ + {''} + + } + > + + +
+
+ + +
+
+
+ )} + {/* 预览框 */} + setIsShowPreview(false)} + footer={null} + width={1000} + > + + +
+
+ + + +
+
+ + ) +} + +export default QuestionPage diff --git a/frontend/src/apps/user/pages/questionList/QuestionListPage.tsx b/frontend/src/apps/user/pages/questionList/QuestionListPage.tsx new file mode 100644 index 0000000..332bbc7 --- /dev/null +++ b/frontend/src/apps/user/pages/questionList/QuestionListPage.tsx @@ -0,0 +1,129 @@ +import React, { useEffect, useState } from 'react' +import { Panel } from '../../../../base/components' +import { + convertQuestionListToTreeStruct, + QuestionListTreeView, +} from '../../../../domain/questionList' +import { QuestionListParentNode } from '../../../../domain/questionList' +import { useQuestionLists } from '../../../../domain/questionList' +import { useSearchParams } from 'react-router-dom' +import TrainingCampListInfo from './components/TrainingCampListInfo.tsx' +import { useQuestionListItem2 } from '../../../../domain/questionList' +import { QuestionListItemQueryParams } from '../../../../domain/questionList/types/types.ts' +import { Empty, Pagination } from 'antd' +import QuestionListView from '../../../../domain/questionList/components/QuestionListView.tsx' +import TrainingCampListHeader from './components/TrainingCampListHeader.tsx' + +const QuestionListPage: React.FC = () => { + /** + * 获取题单分类,并根据分类,将其转化为树形结构 + */ + const { questionLists } = useQuestionLists() + const treeData = convertQuestionListToTreeStruct(questionLists) + + /** + * 监听选中的题单 ID,可用来获取该题单对应的题单项 + */ + const [selectedQuestionListId, setSelectedQuestionListId] = useState< + number | undefined + >() + const handleQuestionListSelect = (questionListId: number | undefined) => { + setSelectedQuestionListId(questionListId) + } + + /** + * 题单项查询参数 + */ + const [queryParams, setQueryParams] = useState({ + page: 1, + pageSize: 10, + questionListId: 0, + }) + + // region 地址栏参数处理 + /** + * 用来动态获取查询参数 + */ + const [searchParams, setSearchParams] = useSearchParams() + const questionListId = searchParams.get('questionListId') || '' + + useEffect(() => { + if (questionListId) { + setSelectedQuestionListId(Number(questionListId)) + } + }, [questionListId]) + + /** + * 监听 selectedCategoryId 变化,更新地址栏参数 + */ + useEffect(() => { + // > 0 防止选中模拟的分类 + if (selectedQuestionListId && selectedQuestionListId > 0) { + setSearchParams({ questionListId: selectedQuestionListId.toString() }) + setQueryParams({ + ...queryParams, + questionListId: selectedQuestionListId, + }) + } else { + setSearchParams({}) + setQueryParams({ + ...queryParams, + questionListId: 0, + }) + } + }, [selectedQuestionListId, setSearchParams]) + // endregion + + const { questionListItems, pagination } = useQuestionListItem2(queryParams) + + return ( +
+
+ + + +
+ {/* 根据 selectedQuestionList 挂载不同的组件 */} +
+ + {selectedQuestionListId === QuestionListParentNode.TRAINING_CAMP && ( + + )} + {selectedQuestionListId === undefined && ( + + )} + {selectedQuestionListId !== undefined && + questionListItems.length > 0 && ( + <> + + + + )} + {selectedQuestionListId !== undefined && + questionListItems.length > 0 && ( +
+ { + setQueryParams({ + ...queryParams, + page, + pageSize, + }) + }} + current={queryParams.page} + pageSize={queryParams.pageSize} + /> +
+ )} +
+
+
+ ) +} + +export default QuestionListPage diff --git a/frontend/src/apps/user/pages/questionList/components/TrainingCampListHeader.tsx b/frontend/src/apps/user/pages/questionList/components/TrainingCampListHeader.tsx new file mode 100644 index 0000000..02910cb --- /dev/null +++ b/frontend/src/apps/user/pages/questionList/components/TrainingCampListHeader.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +const TrainingCampListHeader: React.FC = () => { + return ( +
+ 满足条件后访问 +
+ ) +} + +export default TrainingCampListHeader diff --git a/frontend/src/apps/user/pages/questionList/components/TrainingCampListInfo.tsx b/frontend/src/apps/user/pages/questionList/components/TrainingCampListInfo.tsx new file mode 100644 index 0000000..90b711d --- /dev/null +++ b/frontend/src/apps/user/pages/questionList/components/TrainingCampListInfo.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +/** + * 单纯用于展示信息的组件 + */ +const TrainingCampListInfo: React.FC = () => { + return ( +
+ 专属题单 +
+ ) +} + +export default TrainingCampListInfo diff --git a/frontend/src/apps/user/pages/questionSet/QuestionSetPage.tsx b/frontend/src/apps/user/pages/questionSet/QuestionSetPage.tsx new file mode 100644 index 0000000..ea4e031 --- /dev/null +++ b/frontend/src/apps/user/pages/questionSet/QuestionSetPage.tsx @@ -0,0 +1,72 @@ +import React, { useEffect, useState } from 'react' +import { Panel } from '../../../../base/components' +import { CategoryTreeView } from '../../../../domain/category' +import { + convertCategoryTreeToNode, + useCategory, +} from '../../../../domain/category' +import { useSearchParams } from 'react-router-dom' +import { QuestionTable } from '../../../../domain/question' + +const QuestionSetPage: React.FC = () => { + /** + * 获取分类信息树并转换为 TreeSelect 的数据结构 + */ + const { categoryTree } = useCategory() + const treeData = convertCategoryTreeToNode(categoryTree) + + /** + * 监听分类树的选中的分类 + */ + const [selectedCategoryId, setSelectedCategoryId] = useState() + const handleCategorySelect = (categoryId: number) => { + setSelectedCategoryId(categoryId) + } + + /** + * 获取地址栏查询参数 + */ + const [searchParams, setSearchParams] = useSearchParams() + const categoryId = searchParams.get('categoryId') || '' + + /** + * 初始化设置 selectedCategoryId + */ + useEffect(() => { + if (categoryId) { + setSelectedCategoryId(Number(categoryId)) + } + }, [categoryId]) + + /** + * 监听 selectedCategoryId 变化,更新地址栏参数 + */ + useEffect(() => { + if (selectedCategoryId) { + setSearchParams({ categoryId: selectedCategoryId.toString() }) + } else { + setSearchParams({}) + } + }, [selectedCategoryId, setSearchParams]) + + return ( +
+
+ + + +
+
+ + + +
+
+ ) +} + +export default QuestionSetPage diff --git a/frontend/src/apps/user/pages/userCenter/UserCenterPage.tsx b/frontend/src/apps/user/pages/userCenter/UserCenterPage.tsx new file mode 100644 index 0000000..c521642 --- /dev/null +++ b/frontend/src/apps/user/pages/userCenter/UserCenterPage.tsx @@ -0,0 +1,67 @@ +import React, { useEffect, useState } from 'react' +import { Panel } from '../../../../base/components' +import { + AppstoreOutlined, + ProfileOutlined, + SettingOutlined, +} from '@ant-design/icons' +import { Menu, MenuProps } from 'antd' +import { NavLink, Outlet, useLocation } from 'react-router-dom' +import { + USER_CENTER, + USER_COLLECT, + USER_INFO, + USER_NOTE, +} from '../../router/config.ts' + +const UserCenterPage: React.FC = () => { + type MenuItem = Required['items'][number] + + const items: MenuItem[] = [ + { + key: USER_INFO, + label: 个人信息, + icon: , + }, + { + key: USER_COLLECT, + label: 个人收藏, + icon: , + }, + { + key: USER_NOTE, + label: 个人笔记, + icon: , + }, + ] + /** + * 监听路由变化,设置菜单选中状态 + */ + const location = useLocation() + const [selectedKeys, setSelectedKeys] = useState([USER_INFO]) + + useEffect(() => { + if (location.pathname === USER_CENTER) { + setSelectedKeys([USER_INFO]) + } else { + setSelectedKeys([location.pathname]) + } + }, [location.pathname]) + + return ( +
+
+ + + +
+
+ + + +
+
+ ) +} + +export default UserCenterPage diff --git a/frontend/src/apps/user/pages/userCenter/collect/CollectionDetail.tsx b/frontend/src/apps/user/pages/userCenter/collect/CollectionDetail.tsx new file mode 100644 index 0000000..3998233 --- /dev/null +++ b/frontend/src/apps/user/pages/userCenter/collect/CollectionDetail.tsx @@ -0,0 +1,61 @@ +import React, { useState } from 'react' +import { NoteList, NoteQueryParams, useNotes } from '../../../../../domain/note' +import { Button } from 'antd' +import { ArrowLeftOutlined } from '@ant-design/icons' + +interface CollectionDetailProps { + selectedCollectionId?: number + toggleShowCollectionDetail: () => void +} + +const CollectionDetail: React.FC = ({ + selectedCollectionId, + toggleShowCollectionDetail, +}) => { + const [noteQueryParams, setNoteQueryParams] = useState({ + page: 1, + pageSize: 10, + collectionId: selectedCollectionId, + }) + + const handleQueryParams = (params: NoteQueryParams) => { + setNoteQueryParams((prev) => { + return { + ...prev, + ...params, + } + }) + } + + const { + noteList, + pagination, + setNoteLikeStatusHandle, + setNoteCollectStatusHandle, + } = useNotes(noteQueryParams) + + return ( +
+
+ +
+ +
+ ) +} + +export default CollectionDetail diff --git a/frontend/src/apps/user/pages/userCenter/collect/UserCollect.tsx b/frontend/src/apps/user/pages/userCenter/collect/UserCollect.tsx new file mode 100644 index 0000000..06aaba5 --- /dev/null +++ b/frontend/src/apps/user/pages/userCenter/collect/UserCollect.tsx @@ -0,0 +1,63 @@ +import React, { useEffect, useState } from 'react' +import { useCollection2 } from '../../../../../domain/collection' +import { CollectionQueryParams } from '../../../../../domain/collection/types/types.ts' +import { useUser } from '../../../../../domain/user/hooks/useUser.ts' +import CollectionList2 from '../../../../../domain/collection/components/CollectionList2.tsx' +import CollectionDetail from './CollectionDetail.tsx' + +// 收藏夹 +const UserCollect: React.FC = () => { + const [queryParams, setQueryParams] = useState({ + noteId: undefined, + creatorId: undefined, + }) + + const user = useUser() + + useEffect(() => { + setQueryParams({ + noteId: undefined, + creatorId: user?.userId, + }) + }, [user]) + + /** + * 用于控制当前显示的是具体的收藏夹内容还是收藏夹列表 + */ + const [showCollectionDetail, setShowCollectionDetail] = useState(false) + const toggleShowCollectionDetail = () => { + setShowCollectionDetail(!showCollectionDetail) + } + + /** + * 选中的收藏夹 ID + */ + const [selectedCollectionId, setSelectedCollectionId] = useState() + const handleSelectedCollectionId = (collectionId: number) => { + setSelectedCollectionId(collectionId) + } + + const { collectionVOList } = useCollection2(queryParams) + + return ( + <> + {showCollectionDetail ? ( + + ) : ( +
+
我的收藏夹
+ +
+ )} + + ) +} + +export default UserCollect diff --git a/frontend/src/apps/user/pages/userCenter/info/UserInfo.tsx b/frontend/src/apps/user/pages/userCenter/info/UserInfo.tsx new file mode 100644 index 0000000..3197984 --- /dev/null +++ b/frontend/src/apps/user/pages/userCenter/info/UserInfo.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { UserInfoForm } from '../../../../../domain/user' + +const UserInfo: React.FC = () => { + return ( +
+ +
+ ) +} + +export default UserInfo diff --git a/frontend/src/apps/user/pages/userCenter/note/UserNote.tsx b/frontend/src/apps/user/pages/userCenter/note/UserNote.tsx new file mode 100644 index 0000000..3835b44 --- /dev/null +++ b/frontend/src/apps/user/pages/userCenter/note/UserNote.tsx @@ -0,0 +1,43 @@ +import React, { useState } from 'react' +import { NoteList, NoteQueryParams, useNotes } from '../../../../../domain/note' +import { useUser } from '../../../../../domain/user/hooks/useUser.ts' + +const UserNote: React.FC = () => { + const user = useUser() + + const [searchParams, setSearchParams] = useState({ + page: 1, + pageSize: 10, + sort: 'create', + order: 'desc', + authorId: user.userId, + }) + + const setSearchParamsHandle = (params: NoteQueryParams) => { + setSearchParams((prev) => ({ ...prev, ...params })) + } + + const { + noteList, + pagination, + setNoteLikeStatusHandle, + setNoteCollectStatusHandle, + } = useNotes(searchParams) + + return ( +
+ +
+ ) +} + +export default UserNote diff --git a/frontend/src/apps/user/pages/userHome/UserHomePage.tsx b/frontend/src/apps/user/pages/userHome/UserHomePage.tsx new file mode 100644 index 0000000..71d9288 --- /dev/null +++ b/frontend/src/apps/user/pages/userHome/UserHomePage.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import { UserHomeProfile, useUser2 } from '../../../../domain/user' +import { useParams } from 'react-router-dom' +import { Panel } from '../../../../base/components' +import { Tabs } from 'antd' +import UserNoteList from './components/UserNoteList.tsx' +import UserCollectList from './components/UserCollectList.tsx' + +const UserHomePage: React.FC = () => { + const { userId } = useParams() + const { userVO } = useUser2(userId ?? '') + + // Tabs Items + const items = [ + { + key: '1', + label: `笔记`, + children: , + }, + { + key: '2', + label: `收藏`, + children: , + }, + ] + + return ( +
+ +
+ + + +
+
+ ) +} + +export default UserHomePage diff --git a/frontend/src/apps/user/pages/userHome/components/UserCollectList.tsx b/frontend/src/apps/user/pages/userHome/components/UserCollectList.tsx new file mode 100644 index 0000000..20a47a4 --- /dev/null +++ b/frontend/src/apps/user/pages/userHome/components/UserCollectList.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +interface UserCollectListProps { + userId?: string +} + +const UserCollectList: React.FC = ({ userId }) => { + return
{userId}
+} + +export default UserCollectList diff --git a/frontend/src/apps/user/pages/userHome/components/UserNoteList.tsx b/frontend/src/apps/user/pages/userHome/components/UserNoteList.tsx new file mode 100644 index 0000000..5b13be5 --- /dev/null +++ b/frontend/src/apps/user/pages/userHome/components/UserNoteList.tsx @@ -0,0 +1,51 @@ +import React, { useState } from 'react' +import { NoteList, NoteQueryParams, useNotes } from '../../../../../domain/note' +import { Empty, Skeleton } from 'antd' + +interface UserNoteListProps { + userId?: string +} + +const UserNoteList: React.FC = ({ userId }) => { + const [queryParams, setQueryParams] = useState({ + page: 1, + pageSize: 10, + sort: 'create', + order: 'desc', + authorId: userId, + }) + + const { + noteList, + pagination, + setNoteLikeStatusHandle, + setNoteCollectStatusHandle, + } = useNotes(queryParams) + + function handleQueryParams(params: NoteQueryParams) { + setQueryParams((prev) => ({ ...prev, ...params })) + } + + return ( +
+ {userId === undefined ? ( + + ) : ( + <> + + {noteList.length === 0 && } + + )} +
+ ) +} + +export default UserNoteList diff --git a/frontend/src/apps/user/router/config.ts b/frontend/src/apps/user/router/config.ts new file mode 100644 index 0000000..044605c --- /dev/null +++ b/frontend/src/apps/user/router/config.ts @@ -0,0 +1,33 @@ +/** + * 首页路径 + */ +export const HOME = '/' +export const HOME_PAGE = '/home' + +/** + * 题库路径 + */ +export const QUESTION_SET = '/question-set' + +/** + * 题单路径 + */ +export const QUESTION_LIST = '/question-list' + +/** + * 个人主页 + */ +export const USER_HOME = '/user' + +/** + * 个人中心 + */ +export const USER_CENTER = '/user-center' +export const USER_INFO = '/user-center/info' +export const USER_COLLECT = '/user-center/collect' +export const USER_NOTE = '/user-center/note' + +/** + * 题目路径 + */ +export const QUESTION = '/questions' diff --git a/frontend/src/apps/user/router/index.tsx b/frontend/src/apps/user/router/index.tsx new file mode 100644 index 0000000..193be91 --- /dev/null +++ b/frontend/src/apps/user/router/index.tsx @@ -0,0 +1,42 @@ +import { Route } from 'react-router-dom' +import UserApp from '../UserApp.tsx' +import { + HOME, + HOME_PAGE, + QUESTION, + QUESTION_LIST, + QUESTION_SET, + USER_CENTER, + USER_COLLECT, + USER_HOME, + USER_INFO, + USER_NOTE, +} from './config.ts' +import { NotFound } from '../../../base/components' +import HomePage from '../pages/home/HomePage.tsx' +import UserCenterPage from '../pages/userCenter/UserCenterPage.tsx' +import UserInfo from '../pages/userCenter/info/UserInfo.tsx' +import UserCollect from '../pages/userCenter/collect/UserCollect.tsx' +import UserNote from '../pages/userCenter/note/UserNote.tsx' +import QuestionSetPage from '../pages/questionSet/QuestionSetPage.tsx' +import QuestionPage from '../pages/question/QuestionPage.tsx' +import UserHomePage from '../pages/userHome/UserHomePage.tsx' +import QuestionListPage from '../pages/questionList/QuestionListPage.tsx' + +export const UserRouteConfig = ( + }> + } /> + } /> + }> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + } /> + } /> + +) diff --git a/frontend/src/base/components/cherryMarkdown/CherryMarkdown.css b/frontend/src/base/components/cherryMarkdown/CherryMarkdown.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/base/components/cherryMarkdown/CherryMarkdown.tsx b/frontend/src/base/components/cherryMarkdown/CherryMarkdown.tsx new file mode 100644 index 0000000..cee4300 --- /dev/null +++ b/frontend/src/base/components/cherryMarkdown/CherryMarkdown.tsx @@ -0,0 +1,78 @@ +import React, { useEffect } from 'react' +import Cherry from 'cherry-markdown' +import 'cherry-markdown/dist/cherry-markdown.css' +import './CherryMarkdown.css' +import { userService } from '../../../domain/user' + +interface CherryMarkdownProps { + value: string // 默认 value + setValue: (value: string) => void // 动态设置 value + onFileUpload?: ( + file: File, + callback: (url: string, params: any) => void, + ) => void // 上传图片回调 +} + +const CherryMarkdown: React.FC = ({ setValue, value }) => { + /** + * 编辑器内容改变处理 + */ + function afterChange(text: string) { + setValue(text) + } + + const toolbar = [ + 'undo', + 'redo', + '|', + { + bold: ['bold', 'italic', 'underline', 'strikethrough'], + }, + '|', + 'header', + 'list', + { + insert: ['image'], + }, + 'fullScreen', + ] + + useEffect(() => { + const cherryInstance = new Cherry({ + id: 'cherry-markdown', + value: value, + editor: { + defaultModel: 'editOnly', + }, + toolbars: { + toolbar: toolbar, + bubble: false, + float: false, + }, + }) + + // 修改 cherryInstance 的 value + cherryInstance.on('afterChange', afterChange) + + cherryInstance.on( + 'fileUpload', + (file: File, callback: (url: string, params: any) => void) => { + const formData = new FormData() + formData.append('file', file) + userService.uploadImageService(formData).then((res) => { + callback(res.data.url, {}) + }) + }, + ) + + return () => { + cherryInstance.destroy() + } + }, []) + + return ( +
+ ) +} + +export default CherryMarkdown diff --git a/frontend/src/base/components/columnDivider/ColumnDivider.tsx b/frontend/src/base/components/columnDivider/ColumnDivider.tsx new file mode 100644 index 0000000..6c90b80 --- /dev/null +++ b/frontend/src/base/components/columnDivider/ColumnDivider.tsx @@ -0,0 +1,7 @@ +import React from 'react' + +const ColumnDivider: React.FC = () => { + return | +} + +export default ColumnDivider diff --git a/frontend/src/base/components/errorFallback/ErrorFallback.tsx b/frontend/src/base/components/errorFallback/ErrorFallback.tsx new file mode 100644 index 0000000..4ce648e --- /dev/null +++ b/frontend/src/base/components/errorFallback/ErrorFallback.tsx @@ -0,0 +1,40 @@ +import React from 'react' +import { Button, Result } from 'antd' + +interface ErrorFallbackProps { + error: Error + resetErrorBoundary: () => void +} + +const ErrorFallback: React.FC = ({ + error, + resetErrorBoundary, +}) => { + return ( + { + window.location.href = '/' + }} + > + 返回首页 + , + , + ]} + > +
+

错误栈信息:

+
+          {error.stack}
+        
+
+
+ ) +} + +export default ErrorFallback diff --git a/frontend/src/base/components/heatMap/CalendarHeatmap.css b/frontend/src/base/components/heatMap/CalendarHeatmap.css new file mode 100644 index 0000000..80d265d --- /dev/null +++ b/frontend/src/base/components/heatMap/CalendarHeatmap.css @@ -0,0 +1,73 @@ +/* + * react-calendar-heatmap styles + * + * All of the styles in this file are optional and configurable! + * The github and gitlab color scales are provided for reference. + */ + +.react-calendar-heatmap text { + font-size: 7px; + fill: rgba(100, 100, 100, 0.65); + font-weight: 500; +} + +.react-calendar-heatmap .react-calendar-heatmap-small-text { + font-size: 5px; +} + +.react-calendar-heatmap rect:hover { + stroke: #555; + stroke-width: 1px; +} + +/* + * Default color scale + */ + +.react-calendar-heatmap .color-empty { + fill: #eeeeee; +} + +.react-calendar-heatmap .color-filled { + fill: #8cc665; +} + +/* + * Github color scale + */ + +.react-calendar-heatmap .color-github-0 { + fill: #eeeeee; +} +.react-calendar-heatmap .color-github-1 { + fill: #d6e685; +} +.react-calendar-heatmap .color-github-2 { + fill: #8cc665; +} +.react-calendar-heatmap .color-github-3 { + fill: #44a340; +} +.react-calendar-heatmap .color-github-4 { + fill: #1e6823; +} + +/* + * Gitlab color scale + */ + +.react-calendar-heatmap .color-gitlab-0 { + fill: #ededed; +} +.react-calendar-heatmap .color-gitlab-1 { + fill: #acd5f2; +} +.react-calendar-heatmap .color-gitlab-2 { + fill: #7fa8d1; +} +.react-calendar-heatmap .color-gitlab-3 { + fill: #49729b; +} +.react-calendar-heatmap .color-gitlab-4 { + fill: #254e77; +} diff --git a/frontend/src/base/components/heatMap/CalendarHeatmap.tsx b/frontend/src/base/components/heatMap/CalendarHeatmap.tsx new file mode 100644 index 0000000..df3d8d1 --- /dev/null +++ b/frontend/src/base/components/heatMap/CalendarHeatmap.tsx @@ -0,0 +1,417 @@ +import React, { ReactElement, useMemo } from 'react' +import './CalendarHeatmap.css' +import { + DAYS_IN_WEEK, + MILLISECONDS_IN_ONE_DAY, + DAY_LABELS, + MONTH_LABELS, +} from './constants' +import { + dateNDaysAgo, + shiftDate, + getBeginningTimeForDate, + convertToDate, + getRange, +} from './utils' + +// 定义组件的 props 接口 +interface CalendarHeatmapProps { + values: Array<{ date: string | number | Date }> + numDays?: number + startDate?: string | number | Date + endDate?: string | number | Date + gutterSize?: number + horizontal?: boolean + showMonthLabels?: boolean + showWeekdayLabels?: boolean + showOutOfRangeDays?: boolean + tooltipDataAttrs?: object | ((value: any) => object) + titleForValue?: (value: any) => string + classForValue?: (value: any) => string + monthLabels?: string[] + weekdayLabels?: string[] + onClick?: (value: any) => void + onMouseOver?: (e: React.MouseEvent, value: any) => void + onMouseLeave?: (e: React.MouseEvent, value: any) => void + transformDayElement?: ( + element: ReactElement, + value: any, + index: number, + ) => ReactElement +} + +// 默认 props +const defaultProps: Partial = { + numDays: undefined, + startDate: dateNDaysAgo(200), + endDate: new Date(), + gutterSize: 1, + horizontal: true, + showMonthLabels: true, + showWeekdayLabels: false, + showOutOfRangeDays: false, + tooltipDataAttrs: undefined, + titleForValue: undefined, + classForValue: (value) => (value ? 'color-filled' : 'color-empty'), + monthLabels: MONTH_LABELS, + weekdayLabels: DAY_LABELS, + onClick: undefined, + onMouseOver: undefined, + onMouseLeave: undefined, + transformDayElement: undefined, +} + +const SQUARE_SIZE = 10 +const MONTH_LABEL_GUTTER_SIZE = 4 +const CSS_PSEDUO_NAMESPACE = 'react-calendar-heatmap-' + +const CalendarHeatmap: React.FC = (props) => { + const { + values, + numDays, + startDate, + endDate, + gutterSize, + horizontal, + showMonthLabels, + showWeekdayLabels, + showOutOfRangeDays, + tooltipDataAttrs, + titleForValue, + monthLabels, + weekdayLabels, + classForValue, + onClick, + onMouseOver, + onMouseLeave, + transformDayElement, + } = { ...defaultProps, ...props } as Required + + // 计算开始日期和结束日期 + const getStartDate = () => + shiftDate(getEndDate(), -getDateDifferenceInDays() + 1) + const getEndDate = () => getBeginningTimeForDate(convertToDate(endDate)) + + // 根据 numDays 或 startDate 计算日期的差值 + const getDateDifferenceInDays = () => { + if (numDays) { + console.warn( + 'numDays is a deprecated prop. It will be removed in the next release. Consider using the startDate prop instead.', + ) + return numDays + } + const timeDiff = getEndDate().getTime() - convertToDate(startDate).getTime() + return Math.ceil(timeDiff / MILLISECONDS_IN_ONE_DAY) + } + + // 获取开始日期前的空白天数 + const getNumEmptyDaysAtStart = () => getStartDate().getDay() + + // 获取带空天数的开始日期 + const getStartDateWithEmptyDays = () => + shiftDate(getStartDate(), -getNumEmptyDaysAtStart()) + + // 获取值的 tooltip 属性 + const getTooltipDataAttrsForValue = (value: any) => { + if (typeof tooltipDataAttrs === 'function') { + return tooltipDataAttrs(value) + } + return tooltipDataAttrs + } + + // 使用 useMemo 来缓存 valueCache + const valueCache = useMemo(() => { + if (!values) return [] + return values.reduce( + (memo, value) => { + const date = convertToDate(value.date) + const index = Math.floor( + (date.getTime() - getStartDateWithEmptyDays().getTime()) / + MILLISECONDS_IN_ONE_DAY, + ) + memo[index] = { + value, + className: classForValue(value), + title: titleForValue ? titleForValue(value) : null, + tooltipDataAttrs: getTooltipDataAttrsForValue(value), + } + return memo + }, + {} as Record, + ) + }, [ + values, + startDate, + endDate, + classForValue, + titleForValue, + tooltipDataAttrs, + ]) + + // 获取每个索引对应的值 + const getValueForIndex = (index: number) => valueCache[index]?.value ?? null + + // 获取每个索引对应的类名 + const getClassNameForIndex = (index: number) => + valueCache[index]?.className ?? classForValue(undefined) + + // 获取每个索引对应的标题 + const getTitleForIndex = (index: number) => + valueCache[index]?.title ?? (titleForValue ? titleForValue(null) : null) + + // 获取每个索引对应的 tooltip 属性 + const getTooltipDataAttrsForIndex = (index: number) => + valueCache[index]?.tooltipDataAttrs ?? + getTooltipDataAttrsForValue({ + date: null, + count: null, + }) + + // 获取周数 + const getWeekCount = () => { + const numDaysRoundedToWeek = + getDateDifferenceInDays() + + getNumEmptyDaysAtStart() + + getNumEmptyDaysAtEnd() + return Math.ceil(numDaysRoundedToWeek / DAYS_IN_WEEK) + } + + // 获取结束日期后的空白天数 + const getNumEmptyDaysAtEnd = () => DAYS_IN_WEEK - 1 - getEndDate().getDay() + + // 获取每周的 transform 属性 + const getTransformForWeek = (weekIndex: number) => { + if (horizontal) { + return `translate(${weekIndex * getSquareSizeWithGutter()}, 0)` + } + return `translate(0, ${weekIndex * getSquareSizeWithGutter()})` + } + + // 获取方块大小加上间隔 + const getSquareSizeWithGutter = () => SQUARE_SIZE + gutterSize + + // 获取 transform 属性以显示所有周 + const getTransformForAllWeeks = () => { + if (horizontal) { + return `translate(${getWeekdayLabelSize()}, ${getMonthLabelSize()})` + } + return `translate(0, ${getWeekdayLabelSize()})` + } + + // 获取 transform 属性以显示周标签 + const getTransformForWeekdayLabels = () => { + if (horizontal) { + return `translate(${SQUARE_SIZE}, ${getMonthLabelSize()})` + } + return undefined + } + + // 获取 transform 属性以显示月标签 + const getTransformForMonthLabels = () => { + if (horizontal) { + return `translate(${getWeekdayLabelSize()}, 0)` + } + return `translate(${getWeekWidth() + MONTH_LABEL_GUTTER_SIZE}, ${getWeekdayLabelSize()})` + } + + // 获取视图框的大小 + const getViewBox = () => { + if (horizontal) { + return `0 0 ${getWidth()} ${getHeight()}` + } + return `0 0 ${getHeight()} ${getWidth()}` + } + + // 获取周宽度 + const getWeekWidth = () => DAYS_IN_WEEK * getSquareSizeWithGutter() + + // 获取宽度 + const getWidth = () => + getWeekCount() * getSquareSizeWithGutter() - + (gutterSize - getWeekdayLabelSize()) + + // 获取高度 + const getHeight = () => + getWeekWidth() + (getMonthLabelSize() - gutterSize) + getWeekdayLabelSize() + + // 获取月份标签的大小 + const getMonthLabelSize = () => { + if (!showMonthLabels) { + return 0 + } + if (horizontal) { + return SQUARE_SIZE + MONTH_LABEL_GUTTER_SIZE + } + return 2 * (SQUARE_SIZE + MONTH_LABEL_GUTTER_SIZE) + } + + // 获取星期几标签的大小 + const getWeekdayLabelSize = () => { + if (!showWeekdayLabels) { + return 0 + } + if (horizontal) { + return 30 + } + return SQUARE_SIZE * 1.5 + } + + // 获取小方块的坐标 + const getSquareCoordinates = (dayIndex: number) => { + if (horizontal) { + return [0, dayIndex * getSquareSizeWithGutter()] + } + return [dayIndex * getSquareSizeWithGutter(), 0] + } + + // 渲染每个小方块 + const renderSquare = (dayIndex: number, index: number) => { + const indexOutOfRange = + index < getNumEmptyDaysAtStart() || + index >= getNumEmptyDaysAtStart() + getDateDifferenceInDays() + + if (indexOutOfRange && !showOutOfRangeDays) { + return null + } + + const value = getValueForIndex(index) + const [x, y] = getSquareCoordinates(dayIndex) + + function generateToolTipContent(date: Date, count: number = 0) { + if (!date) return '' + date = new Date(date) + const dateString = `${date.getMonth() + 1}月${date.getDate()}日` + const countString = count ? `提交 ${count} 次笔记` : '未提交笔记' + const rankString = `排名 ${value?.rank}` + return `${dateString} ${countString} ${rankString}` + } + + const rect = ( + onClick?.(value)} + onMouseOver={(e) => onMouseOver?.(e, value)} + onMouseLeave={(e) => onMouseLeave?.(e, value)} + {...getTooltipDataAttrsForIndex(index)} + > + {getTitleForIndex(index)} + + ) + return transformDayElement ? transformDayElement(rect, value, index) : rect + } + + // 渲染所有周 + const renderAllWeeks = () => { + return getRange(getWeekCount()).map((weekIndex) => renderWeek(weekIndex)) + } + + // 渲染某一周 + const renderWeek = (weekIndex: number) => { + return ( + + {getRange(DAYS_IN_WEEK).map((dayIndex) => + renderSquare(dayIndex, weekIndex * DAYS_IN_WEEK + dayIndex), + )} + + ) + } + + // 渲染月份标签 + const renderMonthLabels = () => { + if (!showMonthLabels) { + return null + } + const weekRange = getRange(getWeekCount() - 1) + return weekRange.map((weekIndex) => { + const endOfWeek = shiftDate( + getStartDateWithEmptyDays(), + (weekIndex + 1) * DAYS_IN_WEEK, + ) + const [x, y] = getMonthLabelCoordinates(weekIndex) + return endOfWeek.getDate() >= 1 && endOfWeek.getDate() <= DAYS_IN_WEEK ? ( + + {monthLabels[endOfWeek.getMonth()]} + + ) : null + }) + } + + // 获取月份标签的坐标 + const getMonthLabelCoordinates = (weekIndex: number) => { + if (horizontal) { + return [ + weekIndex * getSquareSizeWithGutter(), + getMonthLabelSize() - MONTH_LABEL_GUTTER_SIZE, + ] + } + const verticalOffset = -2 + return [0, (weekIndex + 1) * getSquareSizeWithGutter() + verticalOffset] + } + + // 渲染星期几标签 + const renderWeekdayLabels = () => { + if (!showWeekdayLabels) { + return null + } + return weekdayLabels!.map((weekdayLabel, dayIndex) => { + const [x, y] = getWeekdayLabelCoordinates(dayIndex) + const cssClasses = `$${horizontal ? '' : `${CSS_PSEDUO_NAMESPACE}small-text`} ${CSS_PSEDUO_NAMESPACE}weekday-label` + return dayIndex % 2 === 1 ? ( + + {weekdayLabel} + + ) : null + }) + } + + // 获取星期几标签的坐标 + const getWeekdayLabelCoordinates = (dayIndex: number) => { + if (horizontal) { + return [0, (dayIndex + 1) * SQUARE_SIZE + dayIndex * gutterSize] + } + return [dayIndex * SQUARE_SIZE + dayIndex * gutterSize, SQUARE_SIZE] + } + + // 渲染组件 + return ( + + + {renderMonthLabels()} + + + {renderAllWeeks()} + + + {renderWeekdayLabels()} + + + ) +} + +export default CalendarHeatmap diff --git a/frontend/src/base/components/heatMap/constants/index.ts b/frontend/src/base/components/heatMap/constants/index.ts new file mode 100644 index 0000000..9c5956f --- /dev/null +++ b/frontend/src/base/components/heatMap/constants/index.ts @@ -0,0 +1,20 @@ +export const MILLISECONDS_IN_ONE_DAY = 24 * 60 * 60 * 1000 + +export const DAYS_IN_WEEK = 7 + +export const MONTH_LABELS = [ + '一月', + '二月', + '三月', + '四月', + '五月', + '六月', + '七月', + '八月', + '九月', + '十月', + '十一月', + '十二月', +] + +export const DAY_LABELS = ['', '周一', '', '周三', '', '周五', ''] diff --git a/frontend/src/base/components/heatMap/utils/index.ts b/frontend/src/base/components/heatMap/utils/index.ts new file mode 100644 index 0000000..d3861bf --- /dev/null +++ b/frontend/src/base/components/heatMap/utils/index.ts @@ -0,0 +1,27 @@ +// returns a new date shifted a certain number of days (can be negative) +export function shiftDate(date: Date, numDays: number) { + const newDate = new Date(date) + newDate.setDate(newDate.getDate() + numDays) + return newDate +} + +export function getBeginningTimeForDate(date: Date) { + return new Date(date.getFullYear(), date.getMonth(), date.getDate()) +} + +// obj can be a parseable string, a millisecond timestamp, or a Date object +export function convertToDate(obj: Date | string | number | undefined) { + return obj instanceof Date ? obj : new Date(obj!) +} + +export function dateNDaysAgo(numDaysAgo: number) { + return shiftDate(new Date(), -numDaysAgo) +} + +export function getRange(count: number) { + const arr = [] + for (let idx = 0; idx < count; idx += 1) { + arr.push(idx) + } + return arr +} diff --git a/frontend/src/base/components/hostModal/HostModal.tsx b/frontend/src/base/components/hostModal/HostModal.tsx new file mode 100644 index 0000000..11f63bb --- /dev/null +++ b/frontend/src/base/components/hostModal/HostModal.tsx @@ -0,0 +1,49 @@ +import React, { useEffect, useState } from 'react' +import { FloatButton, Input, Modal } from 'antd' +import { kamanoteHost } from '../../constants' +import { Wifi } from '@icon-park/react' + +const HostModal: React.FC = () => { + const [host, setHost] = useState('') + const [open, setOpen] = useState(false) + + useEffect(() => { + setHost(localStorage.getItem(kamanoteHost) || '') + }, []) + + return ( + <> + { + setOpen(true) + }} + icon={} + > + Host + + setOpen(false)} + footer={null} + > +
+
+ 配置网络请求的 Host 地址,默认为:项目启动地址 +
+ { + setHost(e.target.value) + localStorage.setItem(kamanoteHost, e.target.value) + }} + /> +
+
+ + ) +} + +export default HostModal diff --git a/frontend/src/base/components/index.tsx b/frontend/src/base/components/index.tsx new file mode 100644 index 0000000..a5ede00 --- /dev/null +++ b/frontend/src/base/components/index.tsx @@ -0,0 +1,27 @@ +import NotFound from './notFound/NotFound.tsx' +import ErrorFallback from './errorFallback/ErrorFallback.tsx' +import HostModal from './hostModal/HostModal.tsx' +import Panel from './panel/Panel.tsx' +import React from 'react' +import TimeAgo from './timeAgo/TimeAgo.tsx' +import MarkdownRender from './markdownRender/MarkdownRender.tsx' +import ColumnDivider from './columnDivider/ColumnDivider.tsx' + +/** + * 懒加载 markdown 编辑器,避免其影响打包后的 index.js 体积过大 + */ +const MarkdownEditor = React.lazy( + () => import('./cherryMarkdown/CherryMarkdown.tsx'), +) + +export { MarkdownEditor } + +export { + NotFound, + ErrorFallback, + HostModal, + Panel, + TimeAgo, + MarkdownRender, + ColumnDivider, +} diff --git a/frontend/src/base/components/markdownRender/CopyButton.tsx b/frontend/src/base/components/markdownRender/CopyButton.tsx new file mode 100644 index 0000000..72c74fc --- /dev/null +++ b/frontend/src/base/components/markdownRender/CopyButton.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react' +import { FaCheck, FaRegCopy } from 'react-icons/fa' + +interface CopyButtonProps { + textToCopy: string +} + +const CopyButton: React.FC = ({ textToCopy }) => { + const [copied, setCopied] = useState(false) + + const handleCopy = () => { + navigator.clipboard.writeText(textToCopy).then(() => { + setCopied(true) + setTimeout(() => setCopied(false), 2000) + }) + } + + return !copied ? ( + + ) : ( + + ) +} + +export default CopyButton diff --git a/frontend/src/base/components/markdownRender/MarkdownRender.tsx b/frontend/src/base/components/markdownRender/MarkdownRender.tsx new file mode 100644 index 0000000..85cbd07 --- /dev/null +++ b/frontend/src/base/components/markdownRender/MarkdownRender.tsx @@ -0,0 +1,61 @@ +import React, { useEffect, useRef } from 'react' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' +import rehypeRaw from 'rehype-raw' +import CopyButton from './CopyButton.tsx' +import { Image } from 'antd' +import hljs from 'highlight.js' +import 'highlight.js/styles/github.css' + +interface MarkdownRenderProps { + markdown: string +} + +const MarkdownRender: React.FC = ({ markdown }) => { + const markdownBodyRef = useRef(null) + + useEffect(() => { + if (!markdownBodyRef.current) return + markdownBodyRef.current.querySelectorAll('pre code').forEach((block) => { + hljs.highlightElement(block as HTMLElement) + }) + }) + + return ( +
+ + + + {children} + +
+ ) : ( + + {children} + + ) + }, + img(props) { + const { src, alt } = props + return {alt} + }, + p: ({ children }) => ( +
{children}
+ ), + }} + > + {markdown} + + + ) +} + +export default MarkdownRender diff --git a/frontend/src/base/components/notFound/NotFound.tsx b/frontend/src/base/components/notFound/NotFound.tsx new file mode 100644 index 0000000..0251a49 --- /dev/null +++ b/frontend/src/base/components/notFound/NotFound.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import { Button, Result } from 'antd' +/** + * 404 Not Found 组件 + */ +const NotFound: React.FC = () => { + function goBack() { + window.history.back() + } + return ( + + 返回上一页 + + } + /> + ) +} + +export default NotFound diff --git a/frontend/src/base/components/panel/Panel.tsx b/frontend/src/base/components/panel/Panel.tsx new file mode 100644 index 0000000..a5ab400 --- /dev/null +++ b/frontend/src/base/components/panel/Panel.tsx @@ -0,0 +1,15 @@ +import React from 'react' + +interface PanelProps { + children?: React.ReactNode +} + +const Panel: React.FC = ({ children }) => { + return ( +
+ {children} +
+ ) +} + +export default Panel diff --git a/frontend/src/base/components/timeAgo/TimeAgo.tsx b/frontend/src/base/components/timeAgo/TimeAgo.tsx new file mode 100644 index 0000000..13a05bd --- /dev/null +++ b/frontend/src/base/components/timeAgo/TimeAgo.tsx @@ -0,0 +1,49 @@ +import React, { useEffect, useState } from 'react' + +interface TimeAgoProps { + datetime?: string // 传入的日期时间字符串 +} + +const TimeAgo: React.FC = ({ datetime }) => { + const [timeAgo, setTimeAgo] = useState('') + // 格式化时间差 + const formatTimeAgo = (date: Date) => { + const now = new Date() + const diff = now.getTime() - date.getTime() + + const seconds = Math.floor(diff / 1000) + const minutes = Math.floor(seconds / 60) + const hours = Math.floor(minutes / 60) + const days = Math.floor(hours / 24) + const months = Math.floor(days / 30) + const years = Math.floor(months / 12) + + if (seconds < 60) return '刚刚' + if (minutes < 60) return `${minutes} 分钟前` + if (hours < 24) return `${hours} 小时前` + if (days < 30) return `${days} 天前` + if (months < 12) return `${months} 个月前` + return `${years} 年前` + } + + useEffect(() => { + const date = new Date(datetime || '') + if (isNaN(date.getTime())) { + setTimeAgo('无效日期') + return + } + // 更新初始显示 + setTimeAgo(formatTimeAgo(date)) + + // 每分钟更新 + const interval = setInterval(() => { + setTimeAgo(formatTimeAgo(date)) + }, 60000) + + return () => clearInterval(interval) + }, [datetime]) + + return {timeAgo} +} + +export default TimeAgo diff --git a/frontend/src/base/constants/index.ts b/frontend/src/base/constants/index.ts new file mode 100644 index 0000000..f6f95f1 --- /dev/null +++ b/frontend/src/base/constants/index.ts @@ -0,0 +1,14 @@ +/** + * 存储用户 token 的 key + */ +export const kamanoteUserToken = 'kamanote:userToken' + +/** + * 存储请求的 host + */ +export const kamanoteHost = 'kamanote:host' + +/** + * 存储主题信息 + */ +export const kamanoteTheme = 'kamanote:theme' diff --git a/frontend/src/base/hooks/index.ts b/frontend/src/base/hooks/index.ts new file mode 100644 index 0000000..5101395 --- /dev/null +++ b/frontend/src/base/hooks/index.ts @@ -0,0 +1,3 @@ +import { useApp } from './useApp.ts' + +export { useApp } diff --git a/frontend/src/base/hooks/useApp.ts b/frontend/src/base/hooks/useApp.ts new file mode 100644 index 0000000..a5aac56 --- /dev/null +++ b/frontend/src/base/hooks/useApp.ts @@ -0,0 +1,12 @@ +import { useSelector } from 'react-redux' +import { RootState } from '../../store/store.ts' + +/** + * 获取全局的应用状态 + * 可用于下面操作: + * + * 1. 判断当前页面处于用户端还是管理端 + */ +export function useApp() { + return useSelector((state: RootState) => state.app) +} diff --git a/frontend/src/base/icon/BronzeTrophy.tsx b/frontend/src/base/icon/BronzeTrophy.tsx new file mode 100644 index 0000000..6cf432f --- /dev/null +++ b/frontend/src/base/icon/BronzeTrophy.tsx @@ -0,0 +1,84 @@ +function BronzeTrophy({ width = '24', height = '24' }) { + return ( + + + + + + + + + + + + + + + + + + ) +} + +export default BronzeTrophy diff --git a/frontend/src/base/icon/GoldTrophy.tsx b/frontend/src/base/icon/GoldTrophy.tsx new file mode 100644 index 0000000..d1c69ce --- /dev/null +++ b/frontend/src/base/icon/GoldTrophy.tsx @@ -0,0 +1,85 @@ +// 金牌-01 +function GoldTrophy({ width = '24', height = '24' }) { + return ( + + + + + + + + + + + + + + + + + + ) +} + +export default GoldTrophy diff --git a/frontend/src/base/icon/SliverTrophy.tsx b/frontend/src/base/icon/SliverTrophy.tsx new file mode 100644 index 0000000..9539cbf --- /dev/null +++ b/frontend/src/base/icon/SliverTrophy.tsx @@ -0,0 +1,85 @@ +// 银牌-01 +function SliverTrophy({ width = '24', height = '24' }) { + return ( + + + + + + + + + + + + + + + + + + ) +} + +export default SliverTrophy diff --git a/frontend/src/base/icon/index.ts b/frontend/src/base/icon/index.ts new file mode 100644 index 0000000..bd0b493 --- /dev/null +++ b/frontend/src/base/icon/index.ts @@ -0,0 +1,5 @@ +import BronzeTrophy from './BronzeTrophy.tsx' +import GoldTrophy from './GoldTrophy.tsx' +import SliverTrophy from './SliverTrophy.tsx' + +export { BronzeTrophy, GoldTrophy, SliverTrophy } diff --git a/frontend/src/base/regex/index.ts b/frontend/src/base/regex/index.ts new file mode 100644 index 0000000..95b7981 --- /dev/null +++ b/frontend/src/base/regex/index.ts @@ -0,0 +1,15 @@ +/** + * 数字、字母、下划线 + */ +export const ALPHANUMERIC_UNDERSCORE = /^[a-zA-Z0-9_]+$/ + +/** + * 数字、字母、下划线、中文 + */ +export const ALPHANUMERIC_UNDERSCORE_CHINESE = /^[a-zA-Z0-9_\u4e00-\u9fa5]+$/ + +/** + * 密码 + */ +export const PASSWORD_ALLOWABLE_CHARACTERS = + /^[a-zA-Z0-9!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?`~]+$/ diff --git a/frontend/src/base/styles/github-markdown-light.css b/frontend/src/base/styles/github-markdown-light.css new file mode 100644 index 0000000..ed37faf --- /dev/null +++ b/frontend/src/base/styles/github-markdown-light.css @@ -0,0 +1,1128 @@ +/* light */ +.markdown-body { + color-scheme: light; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + margin: 0; + color: #1f2328; + background-color: #ffffff; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', + Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; +} + +.markdown-body .octicon { + display: inline-block; + fill: currentColor; + vertical-align: text-bottom; +} + +.markdown-body h1:hover .anchor .octicon-link:before, +.markdown-body h2:hover .anchor .octicon-link:before, +.markdown-body h3:hover .anchor .octicon-link:before, +.markdown-body h4:hover .anchor .octicon-link:before, +.markdown-body h5:hover .anchor .octicon-link:before, +.markdown-body h6:hover .anchor .octicon-link:before { + width: 16px; + height: 16px; + content: ' '; + display: inline-block; + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,"); + mask-image: url("data:image/svg+xml,"); +} + +.markdown-body details, +.markdown-body figcaption, +.markdown-body figure { + display: block; +} + +.markdown-body summary { + display: list-item; +} + +.markdown-body [hidden] { + display: none !important; +} + +.markdown-body a { + background-color: transparent; + color: #0969da; + text-decoration: none; +} + +.markdown-body abbr[title] { + border-bottom: none; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +.markdown-body b, +.markdown-body strong { + font-weight: 600; +} + +.markdown-body dfn { + font-style: italic; +} + +.markdown-body h1 { + margin: 0.67em 0; + font-weight: 600; + padding-bottom: 0.3em; + font-size: 2em; + border-bottom: 1px solid #d1d9e0b3; +} + +.markdown-body mark { + background-color: #fff8c5; + color: #1f2328; +} + +.markdown-body small { + font-size: 90%; +} + +.markdown-body sub, +.markdown-body sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +.markdown-body sub { + bottom: -0.25em; +} + +.markdown-body sup { + top: -0.5em; +} + +.markdown-body img { + border-style: none; + max-width: 100%; + box-sizing: content-box; +} + +.markdown-body code, +.markdown-body kbd, +.markdown-body pre, +.markdown-body samp { + font-family: monospace; + font-size: 1em; +} + +.markdown-body figure { + margin: 1em 2.5rem; +} + +.markdown-body hr { + box-sizing: content-box; + overflow: hidden; + background: transparent; + border-bottom: 1px solid #d1d9e0b3; + height: 0.25em; + padding: 0; + margin: 1.5rem 0; + background-color: #d1d9e0; + border: 0; +} + +.markdown-body input { + font: inherit; + margin: 0; + overflow: visible; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.markdown-body [type='button'], +.markdown-body [type='reset'], +.markdown-body [type='submit'] { + -webkit-appearance: button; + appearance: button; +} + +.markdown-body [type='checkbox'], +.markdown-body [type='radio'] { + box-sizing: border-box; + padding: 0; +} + +.markdown-body [type='number']::-webkit-inner-spin-button, +.markdown-body [type='number']::-webkit-outer-spin-button { + height: auto; +} + +.markdown-body [type='search']::-webkit-search-cancel-button, +.markdown-body [type='search']::-webkit-search-decoration { + -webkit-appearance: none; + appearance: none; +} + +.markdown-body ::-webkit-input-placeholder { + color: inherit; + opacity: 0.54; +} + +.markdown-body ::-webkit-file-upload-button { + -webkit-appearance: button; + appearance: button; + font: inherit; +} + +.markdown-body a:hover { + text-decoration: underline; +} + +.markdown-body ::placeholder { + color: #59636e; + opacity: 1; +} + +.markdown-body hr::before { + display: table; + content: ''; +} + +.markdown-body hr::after { + display: table; + clear: both; + content: ''; +} + +.markdown-body table { + border-spacing: 0; + border-collapse: collapse; + display: block; + width: max-content; + max-width: 100%; + overflow: auto; + font-variant: tabular-nums; +} + +.markdown-body td, +.markdown-body th { + padding: 0; +} + +.markdown-body details summary { + cursor: pointer; +} + +.markdown-body a:focus, +.markdown-body [role='button']:focus, +.markdown-body input[type='radio']:focus, +.markdown-body input[type='checkbox']:focus { + outline: 2px solid #0969da; + outline-offset: -2px; + box-shadow: none; +} + +.markdown-body a:focus:not(:focus-visible), +.markdown-body [role='button']:focus:not(:focus-visible), +.markdown-body input[type='radio']:focus:not(:focus-visible), +.markdown-body input[type='checkbox']:focus:not(:focus-visible) { + outline: solid 1px transparent; +} + +.markdown-body a:focus-visible, +.markdown-body [role='button']:focus-visible, +.markdown-body input[type='radio']:focus-visible, +.markdown-body input[type='checkbox']:focus-visible { + outline: 2px solid #0969da; + outline-offset: -2px; + box-shadow: none; +} + +.markdown-body a:not([class]):focus, +.markdown-body a:not([class]):focus-visible, +.markdown-body input[type='radio']:focus, +.markdown-body input[type='radio']:focus-visible, +.markdown-body input[type='checkbox']:focus, +.markdown-body input[type='checkbox']:focus-visible { + outline-offset: 0; +} + +.markdown-body kbd { + display: inline-block; + padding: 0.25rem; + font: + 11px ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + line-height: 10px; + color: #1f2328; + vertical-align: middle; + background-color: #f6f8fa; + border: solid 1px #d1d9e0b3; + border-bottom-color: #d1d9e0b3; + border-radius: 6px; + box-shadow: inset 0 -1px 0 #d1d9e0b3; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin-top: 1.5rem; + margin-bottom: 1rem; + font-weight: 600; + line-height: 1.25; +} + +.markdown-body h2 { + font-weight: 600; + padding-bottom: 0.3em; + font-size: 1.5em; + border-bottom: 1px solid #d1d9e0b3; +} + +.markdown-body h3 { + font-weight: 600; + font-size: 1.25em; +} + +.markdown-body h4 { + font-weight: 600; + font-size: 1em; +} + +.markdown-body h5 { + font-weight: 600; + font-size: 0.875em; +} + +.markdown-body h6 { + font-weight: 600; + font-size: 0.85em; + color: #59636e; +} + +.markdown-body p { + margin-top: 0; + margin-bottom: 10px; +} + +.markdown-body blockquote { + margin: 0; + padding: 0 1em; + color: #59636e; + border-left: 0.25em solid #d1d9e0; +} + +.markdown-body ul, +.markdown-body ol { + margin-top: 0; + margin-bottom: 0; + padding-left: 2em; +} + +.markdown-body ol ol, +.markdown-body ul ol { + list-style-type: lower-roman; +} + +.markdown-body ul ul ol, +.markdown-body ul ol ol, +.markdown-body ol ul ol, +.markdown-body ol ol ol { + list-style-type: lower-alpha; +} + +.markdown-body dd { + margin-left: 0; +} + +.markdown-body tt, +.markdown-body code, +.markdown-body samp { + font-family: + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + font-size: 12px; +} + +.markdown-body pre { + margin-top: 0; + margin-bottom: 0; + font-family: + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + font-size: 12px; + word-wrap: normal; +} + +.markdown-body .octicon { + display: inline-block; + overflow: visible !important; + vertical-align: text-bottom; + fill: currentColor; +} + +.markdown-body input::-webkit-outer-spin-button, +.markdown-body input::-webkit-inner-spin-button { + margin: 0; + appearance: none; +} + +.markdown-body .mr-2 { + margin-right: 0.5rem !important; +} + +.markdown-body::before { + display: table; + content: ''; +} + +.markdown-body::after { + display: table; + clear: both; + content: ''; +} + +.markdown-body > *:first-child { + margin-top: 0 !important; +} + +.markdown-body > *:last-child { + margin-bottom: 0 !important; +} + +.markdown-body a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-body .absent { + color: #d1242f; +} + +.markdown-body .anchor { + float: left; + padding-right: 0.25rem; + margin-left: -20px; + line-height: 1; +} + +.markdown-body .anchor:focus { + outline: none; +} + +.markdown-body p, +.markdown-body blockquote, +.markdown-body ul, +.markdown-body ol, +.markdown-body dl, +.markdown-body table, +.markdown-body pre, +.markdown-body details { + margin-top: 0; + margin-bottom: 1rem; +} + +.markdown-body blockquote > :first-child { + margin-top: 0; +} + +.markdown-body blockquote > :last-child { + margin-bottom: 0; +} + +.markdown-body h1 .octicon-link, +.markdown-body h2 .octicon-link, +.markdown-body h3 .octicon-link, +.markdown-body h4 .octicon-link, +.markdown-body h5 .octicon-link, +.markdown-body h6 .octicon-link { + color: #1f2328; + vertical-align: middle; + visibility: hidden; +} + +.markdown-body h1:hover .anchor, +.markdown-body h2:hover .anchor, +.markdown-body h3:hover .anchor, +.markdown-body h4:hover .anchor, +.markdown-body h5:hover .anchor, +.markdown-body h6:hover .anchor { + text-decoration: none; +} + +.markdown-body h1:hover .anchor .octicon-link, +.markdown-body h2:hover .anchor .octicon-link, +.markdown-body h3:hover .anchor .octicon-link, +.markdown-body h4:hover .anchor .octicon-link, +.markdown-body h5:hover .anchor .octicon-link, +.markdown-body h6:hover .anchor .octicon-link { + visibility: visible; +} + +.markdown-body h1 tt, +.markdown-body h1 code, +.markdown-body h2 tt, +.markdown-body h2 code, +.markdown-body h3 tt, +.markdown-body h3 code, +.markdown-body h4 tt, +.markdown-body h4 code, +.markdown-body h5 tt, +.markdown-body h5 code, +.markdown-body h6 tt, +.markdown-body h6 code { + padding: 0 0.2em; + font-size: inherit; +} + +.markdown-body summary h1, +.markdown-body summary h2, +.markdown-body summary h3, +.markdown-body summary h4, +.markdown-body summary h5, +.markdown-body summary h6 { + display: inline-block; +} + +.markdown-body summary h1 .anchor, +.markdown-body summary h2 .anchor, +.markdown-body summary h3 .anchor, +.markdown-body summary h4 .anchor, +.markdown-body summary h5 .anchor, +.markdown-body summary h6 .anchor { + margin-left: -40px; +} + +.markdown-body summary h1, +.markdown-body summary h2 { + padding-bottom: 0; + border-bottom: 0; +} + +.markdown-body ul.no-list, +.markdown-body ol.no-list { + padding: 0; + list-style-type: none; +} + +.markdown-body ol[type='a s'] { + list-style-type: lower-alpha; +} + +.markdown-body ol[type='A s'] { + list-style-type: upper-alpha; +} + +.markdown-body ol[type='i s'] { + list-style-type: lower-roman; +} + +.markdown-body ol[type='I s'] { + list-style-type: upper-roman; +} + +.markdown-body ol[type='1'] { + list-style-type: decimal; +} + +.markdown-body div > ol:not([type]) { + list-style-type: decimal; +} + +.markdown-body ul ul, +.markdown-body ul ol, +.markdown-body ol ol, +.markdown-body ol ul { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body li > p { + margin-top: 1rem; +} + +.markdown-body li + li { + margin-top: 0.25em; +} + +.markdown-body dl { + padding: 0; +} + +.markdown-body dl dt { + padding: 0; + margin-top: 1rem; + font-size: 1em; + font-style: italic; + font-weight: 600; +} + +.markdown-body dl dd { + padding: 0 1rem; + margin-bottom: 1rem; +} + +.markdown-body table th { + font-weight: 600; +} + +.markdown-body table th, +.markdown-body table td { + padding: 6px 13px; + border: 1px solid #d1d9e0; +} + +.markdown-body table td > :last-child { + margin-bottom: 0; +} + +.markdown-body table tr { + background-color: #ffffff; + border-top: 1px solid #d1d9e0b3; +} + +.markdown-body table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.markdown-body table img { + background-color: transparent; +} + +.markdown-body img[align='right'] { + padding-left: 20px; +} + +.markdown-body img[align='left'] { + padding-right: 20px; +} + +.markdown-body .emoji { + max-width: none; + vertical-align: text-top; + background-color: transparent; +} + +.markdown-body span.frame { + display: block; + overflow: hidden; +} + +.markdown-body span.frame > span { + display: block; + float: left; + width: auto; + padding: 7px; + margin: 13px 0 0; + overflow: hidden; + border: 1px solid #d1d9e0; +} + +.markdown-body span.frame span img { + display: block; + float: left; +} + +.markdown-body span.frame span span { + display: block; + padding: 5px 0 0; + clear: both; + color: #1f2328; +} + +.markdown-body span.align-center { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body span.align-center > span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: center; +} + +.markdown-body span.align-center span img { + margin: 0 auto; + text-align: center; +} + +.markdown-body span.align-right { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body span.align-right > span { + display: block; + margin: 13px 0 0; + overflow: hidden; + text-align: right; +} + +.markdown-body span.align-right span img { + margin: 0; + text-align: right; +} + +.markdown-body span.float-left { + display: block; + float: left; + margin-right: 13px; + overflow: hidden; +} + +.markdown-body span.float-left span { + margin: 13px 0 0; +} + +.markdown-body span.float-right { + display: block; + float: right; + margin-left: 13px; + overflow: hidden; +} + +.markdown-body span.float-right > span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: right; +} + +.markdown-body code, +.markdown-body tt { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + white-space: break-spaces; + background-color: #818b981f; + border-radius: 6px; +} + +.markdown-body code br, +.markdown-body tt br { + display: none; +} + +.markdown-body del code { + text-decoration: inherit; +} + +.markdown-body samp { + font-size: 85%; +} + +.markdown-body pre code { + font-size: 100%; +} + +.markdown-body pre > code { + padding: 0; + margin: 0; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; +} + +.markdown-body .highlight { + margin-bottom: 1rem; +} + +.markdown-body .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.markdown-body .highlight pre, +.markdown-body pre { + padding: 1rem; + overflow: auto; + font-size: 85%; + line-height: 1.45; + color: #1f2328; + background-color: #f6f8fa; + border-radius: 6px; +} + +.markdown-body pre code, +.markdown-body pre tt { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} + +.markdown-body .csv-data td, +.markdown-body .csv-data th { + padding: 5px; + overflow: hidden; + font-size: 12px; + line-height: 1; + text-align: left; + white-space: nowrap; +} + +.markdown-body .csv-data .blob-num { + padding: 10px 0.5rem 9px; + text-align: right; + background: #ffffff; + border: 0; +} + +.markdown-body .csv-data tr { + border-top: 0; +} + +.markdown-body .csv-data th { + font-weight: 600; + background: #f6f8fa; + border-top: 0; +} + +.markdown-body [data-footnote-ref]::before { + content: '['; +} + +.markdown-body [data-footnote-ref]::after { + content: ']'; +} + +.markdown-body .footnotes { + font-size: 12px; + color: #59636e; + border-top: 1px solid #d1d9e0; +} + +.markdown-body .footnotes ol { + padding-left: 1rem; +} + +.markdown-body .footnotes ol ul { + display: inline-block; + padding-left: 1rem; + margin-top: 1rem; +} + +.markdown-body .footnotes li { + position: relative; +} + +.markdown-body .footnotes li:target::before { + position: absolute; + top: calc(0.5rem * -1); + right: calc(0.5rem * -1); + bottom: calc(0.5rem * -1); + left: calc(1.5rem * -1); + pointer-events: none; + content: ''; + border: 2px solid #0969da; + border-radius: 6px; +} + +.markdown-body .footnotes li:target { + color: #1f2328; +} + +.markdown-body .footnotes .data-footnote-backref g-emoji { + font-family: monospace; +} + +.markdown-body body:has(:modal) { + padding-right: var(--dialog-scrollgutter) !important; +} + +.markdown-body .pl-c { + color: #59636e; +} + +.markdown-body .pl-c1, +.markdown-body .pl-s .pl-v { + color: #0550ae; +} + +.markdown-body .pl-e, +.markdown-body .pl-en { + color: #6639ba; +} + +.markdown-body .pl-smi, +.markdown-body .pl-s .pl-s1 { + color: #1f2328; +} + +.markdown-body .pl-ent { + color: #0550ae; +} + +.markdown-body .pl-k { + color: #cf222e; +} + +.markdown-body .pl-s, +.markdown-body .pl-pds, +.markdown-body .pl-s .pl-pse .pl-s1, +.markdown-body .pl-sr, +.markdown-body .pl-sr .pl-cce, +.markdown-body .pl-sr .pl-sre, +.markdown-body .pl-sr .pl-sra { + color: #0a3069; +} + +.markdown-body .pl-v, +.markdown-body .pl-smw { + color: #953800; +} + +.markdown-body .pl-bu { + color: #82071e; +} + +.markdown-body .pl-ii { + color: #f6f8fa; + background-color: #82071e; +} + +.markdown-body .pl-c2 { + color: #f6f8fa; + background-color: #cf222e; +} + +.markdown-body .pl-sr .pl-cce { + font-weight: bold; + color: #116329; +} + +.markdown-body .pl-ml { + color: #3b2300; +} + +.markdown-body .pl-mh, +.markdown-body .pl-mh .pl-en, +.markdown-body .pl-ms { + font-weight: bold; + color: #0550ae; +} + +.markdown-body .pl-mi { + font-style: italic; + color: #1f2328; +} + +.markdown-body .pl-mb { + font-weight: bold; + color: #1f2328; +} + +.markdown-body .pl-md { + color: #82071e; + background-color: #ffebe9; +} + +.markdown-body .pl-mi1 { + color: #116329; + background-color: #dafbe1; +} + +.markdown-body .pl-mc { + color: #953800; + background-color: #ffd8b5; +} + +.markdown-body .pl-mi2 { + color: #d1d9e0; + background-color: #0550ae; +} + +.markdown-body .pl-mdr { + font-weight: bold; + color: #8250df; +} + +.markdown-body .pl-ba { + color: #59636e; +} + +.markdown-body .pl-sg { + color: #818b98; +} + +.markdown-body .pl-corl { + text-decoration: underline; + color: #0a3069; +} + +.markdown-body [role='button']:focus:not(:focus-visible), +.markdown-body [role='tabpanel'][tabindex='0']:focus:not(:focus-visible), +.markdown-body button:focus:not(:focus-visible), +.markdown-body summary:focus:not(:focus-visible), +.markdown-body a:focus:not(:focus-visible) { + outline: none; + box-shadow: none; +} + +.markdown-body [tabindex='0']:focus:not(:focus-visible), +.markdown-body details-dialog:focus:not(:focus-visible) { + outline: none; +} + +.markdown-body g-emoji { + display: inline-block; + min-width: 1ch; + font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + font-size: 1em; + font-style: normal !important; + font-weight: 400; + line-height: 1; + vertical-align: -0.075em; +} + +.markdown-body g-emoji img { + width: 1em; + height: 1em; +} + +.markdown-body .task-list-item { + list-style-type: none; +} + +.markdown-body .task-list-item label { + font-weight: 400; +} + +.markdown-body .task-list-item.enabled label { + cursor: pointer; +} + +.markdown-body .task-list-item + .task-list-item { + margin-top: 0.25rem; +} + +.markdown-body .task-list-item .handle { + display: none; +} + +.markdown-body .task-list-item-checkbox { + margin: 0 0.2em 0.25em -1.4em; + vertical-align: middle; +} + +.markdown-body ul:dir(rtl) .task-list-item-checkbox { + margin: 0 -1.6em 0.25em 0.2em; +} + +.markdown-body ol:dir(rtl) .task-list-item-checkbox { + margin: 0 -1.6em 0.25em 0.2em; +} + +.markdown-body .contains-task-list:hover .task-list-item-convert-container, +.markdown-body + .contains-task-list:focus-within + .task-list-item-convert-container { + display: block; + width: auto; + height: 24px; + overflow: visible; + clip: auto; +} + +.markdown-body ::-webkit-calendar-picker-indicator { + filter: invert(50%); +} + +.markdown-body .markdown-alert { + padding: 0.5rem 1rem; + margin-bottom: 1rem; + color: inherit; + border-left: 0.25em solid #d1d9e0; +} + +.markdown-body .markdown-alert > :first-child { + margin-top: 0; +} + +.markdown-body .markdown-alert > :last-child { + margin-bottom: 0; +} + +.markdown-body .markdown-alert .markdown-alert-title { + display: flex; + font-weight: 500; + align-items: center; + line-height: 1; +} + +.markdown-body .markdown-alert.markdown-alert-note { + border-left-color: #0969da; +} + +.markdown-body .markdown-alert.markdown-alert-note .markdown-alert-title { + color: #0969da; +} + +.markdown-body .markdown-alert.markdown-alert-important { + border-left-color: #8250df; +} + +.markdown-body .markdown-alert.markdown-alert-important .markdown-alert-title { + color: #8250df; +} + +.markdown-body .markdown-alert.markdown-alert-warning { + border-left-color: #9a6700; +} + +.markdown-body .markdown-alert.markdown-alert-warning .markdown-alert-title { + color: #9a6700; +} + +.markdown-body .markdown-alert.markdown-alert-tip { + border-left-color: #1a7f37; +} + +.markdown-body .markdown-alert.markdown-alert-tip .markdown-alert-title { + color: #1a7f37; +} + +.markdown-body .markdown-alert.markdown-alert-caution { + border-left-color: #cf222e; +} + +.markdown-body .markdown-alert.markdown-alert-caution .markdown-alert-title { + color: #d1242f; +} + +.markdown-body > *:first-child > .heading-element:first-child { + margin-top: 0 !important; +} + +.markdown-body .highlight pre:has(+ .zeroclipboard-container) { + min-height: 52px; +} diff --git a/frontend/src/base/styles/github-markdown.css b/frontend/src/base/styles/github-markdown.css new file mode 100644 index 0000000..f432bed --- /dev/null +++ b/frontend/src/base/styles/github-markdown.css @@ -0,0 +1,1263 @@ +.markdown-body { + --base-size-4: 0.25rem; + --base-size-8: 0.5rem; + --base-size-16: 1rem; + --base-size-24: 1.5rem; + --base-size-40: 2.5rem; + --base-text-weight-normal: 400; + --base-text-weight-medium: 500; + --base-text-weight-semibold: 600; + --fontStack-monospace: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, + Liberation Mono, monospace; + --fgColor-accent: Highlight; +} + +@media (prefers-color-scheme: dark) { + .markdown-body, + [data-theme='dark'] { + /* dark */ + color-scheme: dark; + --focus-outlineColor: #1f6feb; + --fgColor-default: #f0f6fc; + --fgColor-muted: #9198a1; + --fgColor-accent: #4493f8; + --fgColor-success: #3fb950; + --fgColor-attention: #d29922; + --fgColor-danger: #f85149; + --fgColor-done: #ab7df8; + --bgColor-default: #0d1117; + --bgColor-muted: #151b23; + --bgColor-neutral-muted: #656c7633; + --bgColor-attention-muted: #bb800926; + --borderColor-default: #3d444d; + --borderColor-muted: #3d444db3; + --borderColor-neutral-muted: #3d444db3; + --borderColor-accent-emphasis: #1f6feb; + --borderColor-success-emphasis: #238636; + --borderColor-attention-emphasis: #9e6a03; + --borderColor-danger-emphasis: #da3633; + --borderColor-done-emphasis: #8957e5; + --color-prettylights-syntax-comment: #9198a1; + --color-prettylights-syntax-constant: #79c0ff; + --color-prettylights-syntax-constant-other-reference-link: #a5d6ff; + --color-prettylights-syntax-entity: #d2a8ff; + --color-prettylights-syntax-storage-modifier-import: #f0f6fc; + --color-prettylights-syntax-entity-tag: #7ee787; + --color-prettylights-syntax-keyword: #ff7b72; + --color-prettylights-syntax-string: #a5d6ff; + --color-prettylights-syntax-variable: #ffa657; + --color-prettylights-syntax-brackethighlighter-unmatched: #f85149; + --color-prettylights-syntax-brackethighlighter-angle: #9198a1; + --color-prettylights-syntax-invalid-illegal-text: #f0f6fc; + --color-prettylights-syntax-invalid-illegal-bg: #8e1519; + --color-prettylights-syntax-carriage-return-text: #f0f6fc; + --color-prettylights-syntax-carriage-return-bg: #b62324; + --color-prettylights-syntax-string-regexp: #7ee787; + --color-prettylights-syntax-markup-list: #f2cc60; + --color-prettylights-syntax-markup-heading: #1f6feb; + --color-prettylights-syntax-markup-italic: #f0f6fc; + --color-prettylights-syntax-markup-bold: #f0f6fc; + --color-prettylights-syntax-markup-deleted-text: #ffdcd7; + --color-prettylights-syntax-markup-deleted-bg: #67060c; + --color-prettylights-syntax-markup-inserted-text: #aff5b4; + --color-prettylights-syntax-markup-inserted-bg: #033a16; + --color-prettylights-syntax-markup-changed-text: #ffdfb6; + --color-prettylights-syntax-markup-changed-bg: #5a1e02; + --color-prettylights-syntax-markup-ignored-text: #f0f6fc; + --color-prettylights-syntax-markup-ignored-bg: #1158c7; + --color-prettylights-syntax-meta-diff-range: #d2a8ff; + --color-prettylights-syntax-sublimelinter-gutter-mark: #3d444d; + } +} + +@media (prefers-color-scheme: light) { + .markdown-body, + [data-theme='light'] { + /* light */ + color-scheme: light; + --focus-outlineColor: #0969da; + --fgColor-default: #1f2328; + --fgColor-muted: #59636e; + --fgColor-accent: #0969da; + --fgColor-success: #1a7f37; + --fgColor-attention: #9a6700; + --fgColor-danger: #d1242f; + --fgColor-done: #8250df; + --bgColor-default: #ffffff; + --bgColor-muted: #f6f8fa; + --bgColor-neutral-muted: #818b981f; + --bgColor-attention-muted: #fff8c5; + --borderColor-default: #d1d9e0; + --borderColor-muted: #d1d9e0b3; + --borderColor-neutral-muted: #d1d9e0b3; + --borderColor-accent-emphasis: #0969da; + --borderColor-success-emphasis: #1a7f37; + --borderColor-attention-emphasis: #9a6700; + --borderColor-danger-emphasis: #cf222e; + --borderColor-done-emphasis: #8250df; + --color-prettylights-syntax-comment: #59636e; + --color-prettylights-syntax-constant: #0550ae; + --color-prettylights-syntax-constant-other-reference-link: #0a3069; + --color-prettylights-syntax-entity: #6639ba; + --color-prettylights-syntax-storage-modifier-import: #1f2328; + --color-prettylights-syntax-entity-tag: #0550ae; + --color-prettylights-syntax-keyword: #cf222e; + --color-prettylights-syntax-string: #0a3069; + --color-prettylights-syntax-variable: #953800; + --color-prettylights-syntax-brackethighlighter-unmatched: #82071e; + --color-prettylights-syntax-brackethighlighter-angle: #59636e; + --color-prettylights-syntax-invalid-illegal-text: #f6f8fa; + --color-prettylights-syntax-invalid-illegal-bg: #82071e; + --color-prettylights-syntax-carriage-return-text: #f6f8fa; + --color-prettylights-syntax-carriage-return-bg: #cf222e; + --color-prettylights-syntax-string-regexp: #116329; + --color-prettylights-syntax-markup-list: #3b2300; + --color-prettylights-syntax-markup-heading: #0550ae; + --color-prettylights-syntax-markup-italic: #1f2328; + --color-prettylights-syntax-markup-bold: #1f2328; + --color-prettylights-syntax-markup-deleted-text: #82071e; + --color-prettylights-syntax-markup-deleted-bg: #ffebe9; + --color-prettylights-syntax-markup-inserted-text: #116329; + --color-prettylights-syntax-markup-inserted-bg: #dafbe1; + --color-prettylights-syntax-markup-changed-text: #953800; + --color-prettylights-syntax-markup-changed-bg: #ffd8b5; + --color-prettylights-syntax-markup-ignored-text: #d1d9e0; + --color-prettylights-syntax-markup-ignored-bg: #0550ae; + --color-prettylights-syntax-meta-diff-range: #8250df; + --color-prettylights-syntax-sublimelinter-gutter-mark: #818b98; + } +} + +.markdown-body { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + margin: 0; + color: var(--fgColor-default); + background-color: var(--bgColor-default); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', + Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; +} + +.markdown-body .octicon { + display: inline-block; + fill: currentColor; + vertical-align: text-bottom; +} + +.markdown-body h1:hover .anchor .octicon-link:before, +.markdown-body h2:hover .anchor .octicon-link:before, +.markdown-body h3:hover .anchor .octicon-link:before, +.markdown-body h4:hover .anchor .octicon-link:before, +.markdown-body h5:hover .anchor .octicon-link:before, +.markdown-body h6:hover .anchor .octicon-link:before { + width: 16px; + height: 16px; + content: ' '; + display: inline-block; + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,"); + mask-image: url("data:image/svg+xml,"); +} + +.markdown-body details, +.markdown-body figcaption, +.markdown-body figure { + display: block; +} + +.markdown-body summary { + display: list-item; +} + +.markdown-body [hidden] { + display: none !important; +} + +.markdown-body a { + background-color: transparent; + color: var(--fgColor-accent); + text-decoration: none; +} + +.markdown-body abbr[title] { + border-bottom: none; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +.markdown-body b, +.markdown-body strong { + font-weight: var(--base-text-weight-semibold, 600); +} + +.markdown-body dfn { + font-style: italic; +} + +.markdown-body h1 { + margin: 0.67em 0; + font-weight: var(--base-text-weight-semibold, 600); + padding-bottom: 0.3em; + font-size: 2em; + border-bottom: 1px solid var(--borderColor-muted); +} + +.markdown-body mark { + background-color: var(--bgColor-attention-muted); + color: var(--fgColor-default); +} + +.markdown-body small { + font-size: 90%; +} + +.markdown-body sub, +.markdown-body sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +.markdown-body sub { + bottom: -0.25em; +} + +.markdown-body sup { + top: -0.5em; +} + +.markdown-body img { + border-style: none; + max-width: 100%; + box-sizing: content-box; +} + +.markdown-body code, +.markdown-body kbd, +.markdown-body pre, +.markdown-body samp { + font-family: monospace; + font-size: 1em; +} + +.markdown-body figure { + margin: 1em var(--base-size-40); +} + +.markdown-body hr { + box-sizing: content-box; + overflow: hidden; + background: transparent; + border-bottom: 1px solid var(--borderColor-muted); + height: 0.25em; + padding: 0; + margin: var(--base-size-24) 0; + background-color: var(--borderColor-default); + border: 0; +} + +.markdown-body input { + font: inherit; + margin: 0; + overflow: visible; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.markdown-body [type='button'], +.markdown-body [type='reset'], +.markdown-body [type='submit'] { + -webkit-appearance: button; + appearance: button; +} + +.markdown-body [type='checkbox'], +.markdown-body [type='radio'] { + box-sizing: border-box; + padding: 0; +} + +.markdown-body [type='number']::-webkit-inner-spin-button, +.markdown-body [type='number']::-webkit-outer-spin-button { + height: auto; +} + +.markdown-body [type='search']::-webkit-search-cancel-button, +.markdown-body [type='search']::-webkit-search-decoration { + -webkit-appearance: none; + appearance: none; +} + +.markdown-body ::-webkit-input-placeholder { + color: inherit; + opacity: 0.54; +} + +.markdown-body ::-webkit-file-upload-button { + -webkit-appearance: button; + appearance: button; + font: inherit; +} + +.markdown-body a:hover { + text-decoration: underline; +} + +.markdown-body ::placeholder { + color: var(--fgColor-muted); + opacity: 1; +} + +.markdown-body hr::before { + display: table; + content: ''; +} + +.markdown-body hr::after { + display: table; + clear: both; + content: ''; +} + +.markdown-body table { + border-spacing: 0; + border-collapse: collapse; + display: block; + width: max-content; + max-width: 100%; + overflow: auto; + font-variant: tabular-nums; +} + +.markdown-body td, +.markdown-body th { + padding: 0; +} + +.markdown-body details summary { + cursor: pointer; +} + +.markdown-body a:focus, +.markdown-body [role='button']:focus, +.markdown-body input[type='radio']:focus, +.markdown-body input[type='checkbox']:focus { + outline: 2px solid var(--focus-outlineColor); + outline-offset: -2px; + box-shadow: none; +} + +.markdown-body a:focus:not(:focus-visible), +.markdown-body [role='button']:focus:not(:focus-visible), +.markdown-body input[type='radio']:focus:not(:focus-visible), +.markdown-body input[type='checkbox']:focus:not(:focus-visible) { + outline: solid 1px transparent; +} + +.markdown-body a:focus-visible, +.markdown-body [role='button']:focus-visible, +.markdown-body input[type='radio']:focus-visible, +.markdown-body input[type='checkbox']:focus-visible { + outline: 2px solid var(--focus-outlineColor); + outline-offset: -2px; + box-shadow: none; +} + +.markdown-body a:not([class]):focus, +.markdown-body a:not([class]):focus-visible, +.markdown-body input[type='radio']:focus, +.markdown-body input[type='radio']:focus-visible, +.markdown-body input[type='checkbox']:focus, +.markdown-body input[type='checkbox']:focus-visible { + outline-offset: 0; +} + +.markdown-body kbd { + display: inline-block; + padding: var(--base-size-4); + font: 11px + var( + --fontStack-monospace, + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace + ); + line-height: 10px; + color: var(--fgColor-default); + vertical-align: middle; + background-color: var(--bgColor-muted); + border: solid 1px var(--borderColor-neutral-muted); + border-bottom-color: var(--borderColor-neutral-muted); + border-radius: 6px; + box-shadow: inset 0 -1px 0 var(--borderColor-neutral-muted); +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin-top: var(--base-size-24); + margin-bottom: var(--base-size-16); + font-weight: var(--base-text-weight-semibold, 600); + line-height: 1.25; +} + +.markdown-body h2 { + font-weight: var(--base-text-weight-semibold, 600); + padding-bottom: 0.3em; + font-size: 1.5em; + border-bottom: 1px solid var(--borderColor-muted); +} + +.markdown-body h3 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: 1.25em; +} + +.markdown-body h4 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: 1em; +} + +.markdown-body h5 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: 0.875em; +} + +.markdown-body h6 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: 0.85em; + color: var(--fgColor-muted); +} + +.markdown-body p { + margin-top: 0; + margin-bottom: 10px; +} + +.markdown-body blockquote { + margin: 0; + padding: 0 1em; + color: var(--fgColor-muted); + border-left: 0.25em solid var(--borderColor-default); +} + +.markdown-body ul, +.markdown-body ol { + margin-top: 0; + margin-bottom: 0; + padding-left: 2em; +} + +.markdown-body ol ol, +.markdown-body ul ol { + list-style-type: lower-roman; +} + +.markdown-body ul ul ol, +.markdown-body ul ol ol, +.markdown-body ol ul ol, +.markdown-body ol ol ol { + list-style-type: lower-alpha; +} + +.markdown-body dd { + margin-left: 0; +} + +.markdown-body tt, +.markdown-body code, +.markdown-body samp { + font-family: var( + --fontStack-monospace, + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace + ); + font-size: 12px; +} + +.markdown-body pre { + margin-top: 0; + margin-bottom: 0; + font-family: var( + --fontStack-monospace, + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace + ); + font-size: 12px; + word-wrap: normal; +} + +.markdown-body .octicon { + display: inline-block; + overflow: visible !important; + vertical-align: text-bottom; + fill: currentColor; +} + +.markdown-body input::-webkit-outer-spin-button, +.markdown-body input::-webkit-inner-spin-button { + margin: 0; + appearance: none; +} + +.markdown-body .mr-2 { + margin-right: var(--base-size-8, 8px) !important; +} + +.markdown-body::before { + display: table; + content: ''; +} + +.markdown-body::after { + display: table; + clear: both; + content: ''; +} + +.markdown-body > *:first-child { + margin-top: 0 !important; +} + +.markdown-body > *:last-child { + margin-bottom: 0 !important; +} + +.markdown-body a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-body .absent { + color: var(--fgColor-danger); +} + +.markdown-body .anchor { + float: left; + padding-right: var(--base-size-4); + margin-left: -20px; + line-height: 1; +} + +.markdown-body .anchor:focus { + outline: none; +} + +.markdown-body p, +.markdown-body blockquote, +.markdown-body ul, +.markdown-body ol, +.markdown-body dl, +.markdown-body table, +.markdown-body pre, +.markdown-body details { + margin-top: 0; + margin-bottom: var(--base-size-16); +} + +.markdown-body blockquote > :first-child { + margin-top: 0; +} + +.markdown-body blockquote > :last-child { + margin-bottom: 0; +} + +.markdown-body h1 .octicon-link, +.markdown-body h2 .octicon-link, +.markdown-body h3 .octicon-link, +.markdown-body h4 .octicon-link, +.markdown-body h5 .octicon-link, +.markdown-body h6 .octicon-link { + color: var(--fgColor-default); + vertical-align: middle; + visibility: hidden; +} + +.markdown-body h1:hover .anchor, +.markdown-body h2:hover .anchor, +.markdown-body h3:hover .anchor, +.markdown-body h4:hover .anchor, +.markdown-body h5:hover .anchor, +.markdown-body h6:hover .anchor { + text-decoration: none; +} + +.markdown-body h1:hover .anchor .octicon-link, +.markdown-body h2:hover .anchor .octicon-link, +.markdown-body h3:hover .anchor .octicon-link, +.markdown-body h4:hover .anchor .octicon-link, +.markdown-body h5:hover .anchor .octicon-link, +.markdown-body h6:hover .anchor .octicon-link { + visibility: visible; +} + +.markdown-body h1 tt, +.markdown-body h1 code, +.markdown-body h2 tt, +.markdown-body h2 code, +.markdown-body h3 tt, +.markdown-body h3 code, +.markdown-body h4 tt, +.markdown-body h4 code, +.markdown-body h5 tt, +.markdown-body h5 code, +.markdown-body h6 tt, +.markdown-body h6 code { + padding: 0 0.2em; + font-size: inherit; +} + +.markdown-body summary h1, +.markdown-body summary h2, +.markdown-body summary h3, +.markdown-body summary h4, +.markdown-body summary h5, +.markdown-body summary h6 { + display: inline-block; +} + +.markdown-body summary h1 .anchor, +.markdown-body summary h2 .anchor, +.markdown-body summary h3 .anchor, +.markdown-body summary h4 .anchor, +.markdown-body summary h5 .anchor, +.markdown-body summary h6 .anchor { + margin-left: -40px; +} + +.markdown-body summary h1, +.markdown-body summary h2 { + padding-bottom: 0; + border-bottom: 0; +} + +.markdown-body ul.no-list, +.markdown-body ol.no-list { + padding: 0; + list-style-type: none; +} + +.markdown-body ol[type='a s'] { + list-style-type: lower-alpha; +} + +.markdown-body ol[type='A s'] { + list-style-type: upper-alpha; +} + +.markdown-body ol[type='i s'] { + list-style-type: lower-roman; +} + +.markdown-body ol[type='I s'] { + list-style-type: upper-roman; +} + +.markdown-body ol[type='1'] { + list-style-type: decimal; +} + +.markdown-body div > ol:not([type]) { + list-style-type: decimal; +} + +.markdown-body ul ul, +.markdown-body ul ol, +.markdown-body ol ol, +.markdown-body ol ul { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body li > p { + margin-top: var(--base-size-16); +} + +.markdown-body li + li { + margin-top: 0.25em; +} + +.markdown-body dl { + padding: 0; +} + +.markdown-body dl dt { + padding: 0; + margin-top: var(--base-size-16); + font-size: 1em; + font-style: italic; + font-weight: var(--base-text-weight-semibold, 600); +} + +.markdown-body dl dd { + padding: 0 var(--base-size-16); + margin-bottom: var(--base-size-16); +} + +.markdown-body table th { + font-weight: var(--base-text-weight-semibold, 600); +} + +.markdown-body table th, +.markdown-body table td { + padding: 6px 13px; + border: 1px solid var(--borderColor-default); +} + +.markdown-body table td > :last-child { + margin-bottom: 0; +} + +.markdown-body table tr { + background-color: var(--bgColor-default); + border-top: 1px solid var(--borderColor-muted); +} + +.markdown-body table tr:nth-child(2n) { + background-color: var(--bgColor-muted); +} + +.markdown-body table img { + background-color: transparent; +} + +.markdown-body img[align='right'] { + padding-left: 20px; +} + +.markdown-body img[align='left'] { + padding-right: 20px; +} + +.markdown-body .emoji { + max-width: none; + vertical-align: text-top; + background-color: transparent; +} + +.markdown-body span.frame { + display: block; + overflow: hidden; +} + +.markdown-body span.frame > span { + display: block; + float: left; + width: auto; + padding: 7px; + margin: 13px 0 0; + overflow: hidden; + border: 1px solid var(--borderColor-default); +} + +.markdown-body span.frame span img { + display: block; + float: left; +} + +.markdown-body span.frame span span { + display: block; + padding: 5px 0 0; + clear: both; + color: var(--fgColor-default); +} + +.markdown-body span.align-center { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body span.align-center > span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: center; +} + +.markdown-body span.align-center span img { + margin: 0 auto; + text-align: center; +} + +.markdown-body span.align-right { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body span.align-right > span { + display: block; + margin: 13px 0 0; + overflow: hidden; + text-align: right; +} + +.markdown-body span.align-right span img { + margin: 0; + text-align: right; +} + +.markdown-body span.float-left { + display: block; + float: left; + margin-right: 13px; + overflow: hidden; +} + +.markdown-body span.float-left span { + margin: 13px 0 0; +} + +.markdown-body span.float-right { + display: block; + float: right; + margin-left: 13px; + overflow: hidden; +} + +.markdown-body span.float-right > span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: right; +} + +.markdown-body code, +.markdown-body tt { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + white-space: break-spaces; + background-color: var(--bgColor-neutral-muted); + border-radius: 6px; +} + +.markdown-body code br, +.markdown-body tt br { + display: none; +} + +.markdown-body del code { + text-decoration: inherit; +} + +.markdown-body samp { + font-size: 85%; +} + +.markdown-body pre code { + font-size: 100%; +} + +.markdown-body pre > code { + padding: 0; + margin: 0; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; +} + +.markdown-body .highlight { + margin-bottom: var(--base-size-16); +} + +.markdown-body .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.markdown-body .highlight pre, +.markdown-body pre { + padding: var(--base-size-16); + overflow: auto; + font-size: 85%; + line-height: 1.45; + color: var(--fgColor-default); + background-color: var(--bgColor-muted); + border-radius: 6px; +} + +.markdown-body pre code, +.markdown-body pre tt { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} + +.markdown-body .csv-data td, +.markdown-body .csv-data th { + padding: 5px; + overflow: hidden; + font-size: 12px; + line-height: 1; + text-align: left; + white-space: nowrap; +} + +.markdown-body .csv-data .blob-num { + padding: 10px var(--base-size-8) 9px; + text-align: right; + background: var(--bgColor-default); + border: 0; +} + +.markdown-body .csv-data tr { + border-top: 0; +} + +.markdown-body .csv-data th { + font-weight: var(--base-text-weight-semibold, 600); + background: var(--bgColor-muted); + border-top: 0; +} + +.markdown-body [data-footnote-ref]::before { + content: '['; +} + +.markdown-body [data-footnote-ref]::after { + content: ']'; +} + +.markdown-body .footnotes { + font-size: 12px; + color: var(--fgColor-muted); + border-top: 1px solid var(--borderColor-default); +} + +.markdown-body .footnotes ol { + padding-left: var(--base-size-16); +} + +.markdown-body .footnotes ol ul { + display: inline-block; + padding-left: var(--base-size-16); + margin-top: var(--base-size-16); +} + +.markdown-body .footnotes li { + position: relative; +} + +.markdown-body .footnotes li:target::before { + position: absolute; + top: calc(var(--base-size-8) * -1); + right: calc(var(--base-size-8) * -1); + bottom: calc(var(--base-size-8) * -1); + left: calc(var(--base-size-24) * -1); + pointer-events: none; + content: ''; + border: 2px solid var(--borderColor-accent-emphasis); + border-radius: 6px; +} + +.markdown-body .footnotes li:target { + color: var(--fgColor-default); +} + +.markdown-body .footnotes .data-footnote-backref g-emoji { + font-family: monospace; +} + +.markdown-body body:has(:modal) { + padding-right: var(--dialog-scrollgutter) !important; +} + +.markdown-body .pl-c { + color: var(--color-prettylights-syntax-comment); +} + +.markdown-body .pl-c1, +.markdown-body .pl-s .pl-v { + color: var(--color-prettylights-syntax-constant); +} + +.markdown-body .pl-e, +.markdown-body .pl-en { + color: var(--color-prettylights-syntax-entity); +} + +.markdown-body .pl-smi, +.markdown-body .pl-s .pl-s1 { + color: var(--color-prettylights-syntax-storage-modifier-import); +} + +.markdown-body .pl-ent { + color: var(--color-prettylights-syntax-entity-tag); +} + +.markdown-body .pl-k { + color: var(--color-prettylights-syntax-keyword); +} + +.markdown-body .pl-s, +.markdown-body .pl-pds, +.markdown-body .pl-s .pl-pse .pl-s1, +.markdown-body .pl-sr, +.markdown-body .pl-sr .pl-cce, +.markdown-body .pl-sr .pl-sre, +.markdown-body .pl-sr .pl-sra { + color: var(--color-prettylights-syntax-string); +} + +.markdown-body .pl-v, +.markdown-body .pl-smw { + color: var(--color-prettylights-syntax-variable); +} + +.markdown-body .pl-bu { + color: var(--color-prettylights-syntax-brackethighlighter-unmatched); +} + +.markdown-body .pl-ii { + color: var(--color-prettylights-syntax-invalid-illegal-text); + background-color: var(--color-prettylights-syntax-invalid-illegal-bg); +} + +.markdown-body .pl-c2 { + color: var(--color-prettylights-syntax-carriage-return-text); + background-color: var(--color-prettylights-syntax-carriage-return-bg); +} + +.markdown-body .pl-sr .pl-cce { + font-weight: bold; + color: var(--color-prettylights-syntax-string-regexp); +} + +.markdown-body .pl-ml { + color: var(--color-prettylights-syntax-markup-list); +} + +.markdown-body .pl-mh, +.markdown-body .pl-mh .pl-en, +.markdown-body .pl-ms { + font-weight: bold; + color: var(--color-prettylights-syntax-markup-heading); +} + +.markdown-body .pl-mi { + font-style: italic; + color: var(--color-prettylights-syntax-markup-italic); +} + +.markdown-body .pl-mb { + font-weight: bold; + color: var(--color-prettylights-syntax-markup-bold); +} + +.markdown-body .pl-md { + color: var(--color-prettylights-syntax-markup-deleted-text); + background-color: var(--color-prettylights-syntax-markup-deleted-bg); +} + +.markdown-body .pl-mi1 { + color: var(--color-prettylights-syntax-markup-inserted-text); + background-color: var(--color-prettylights-syntax-markup-inserted-bg); +} + +.markdown-body .pl-mc { + color: var(--color-prettylights-syntax-markup-changed-text); + background-color: var(--color-prettylights-syntax-markup-changed-bg); +} + +.markdown-body .pl-mi2 { + color: var(--color-prettylights-syntax-markup-ignored-text); + background-color: var(--color-prettylights-syntax-markup-ignored-bg); +} + +.markdown-body .pl-mdr { + font-weight: bold; + color: var(--color-prettylights-syntax-meta-diff-range); +} + +.markdown-body .pl-ba { + color: var(--color-prettylights-syntax-brackethighlighter-angle); +} + +.markdown-body .pl-sg { + color: var(--color-prettylights-syntax-sublimelinter-gutter-mark); +} + +.markdown-body .pl-corl { + text-decoration: underline; + color: var(--color-prettylights-syntax-constant-other-reference-link); +} + +.markdown-body [role='button']:focus:not(:focus-visible), +.markdown-body [role='tabpanel'][tabindex='0']:focus:not(:focus-visible), +.markdown-body button:focus:not(:focus-visible), +.markdown-body summary:focus:not(:focus-visible), +.markdown-body a:focus:not(:focus-visible) { + outline: none; + box-shadow: none; +} + +.markdown-body [tabindex='0']:focus:not(:focus-visible), +.markdown-body details-dialog:focus:not(:focus-visible) { + outline: none; +} + +.markdown-body g-emoji { + display: inline-block; + min-width: 1ch; + font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + font-size: 1em; + font-style: normal !important; + font-weight: var(--base-text-weight-normal, 400); + line-height: 1; + vertical-align: -0.075em; +} + +.markdown-body g-emoji img { + width: 1em; + height: 1em; +} + +.markdown-body .task-list-item { + list-style-type: none; +} + +.markdown-body .task-list-item label { + font-weight: var(--base-text-weight-normal, 400); +} + +.markdown-body .task-list-item.enabled label { + cursor: pointer; +} + +.markdown-body .task-list-item + .task-list-item { + margin-top: var(--base-size-4); +} + +.markdown-body .task-list-item .handle { + display: none; +} + +.markdown-body .task-list-item-checkbox { + margin: 0 0.2em 0.25em -1.4em; + vertical-align: middle; +} + +.markdown-body ul:dir(rtl) .task-list-item-checkbox { + margin: 0 -1.6em 0.25em 0.2em; +} + +.markdown-body ol:dir(rtl) .task-list-item-checkbox { + margin: 0 -1.6em 0.25em 0.2em; +} + +.markdown-body .contains-task-list:hover .task-list-item-convert-container, +.markdown-body + .contains-task-list:focus-within + .task-list-item-convert-container { + display: block; + width: auto; + height: 24px; + overflow: visible; + clip: auto; +} + +.markdown-body ::-webkit-calendar-picker-indicator { + filter: invert(50%); +} + +.markdown-body .markdown-alert { + padding: var(--base-size-8) var(--base-size-16); + margin-bottom: var(--base-size-16); + color: inherit; + border-left: 0.25em solid var(--borderColor-default); +} + +.markdown-body .markdown-alert > :first-child { + margin-top: 0; +} + +.markdown-body .markdown-alert > :last-child { + margin-bottom: 0; +} + +.markdown-body .markdown-alert .markdown-alert-title { + display: flex; + font-weight: var(--base-text-weight-medium, 500); + align-items: center; + line-height: 1; +} + +.markdown-body .markdown-alert.markdown-alert-note { + border-left-color: var(--borderColor-accent-emphasis); +} + +.markdown-body .markdown-alert.markdown-alert-note .markdown-alert-title { + color: var(--fgColor-accent); +} + +.markdown-body .markdown-alert.markdown-alert-important { + border-left-color: var(--borderColor-done-emphasis); +} + +.markdown-body .markdown-alert.markdown-alert-important .markdown-alert-title { + color: var(--fgColor-done); +} + +.markdown-body .markdown-alert.markdown-alert-warning { + border-left-color: var(--borderColor-attention-emphasis); +} + +.markdown-body .markdown-alert.markdown-alert-warning .markdown-alert-title { + color: var(--fgColor-attention); +} + +.markdown-body .markdown-alert.markdown-alert-tip { + border-left-color: var(--borderColor-success-emphasis); +} + +.markdown-body .markdown-alert.markdown-alert-tip .markdown-alert-title { + color: var(--fgColor-success); +} + +.markdown-body .markdown-alert.markdown-alert-caution { + border-left-color: var(--borderColor-danger-emphasis); +} + +.markdown-body .markdown-alert.markdown-alert-caution .markdown-alert-title { + color: var(--fgColor-danger); +} + +.markdown-body > *:first-child > .heading-element:first-child { + margin-top: 0 !important; +} + +.markdown-body .highlight pre:has(+ .zeroclipboard-container) { + min-height: 52px; +} diff --git a/frontend/src/base/types/index.ts b/frontend/src/base/types/index.ts new file mode 100644 index 0000000..dce8054 --- /dev/null +++ b/frontend/src/base/types/index.ts @@ -0,0 +1,8 @@ +/** 全局通用的类型定义 */ + +/** + * 排序字段规则 + * desc 降序 + * asc 升序 + */ +export type SortOrder = 'desc' | 'asc' diff --git a/frontend/src/base/utils/index.ts b/frontend/src/base/utils/index.ts new file mode 100644 index 0000000..c4c0734 --- /dev/null +++ b/frontend/src/base/utils/index.ts @@ -0,0 +1,27 @@ +type DiffResult = Partial + +/** + * 比较两个对象,返回修改后的字段及新值 + * @param original 原始对象 + * @param updated 修改后的对象 + * @returns 修改的字段及新值 + */ +export function diffObject>( + original: T, + updated: T, +): DiffResult { + const result: DiffResult = {} + + for (const key in updated) { + if (Object.prototype.hasOwnProperty.call(updated, key)) { + const originalValue = original[key] + const updatedValue = updated[key] + // 对比值,如果不相等(包括 undefined),将字段及新值保存 + if (originalValue !== updatedValue) { + result[key] = updatedValue + } + } + } + + return result +} diff --git a/frontend/src/domain/app/hooks/useTheme.ts b/frontend/src/domain/app/hooks/useTheme.ts new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/domain/app/index.ts b/frontend/src/domain/app/index.ts new file mode 100644 index 0000000..5578411 --- /dev/null +++ b/frontend/src/domain/app/index.ts @@ -0,0 +1,3 @@ +import type { Theme } from './types' + +export type { Theme } diff --git a/frontend/src/domain/app/types/index.ts b/frontend/src/domain/app/types/index.ts new file mode 100644 index 0000000..5f20d6b --- /dev/null +++ b/frontend/src/domain/app/types/index.ts @@ -0,0 +1 @@ +export type Theme = 'light' | 'dark' diff --git a/frontend/src/domain/category/api/categoryApi.ts b/frontend/src/domain/category/api/categoryApi.ts new file mode 100644 index 0000000..512c64a --- /dev/null +++ b/frontend/src/domain/category/api/categoryApi.ts @@ -0,0 +1,8 @@ +import { ApiList } from '../../../request' + +export const adminCategoryApiList: ApiList = { + categories: ['GET', '/api/admin/categories'], + createCategory: ['POST', '/api/admin/categories'], + updateCategory: ['PATCH', '/api/admin/categories/{categoryId}'], + deleteCategory: ['DELETE', '/api/admin/categories/{categoryId}'], +} diff --git a/frontend/src/domain/category/components/CategoryList.tsx b/frontend/src/domain/category/components/CategoryList.tsx new file mode 100644 index 0000000..719ac50 --- /dev/null +++ b/frontend/src/domain/category/components/CategoryList.tsx @@ -0,0 +1,160 @@ +import React, { useState } from 'react' +import { CategoryTree, OptMode } from '../types/types.ts' +import { Button, Popconfirm, Table, Tooltip } from 'antd' +import { TableColumnsType, Tag } from 'antd' +import { isParentCategory } from '../utils' +import { AddSubset, AddThree, DeleteOne, EditTwo } from '@icon-park/react' +import CategoryOptDrawer from './CategoryOptDrawer.tsx' +import { useCategory } from '../hooks/useCategory.ts' + +const CategoryList: React.FC = () => { + /** + * 选中的 Category + */ + const [selectedCategory, setSelectedCategory] = useState() + + /** + * 抽屉是否打开 + */ + const [isDrawerOpen, setIsDrawerOpen] = useState(false) + const toggleIsDrawerOpen = () => { + setIsDrawerOpen(!isDrawerOpen) + } + + /** + * 抽屉打开的模式 + */ + const [mode, setMode] = useState('create') + + /** + * 获取分类及其附属操作函数 + */ + const { + categoryTree, + updateCategory, + createCategory, + deleteCategory, + loading, + } = useCategory() + + /** + * CategoryList 的表格列定义 + */ + const columns: TableColumnsType = [ + { + title: '分类 ID', + dataIndex: 'categoryId', + width: '5%', + key: 'categoryId', + }, + { + title: '分类名', + dataIndex: 'name', + width: '10%', + key: 'name', + }, + { + title: '分类类型', + dataIndex: 'parentCategoryId', + width: '15%', + key: 'parentCategoryId', + render: (_, category) => ( + <> + {isParentCategory(category) ? ( + 父分类 + ) : ( + 子分类 + )} + + ), + }, + { + title: '操作', + width: '10%', + render: (_, category) => { + return ( +
+ + { + setSelectedCategory(category) + setMode('update') + setIsDrawerOpen(true) + }} + /> + + { + await deleteCategory(category) + }} + > + + + + + {/* 只有一级分类(父分类)才能添加子分类 */} + {isParentCategory(category) && ( + + { + setMode('create') + setSelectedCategory(category) + setIsDrawerOpen(true) + }} + /> + + )} +
+ ) + }, + }, + ] + + return ( +
+
+ +
+ + + + ) +} + +export default CategoryList diff --git a/frontend/src/domain/category/components/CategoryOptDrawer.tsx b/frontend/src/domain/category/components/CategoryOptDrawer.tsx new file mode 100644 index 0000000..29b2a82 --- /dev/null +++ b/frontend/src/domain/category/components/CategoryOptDrawer.tsx @@ -0,0 +1,165 @@ +import React, { useEffect } from 'react' +import { CategoryTree, OptMode } from '../types/types.ts' +import { Button, Drawer, Form, Input, message } from 'antd' +import { useForm } from 'antd/es/form/Form' +import { CreateCategoryBody } from '../types/categoryService.ts' + +interface CategoryOptDrawerProps { + category: CategoryTree | undefined + isDrawerOpen: boolean + mode: OptMode + updateCategory: (category: CategoryTree) => void + createCategory: (category: CreateCategoryBody) => void + toggleIsDrawerOpen: () => void +} + +// 枚举可能的操作类型 +enum ModeEnum { + createParentCategory = 1, + createSubCategory = 2, + updateCategory = 3, +} + +const CategoryOptDrawer: React.FC = ({ + mode, + category, + updateCategory, + createCategory, + isDrawerOpen, + toggleIsDrawerOpen, +}) => { + const [form] = useForm() + + /** + * 根据 mode 和 category 枚举当前的操作模式 + * + * 如果 mode 为 'create' && category 为 undefined,则创建父分类 + * 如果 mode 为 'create' && category 不为 undefined,则创建子分类 + * 如果 mode 为 'update',则编辑分类 + * + * 其余情况向外抛出错误 + */ + let modeEnum: ModeEnum + + if (mode === 'create' && category === undefined) { + modeEnum = ModeEnum.createParentCategory + } else if (mode === 'create' && category !== undefined) { + modeEnum = ModeEnum.createSubCategory + } else if (mode === 'update' && category !== undefined) { + modeEnum = ModeEnum.updateCategory + } else { + console.log('mode 和 category 错误') + throw new Error('mode 和 category 错误') + } + + function onFinish(values: any) { + if (modeEnum === ModeEnum.createParentCategory) { + createCategory({ + name: values.name, + parentCategoryId: 0, + }) + message.success('创建分类成功') + } else if (modeEnum === ModeEnum.createSubCategory) { + createCategory({ + name: values.name, + parentCategoryId: category!.categoryId, + }) + message.success('创建分类成功') + } else if (modeEnum === ModeEnum.updateCategory) { + if (category === undefined) return + updateCategory({ + ...category, + name: values.name, + }) + message.success('更新分类成功') + } else { + console.log('error') + throw new Error('error') + } + toggleIsDrawerOpen() + } + + useEffect(() => { + if (modeEnum === ModeEnum.createParentCategory) { + // 创建父分类,不需要设置初始值 + return + } else if (modeEnum === ModeEnum.createSubCategory) { + // 创建子分类,需要设置父分类信息 + form.setFieldsValue({ + parentCategoryName: category?.name, + }) + } else if (mode === 'update' && category !== undefined) { + form.setFieldsValue({ + categoryId: category.categoryId, + name: category.name, + }) + } else { + console.log('error') + } + return () => { + // 清空表单 + form.resetFields() + } + }) + + return ( +
+ +
+ {modeEnum === ModeEnum.createSubCategory && ( + + + + )} + {modeEnum === ModeEnum.updateCategory && ( + + + + )} + + + + + + + +
+
+ ) +} + +export default CategoryOptDrawer diff --git a/frontend/src/domain/category/components/CategoryTreeView.tsx b/frontend/src/domain/category/components/CategoryTreeView.tsx new file mode 100644 index 0000000..1b37180 --- /dev/null +++ b/frontend/src/domain/category/components/CategoryTreeView.tsx @@ -0,0 +1,85 @@ +import React, { useCallback, useEffect, useState } from 'react' +import { Tree } from 'antd' +import { CategoryTreeNode } from '../types/types.ts' +import { CaretDownOutlined } from '@ant-design/icons' + +/** + * 提供分类树组件 + */ +interface CategoryTreeProps { + treeData: CategoryTreeNode[] // 树形结构 + selectedCategoryId: number | undefined // 默认选中的 categoryId + setSelectedCategoryId: (categoryId: number) => void // 设置选中的 categoryId +} + +const CategoryTreeView: React.FC = ({ + treeData, + selectedCategoryId, + setSelectedCategoryId, +}) => { + const [selectedKeys, setSelectedKeys] = useState() + const [expandedKeys, setExpandedKeys] = useState() + + /** + * 选中节点 + */ + function onSelectHandle(keys: React.Key[]) { + const key = keys[0] + setSelectedKeys([key]) + /** + * 设置选中模型 + */ + setSelectedCategoryId(Number(key)) + } + + /** + * 展开节点或者节点的父节点 + */ + const openExpandedKey = useCallback( + (key: React.Key) => { + if (treeData.find((item) => item.key === Number(key))) { + // 属于父分类 + setExpandedKeys([key]) + } else { + // 属于子分类的 key + treeData.forEach((parent) => { + if (parent.children) { + parent.children.forEach((child) => { + if (child.key === Number(key)) { + setExpandedKeys([parent.key]) + } + }) + } + }) + } + }, + [treeData], + ) + + function onExpandHandle(keys: React.Key[]) { + setExpandedKeys(keys) + } + /** + * 当 selectedCategoryId 发生变化时,自动设置 + */ + useEffect(() => { + if (selectedCategoryId) { + setSelectedKeys([selectedCategoryId]) + openExpandedKey(selectedCategoryId) + } + }, [selectedCategoryId, openExpandedKey]) + + return ( + } + onExpand={onExpandHandle} + onSelect={onSelectHandle} + selectedKeys={selectedKeys} + expandedKeys={expandedKeys} + blockNode={true} + > + ) +} + +export default CategoryTreeView diff --git a/frontend/src/domain/category/hooks/useCategory.ts b/frontend/src/domain/category/hooks/useCategory.ts new file mode 100644 index 0000000..a63bbbd --- /dev/null +++ b/frontend/src/domain/category/hooks/useCategory.ts @@ -0,0 +1,167 @@ +import { useEffect, useState } from 'react' +import { CategoryTree } from '../types/types.ts' +import { adminCategoryService } from '../service/categoryService.ts' +import { isParentCategory } from '../utils' +import { CreateCategoryBody } from '../types/categoryService.ts' + +export function useCategory() { + const [loading, setLoading] = useState(false) + const [categoryTree, setCategoryTree] = useState([]) + + useEffect(() => { + async function fetchData() { + setLoading(true) + const resp = await adminCategoryService.categoriesService() + setCategoryTree(() => { + // 添加 key 属性 + return resp.data.map((item) => { + return { + ...item, + key: item.categoryId, + } + }) + }) + setLoading(false) + } + + fetchData().then() + }, []) + + /** + * 新增分类处理函数 + * + * 如果「新增分类」是父分类,在获取到服务器返回的「新增分类」的 categoryId 后 + * 直接将「新增分类」添加到 categoryTree 数组中 + * + * 如果「新增分类」是子分类,需要在 categoryTree 数组中查找「新增分类」的父分类 + * 然后再将「新增分类」添加到其父分类的 children 数组中 + */ + async function createCategory(newCategory: CreateCategoryBody) { + const resp = await adminCategoryService.createCategoryService({ + name: newCategory.name, + parentCategoryId: newCategory.parentCategoryId, + }) + const newCategoryId = resp.data.categoryId + if (isParentCategory(newCategory)) { + // 父分类的情况 + setCategoryTree((prev) => { + return [ + ...prev, + { + ...newCategory, + key: newCategoryId, + categoryId: newCategoryId, + children: [], + }, + ] + }) + } else { + // 子分类的情况 + setCategoryTree((prev) => { + return prev.map((item) => { + if (item.categoryId === newCategory.parentCategoryId) { + if (item.children) { + item.children.push({ + ...newCategory, + key: newCategoryId, + categoryId: newCategoryId, + }) + } else { + item.children = [ + { + ...newCategory, + key: newCategoryId, + categoryId: newCategoryId, + }, + ] + } + } + return item + }) + }) + } + } + + /** + * 删除分类处理函数 + * + * 如果「被删分类」是父分类,直接从本地状态中删除对应的分类 + * + * 如果「被删分类」是子分类,先找到其父分类, + * 然后将其父分类的 children 数组中删除对应的分类 + */ + async function deleteCategory(category: CategoryTree) { + // 调用删除接口 + await adminCategoryService.deleteCategoryService(category.categoryId) + + // 从本地状态中删除对应的分类 + if (isParentCategory(category)) { + // 父分类的情况 + setCategoryTree((prev) => { + return prev.filter((item) => item.categoryId !== category.categoryId) + }) + } else { + setCategoryTree((prev) => { + return prev.map((item) => { + if (item.categoryId === category.parentCategoryId) { + if (item.children) { + item.children = item.children.filter( + (child) => child.categoryId !== category.categoryId, + ) + } + } + return item + }) + }) + } + } + + /** + * 更新分类处理函数 + * + * 如果「被更新分类」是父分类,直接将「被更新分类」替换掉本地状态中对应的分类 + * + * 如果「被更新分类」是子分类,先找到其父分类, + * 然后将其父分类的 children 数组中对应的分类替换掉 + */ + async function updateCategory(updatedCategory: CategoryTree) { + // 调用更新分类的接口 + await adminCategoryService.updateCategoryService(updatedCategory) + + // 更新本地的分类 + if (isParentCategory(updatedCategory)) { + setCategoryTree((prev) => { + return prev.map((item) => { + if (item.categoryId === updatedCategory.categoryId) { + return updatedCategory + } + return item + }) + }) + } else { + setCategoryTree((prev) => { + return prev.map((item) => { + if (item.categoryId === updatedCategory.parentCategoryId) { + if (item.children) { + item.children = item.children.map((child) => { + if (child.categoryId === updatedCategory.categoryId) { + return updatedCategory + } + return child + }) + } + } + return item + }) + }) + } + } + + return { + loading, + categoryTree, + createCategory, + deleteCategory, + updateCategory, + } +} diff --git a/frontend/src/domain/category/index.ts b/frontend/src/domain/category/index.ts new file mode 100644 index 0000000..4f5b205 --- /dev/null +++ b/frontend/src/domain/category/index.ts @@ -0,0 +1,13 @@ +import { CategoryEntity, Category, CategoryTree } from './types/types.ts' +import CategoryList from './components/CategoryList.tsx' +import { useCategory } from './hooks/useCategory.ts' +import { convertCategoryTreeToNode } from './utils' +import CategoryTreeView from './components/CategoryTreeView.tsx' + +export type { CategoryEntity, Category, CategoryTree } + +export { CategoryList, CategoryTreeView } + +export { useCategory } + +export { convertCategoryTreeToNode } diff --git a/frontend/src/domain/category/service/categoryService.ts b/frontend/src/domain/category/service/categoryService.ts new file mode 100644 index 0000000..e7b109c --- /dev/null +++ b/frontend/src/domain/category/service/categoryService.ts @@ -0,0 +1,51 @@ +import { httpClient } from '../../../request' +import { Category, CategoryTree } from '../types/types.ts' +import { adminCategoryApiList } from '../api/categoryApi.ts' +import { + CreateCategoryBody, + CreateCategoryResponse, +} from '../types/categoryService.ts' + +export const adminCategoryService = { + /** + * 获取分类列表 + */ + categoriesService: () => { + return httpClient.request(adminCategoryApiList.categories, { + queryParams: {}, + }) + }, + + /** + * 新增分类 + */ + createCategoryService: (body: CreateCategoryBody) => { + return httpClient.request( + adminCategoryApiList.createCategory, + { + body: body, + }, + ) + }, + + /** + * 删除分类 + */ + deleteCategoryService: (categoryId: number) => { + return httpClient.request(adminCategoryApiList.deleteCategory, { + pathParams: [categoryId], + }) + }, + + /** + * 更新分类 + */ + updateCategoryService: (category: Category) => { + return httpClient.request(adminCategoryApiList.updateCategory, { + body: { + name: category.name, + }, + pathParams: [category.categoryId], + }) + }, +} diff --git a/frontend/src/domain/category/types/categoryService.ts b/frontend/src/domain/category/types/categoryService.ts new file mode 100644 index 0000000..5f1793e --- /dev/null +++ b/frontend/src/domain/category/types/categoryService.ts @@ -0,0 +1,11 @@ +/** + * 新增分类的请求体类型和返回值类型 + */ +export type CreateCategoryBody = { + parentCategoryId: number + name: string +} + +export type CreateCategoryResponse = { + categoryId: number +} diff --git a/frontend/src/domain/category/types/types.ts b/frontend/src/domain/category/types/types.ts new file mode 100644 index 0000000..69df8da --- /dev/null +++ b/frontend/src/domain/category/types/types.ts @@ -0,0 +1,39 @@ +/** + * 分类列表 + */ +export interface CategoryEntity { + categoryId: number + name: string + parentCategoryId: number + createdAt: string + updatedAt: string +} + +/** + * 除去创建时间、更新时间 + */ +export type Category = Omit + +/** + * 树形结构的 Category 列表 + */ +export type CategoryTree = Category & { + key: number + children?: CategoryTree[] +} + +/** + * Category 的抽屉操作模式 + * create: 新增 + * update: 修改 + */ +export type OptMode = 'create' | 'update' + +/** + * 分类树节点 + */ +export interface CategoryTreeNode { + title: string + key: number // 对应 categoryId + children?: CategoryTreeNode[] +} diff --git a/frontend/src/domain/category/utils/index.ts b/frontend/src/domain/category/utils/index.ts new file mode 100644 index 0000000..ec35cc6 --- /dev/null +++ b/frontend/src/domain/category/utils/index.ts @@ -0,0 +1,23 @@ +import { Category, CategoryTree, CategoryTreeNode } from '../types/types.ts' + +/** + * 判断是否是父分类 + */ +export function isParentCategory(category: Partial) { + return category.parentCategoryId === 0 +} + +/** + * 将 category 转为分类树 + */ +export function convertCategoryTreeToNode( + categoryTree: CategoryTree[], +): CategoryTreeNode[] { + return categoryTree.map((item) => { + return { + title: item.name, + key: item.categoryId, + children: item.children ? convertCategoryTreeToNode(item.children) : [], + } + }) +} diff --git a/frontend/src/domain/collection/api/collectionApiList.ts b/frontend/src/domain/collection/api/collectionApiList.ts new file mode 100644 index 0000000..66d2c9d --- /dev/null +++ b/frontend/src/domain/collection/api/collectionApiList.ts @@ -0,0 +1,8 @@ +import { ApiList } from '../../../request' + +export const collectionApiList: ApiList = { + getCollectionList: ['GET', '/api/collections'], + createCollection: ['POST', '/api/collections'], + deleteCollection: ['DELETE', '/api/collections/{collectionId}'], + batchUpdateCollection: ['POST', '/api/collections/batch'], +} diff --git a/frontend/src/domain/collection/components/CollectModalFooter.tsx b/frontend/src/domain/collection/components/CollectModalFooter.tsx new file mode 100644 index 0000000..b48cd64 --- /dev/null +++ b/frontend/src/domain/collection/components/CollectModalFooter.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import { Button } from 'antd' + +interface CollectModalFooterProps { + onCreate: () => void + onConfirm: () => void +} + +const CollectModalFooter: React.FC = ({ + onCreate, + onConfirm, +}) => { + return ( +
+
+ + 创建收藏夹 +
+ +
+ ) +} + +export default CollectModalFooter diff --git a/frontend/src/domain/collection/components/CollectionList.tsx b/frontend/src/domain/collection/components/CollectionList.tsx new file mode 100644 index 0000000..69db0db --- /dev/null +++ b/frontend/src/domain/collection/components/CollectionList.tsx @@ -0,0 +1,84 @@ +import React from 'react' +import { CollectionVO } from '../types/types.ts' + +interface CollectionListProps { + collectionVOList: CollectionVO[] // 收藏夹列表 + collectNote: ( + collectionId: number, + noteId: number, + collect: boolean, + ) => Promise // 收藏笔记 + setNoteCollectStatusHandle: (noteId: number, isCollected: boolean) => void +} + +// 在 Modal 中展示的收藏夹列表 +const CollectionList: React.FC = ({ + collectionVOList, + collectNote, + setNoteCollectStatusHandle, +}) => { + return ( +
+ {collectionVOList.map((collectionVO) => ( + + ))} +
+ ) +} + +export default CollectionList diff --git a/frontend/src/domain/collection/components/CollectionList2.tsx b/frontend/src/domain/collection/components/CollectionList2.tsx new file mode 100644 index 0000000..5598619 --- /dev/null +++ b/frontend/src/domain/collection/components/CollectionList2.tsx @@ -0,0 +1,86 @@ +import React from 'react' +import { CollectionVO } from '../types/types.ts' +import { Button, Dropdown, List, message } from 'antd' +import { + DeleteOutlined, + EditOutlined, + EllipsisOutlined, +} from '@ant-design/icons' +import type { MenuProps } from 'antd' + +interface CollectionList2Props { + collectionVOList: CollectionVO[] + handleSelectedCollectionId: (collectionId: number) => void + toggleShowCollectionDetail: () => void +} + +// 用户后台的收藏夹列表 +const CollectionList2: React.FC = ({ + collectionVOList, + handleSelectedCollectionId, + toggleShowCollectionDetail, +}) => { + return ( + { + // TODO: 实现删除和修改收藏夹的功能 + const items: MenuProps['items'] = [ + { + key: item.collectionId + 'ic2', + label:
编辑收藏夹
, + icon: , + onClick: () => { + message.info('todo...') + }, + }, + { + key: item.collectionId + 'ic', + label:
删除收藏夹
, + icon: , + onClick: () => { + message.info('todo...') + }, + }, + ] + /** + * 点击收藏夹事件处理 + * 需要将 Dropdown 的点击事件屏蔽 + */ + const clickHandle = (e: any) => { + if ( + !e.target.closest('.ant-dropdown-trigger') && + !e.target.closest('.ant-dropdown') + ) { + handleSelectedCollectionId(item.collectionId) + toggleShowCollectionDetail() + } + } + + return ( + +
+
+
+ {item.name} +
+ +
+
+ {item.description ?? '-'} +
+
+
+ ) + }} + /> + ) +} + +export default CollectionList2 diff --git a/frontend/src/domain/collection/components/CollectionModal.tsx b/frontend/src/domain/collection/components/CollectionModal.tsx new file mode 100644 index 0000000..2a64148 --- /dev/null +++ b/frontend/src/domain/collection/components/CollectionModal.tsx @@ -0,0 +1,72 @@ +import React, { useState } from 'react' +import { CollectionVO, CreateCollectionBody } from '../types/types.ts' +import { Empty, Modal } from 'antd' +import CollectModalFooter from './CollectModalFooter.tsx' +import CreateCollectionModal from './CreateCollectionModal.tsx' +import CollectionList from './CollectionList.tsx' + +interface CollectionModalProps { + isModalOpen: boolean // 控制弹窗显示状态 + toggleIsModalOpen: () => void // 切换弹窗显示状态 + collectionVOList: CollectionVO[] // 收藏夹列表 + selectedNoteId: number | undefined + createCollection: ( + body: CreateCollectionBody, + noteId?: number, // 笔记 ID,点击某个笔记弹窗时,该笔记的 ID + ) => Promise // 创建收藏夹处理函数 + collectNote: ( + collectionId: number, + noteId: number, + collect: boolean, + ) => Promise // 收藏笔记 + setNoteCollectStatusHandle: (noteId: number, isCollected: boolean) => void // 设置笔记的收藏状态 +} + +// 用来显示收藏夹列表的 Modal 组件 +const CollectionModal: React.FC = ({ + isModalOpen, + toggleIsModalOpen, + collectionVOList, + createCollection, + collectNote, + setNoteCollectStatusHandle, + selectedNoteId, +}) => { + const [isCreateModalOpen, setIsCreateModalOpen] = useState(false) + const toggleCreateModalOpen = () => { + setIsCreateModalOpen(!isCreateModalOpen) + } + + return ( + <> + + } + > +
选择收藏夹
+ {collectionVOList.length === 0 && } + {collectionVOList.length > 0 && ( + + )} +
+ + + ) +} + +export default CollectionModal diff --git a/frontend/src/domain/collection/components/CreateCollectionModal.tsx b/frontend/src/domain/collection/components/CreateCollectionModal.tsx new file mode 100644 index 0000000..5ee8882 --- /dev/null +++ b/frontend/src/domain/collection/components/CreateCollectionModal.tsx @@ -0,0 +1,74 @@ +import React, { useState } from 'react' +import { Button, Form, Input, Modal } from 'antd' +import TextArea from 'antd/es/input/TextArea' +import { CreateCollectionBody } from '../types/types.ts' + +interface CreateCollectionModalProps { + isModalOpen: boolean + toggleIsModalOpen: () => void + createCollection: ( + body: CreateCollectionBody, + noteId?: number, // 笔记 ID,点击某个笔记弹窗时,该笔记的 ID + ) => Promise // 创建收藏夹处理函数 + selectedNoteId?: number +} + +const CreateCollectionModal: React.FC = ({ + isModalOpen, + toggleIsModalOpen, + createCollection, + selectedNoteId, +}) => { + const [form] = Form.useForm() + + const [loading, setLoading] = useState(false) + + const onFinishHandle = async (values: CreateCollectionBody) => { + setLoading(true) + await createCollection(values, selectedNoteId) + setLoading(false) + form.resetFields() + toggleIsModalOpen() + } + + return ( + +
创建新的收藏夹
+
+ + + + + + + + + + + + + + + ) +} + +export default QuestionListOptDrawer diff --git a/frontend/src/domain/questionList/components/QuestionListTable.tsx b/frontend/src/domain/questionList/components/QuestionListTable.tsx new file mode 100644 index 0000000..8d452cd --- /dev/null +++ b/frontend/src/domain/questionList/components/QuestionListTable.tsx @@ -0,0 +1,176 @@ +import React, { useState } from 'react' +import { + Button, + message, + Popconfirm, + Table, + TableColumnsType, + Tag, + Tooltip, +} from 'antd' +import { QuestionListEntity, QuestionListOptType } from '../types/types.ts' +import { useQuestionLists } from '../hooks/useQuestionLists.ts' +import { AddThree, DeleteOne, EditTwo, ViewList } from '@icon-park/react' +import QuestionListOptDrawer from './QuestionListOptDrawer.tsx' +import { useNavigate } from 'react-router-dom' +import { QUESTION_LIST_MANAGE } from '../../../apps/admin/router/config.ts' + +const QuestionListTable: React.FC = () => { + const { + questionLists, + createQuestionListHandle, + updateQuestionListHandle, + deleteQuestionListHandle, + loading, + } = useQuestionLists() + + /** + * control drawer open status + */ + const [isDrawerOpen, setIsDrawerOpen] = useState(false) + const toggleIsDrawerOpen = () => { + setIsDrawerOpen(!isDrawerOpen) + } + + /** + * selected question list id + */ + const [selectedQuestionList, setSelectedQuestionList] = + useState() + + /** + * open drawer mode (create or update) + */ + const [mode, setMode] = useState('create') + + /** + * create question list button click handle + */ + const createButtonClickHandle = () => { + setMode('create') + setIsDrawerOpen(true) + } + + /** + * edit question list button click handle + */ + const editButtonClickHandle = (questionList: QuestionListEntity) => { + setMode('update') + setSelectedQuestionList(questionList) + setIsDrawerOpen(true) + } + + const navigate = useNavigate() + + /** + * table columns + */ + const columns: TableColumnsType = [ + { + title: '题单ID', + dataIndex: 'questionListId', + key: 'questionListId', + width: '20%', + }, + { + title: '题单', + dataIndex: 'name', + key: 'name', + width: '20%', + }, + { + title: '分类', + dataIndex: 'type', + key: 'type', + render: (type) => { + switch (type) { + case 1: + return 普通题单 + case 2: + return 专属题单 + } + }, + width: '20%', + }, + { + title: '操作', + key: 'opt', + render: (_, questionList) => { + return ( +
+ + editButtonClickHandle(questionList)} + /> + + { + await deleteQuestionListHandle(questionList.questionListId) + message.success('删除成功') + }} + > + + + + + + { + navigate( + `${QUESTION_LIST_MANAGE}/${questionList.questionListId}`, + ) + }} + /> + +
+ ) + }, + width: '40%', + }, + ] + + return ( +
+
+ +
+
+ +
+ ) +} + +export default QuestionListTable diff --git a/frontend/src/domain/questionList/components/QuestionListTreeView.tsx b/frontend/src/domain/questionList/components/QuestionListTreeView.tsx new file mode 100644 index 0000000..2bbc199 --- /dev/null +++ b/frontend/src/domain/questionList/components/QuestionListTreeView.tsx @@ -0,0 +1,73 @@ +import React, { useEffect, useState } from 'react' +import { Tree } from 'antd' +import { QuestionListCategory } from '../types/types.ts' + +interface QuestionListTreeViewProps { + treeData: QuestionListCategory[] + selectedQuestionListId: number | undefined + handleQuestionListSelect: (selectedQuestionListId: number | undefined) => void +} + +const QuestionListTreeView: React.FC = ({ + treeData, + handleQuestionListSelect, + selectedQuestionListId, +}) => { + const [selectedKeys, setSelectedKeys] = useState([ + selectedQuestionListId ?? '', + ]) + + const [expandedKeys, setExpandedKeys] = useState([]) + + useEffect(() => { + if (selectedQuestionListId) { + setSelectedKeys([selectedQuestionListId ?? '']) + const expandKeys = treeData.find((treeNode) => { + return treeNode.children?.find((childTreeNode) => { + if (childTreeNode.questionListId === selectedQuestionListId) { + return true + } + }) + }) + if (expandKeys) { + setExpandedKeys([expandKeys.key]) + } + } + }, [selectedQuestionListId, treeData]) + + /** + * 选中节点 + */ + function onSelectHandle(keys: React.Key[], { selectedNodes }: any) { + if ( + selectedNodes && + selectedNodes instanceof Array && + selectedNodes.length === 0 + ) + return + const key = keys[0] + const selectedNode = selectedNodes[0] as QuestionListCategory + setSelectedKeys([key]) + /** + * 设置选中题单 ID + */ + handleQuestionListSelect(selectedNode.key) + } + + function onExpandHandle(keys: React.Key[]) { + setExpandedKeys(keys) + } + + return ( + + ) +} + +export default QuestionListTreeView diff --git a/frontend/src/domain/questionList/components/QuestionListView.tsx b/frontend/src/domain/questionList/components/QuestionListView.tsx new file mode 100644 index 0000000..08015a2 --- /dev/null +++ b/frontend/src/domain/questionList/components/QuestionListView.tsx @@ -0,0 +1,45 @@ +import React from 'react' +import { QuestionListItemUserVO } from '../types/types.ts' +import { List } from 'antd' +import { BsCheck2Circle } from 'react-icons/bs' +import { NavLink } from 'react-router-dom' +import { QUESTION } from '../../../apps/user/router/config.ts' + +interface QuestionListViewProps { + questionList: QuestionListItemUserVO[] +} + +const QuestionListView: React.FC = ({ + questionList, +}) => { + return ( +
+ ( + +
+
+ {item.userQuestionStatus.finished ? ( + + ) : ( + '' + )} +
+
+ + {item.question.title} + +
+
+
+ )} + /> +
+ ) +} + +export default QuestionListView diff --git a/frontend/src/domain/questionList/hooks/useQuestionList.ts b/frontend/src/domain/questionList/hooks/useQuestionList.ts new file mode 100644 index 0000000..4b49a7b --- /dev/null +++ b/frontend/src/domain/questionList/hooks/useQuestionList.ts @@ -0,0 +1,32 @@ +import { useEffect, useState } from 'react' +import { QuestionListEntity } from '../types/types.ts' +import { adminQuestionListService } from '../service/questionListService.ts' + +// 获取题单 +export function useQuestionList2(questionListId: number) { + /** + * 获取题单 + */ + const [questionList, setQuestionList] = useState() + const [loading, setLoading] = useState(false) + + useEffect(() => { + async function fetchData() { + setLoading(true) + const { data } = + await adminQuestionListService.getQuestionListByIdService( + questionListId, + ) + setQuestionList(data) + } + + fetchData().then(() => { + setLoading(false) + }) + }, [questionListId]) + + return { + loading, + questionList, + } +} diff --git a/frontend/src/domain/questionList/hooks/useQuestionListItem.ts b/frontend/src/domain/questionList/hooks/useQuestionListItem.ts new file mode 100644 index 0000000..63b9712 --- /dev/null +++ b/frontend/src/domain/questionList/hooks/useQuestionListItem.ts @@ -0,0 +1,114 @@ +import { useEffect, useState } from 'react' +import { QuestionListItemVO } from '../types/types.ts' +import { adminQuestionListService } from '../service/questionListService.ts' +import { QuestionSummary } from '../../question' +import { message } from 'antd' + +export function useQuestionListItem(questionListId: number) { + /** + * 题单项列表 + */ + const [questionListItems, setQuestionListItems] = useState< + QuestionListItemVO[] + >([]) + + const [loading, setLoading] = useState(false) + + useEffect(() => { + async function fetchData() { + setLoading(true) + const { data } = + await adminQuestionListService.getQuestionListItemService( + questionListId, + ) + setQuestionListItems(data) + } + + fetchData().then(() => { + setLoading(false) + }) + }, [questionListId]) + + /** + * 添加题单项 + * @param questionListId 题单 ID + * @param question QuestionSummary + */ + async function createQuestionListItem( + questionListId: number, + question: QuestionSummary, + ) { + /** + * 检查是否加入了重复的题目 + */ + if ( + questionListItems.some( + (item) => item.question.questionId === question.questionId, + ) + ) { + message.warning('题目已存在') + return + } + const { data } = + await adminQuestionListService.createQuestionListItemService( + questionListId, + question.questionId, + ) + setQuestionListItems((prev) => { + const item = { + questionListId, + question, + rank: data.rank, + } + return [...prev, item] + }) + message.success('添加成功') + } + + /** + * 删除题单项 + */ + async function deleteQuestionListItem( + questionListId: number, + questionId: number, + ) { + await adminQuestionListService.deleteQuestionListItemService( + questionListId, + questionId, + ) + setQuestionListItems( + questionListItems.filter( + (item) => item.question.questionId !== questionId, + ), + ) + message.success('删除成功') + } + + /** + * 排序处理函数 + */ + async function sortListItemVO(listItemVO: QuestionListItemVO[]) { + if (listItemVO.length === 0) { + message.warning('listItem 长度为 0') + return + } + + setQuestionListItems(listItemVO) + + const questionListId = listItemVO[0].questionListId + const questionIds = listItemVO.map((item) => item.question.questionId) + + await adminQuestionListService.sortQuestionListItemService({ + questionListId, + questionIds, + }) + } + + return { + loading, + questionListItems, + createQuestionListItem, + deleteQuestionListItem, + sortListItemVO, + } +} diff --git a/frontend/src/domain/questionList/hooks/useQuestionListItem2.ts b/frontend/src/domain/questionList/hooks/useQuestionListItem2.ts new file mode 100644 index 0000000..883ed5d --- /dev/null +++ b/frontend/src/domain/questionList/hooks/useQuestionListItem2.ts @@ -0,0 +1,51 @@ +import { + QuestionListItemQueryParams, + QuestionListItemUserVO, +} from '../types/types.ts' +import { useEffect, useState } from 'react' +import { userQuestionListService } from '../service/questionListService.ts' +import { Pagination } from '../../../request' + +export function useQuestionListItem2(query: QuestionListItemQueryParams) { + /** + * 题单项目列表 + */ + const [questionListItems, setQuestionListItems] = useState< + QuestionListItemUserVO[] + >([]) + + /** + * 分页信息 + */ + const [pagination, setPagination] = useState() + + /** + * + */ + const [loading, setLoading] = useState(false) + + useEffect(() => { + if (query.questionListId === undefined || query.questionListId === 0) { + setQuestionListItems([]) + return + } + + async function fetchQuestionListItems() { + setLoading(true) + const res = + await userQuestionListService.getQuestionListByIdService(query) + setPagination(res.pagination) + setQuestionListItems(res.data) + } + + fetchQuestionListItems().then(() => { + setLoading(false) + }) + }, [query.page, query.pageSize, query.questionListId]) + + return { + loading, + pagination, + questionListItems, + } +} diff --git a/frontend/src/domain/questionList/hooks/useQuestionLists.ts b/frontend/src/domain/questionList/hooks/useQuestionLists.ts new file mode 100644 index 0000000..24904ce --- /dev/null +++ b/frontend/src/domain/questionList/hooks/useQuestionLists.ts @@ -0,0 +1,89 @@ +import { useEffect, useState } from 'react' +import { + CreateOrUpDateQuestionListBody, + QuestionListEntity, +} from '../types/types.ts' +import { adminQuestionListService } from '../service/questionListService.ts' + +export function useQuestionLists() { + /** + * 题单列表 + */ + const [questionLists, setQuestionLists] = useState() + const [loading, setLoading] = useState(false) + + useEffect(() => { + async function fetchData() { + setLoading(true) + const resp = await adminQuestionListService.getQuestionListService() + setQuestionLists(resp.data) + setLoading(false) + } + + fetchData().then() + }, []) + + /** + * 删除题单处理函数 + */ + async function deleteQuestionListHandle(questionListId: number) { + await adminQuestionListService.deleteQuestionListService(questionListId) + setQuestionLists( + questionLists?.filter((item) => item.questionListId !== questionListId), + ) + } + + /** + * 创建题单处理函数 + */ + async function createQuestionListHandle( + params: CreateOrUpDateQuestionListBody, + ) { + const resp = + await adminQuestionListService.createQuestionListService(params) + const questionListId = resp.data.questionListId + + setQuestionLists([ + ...(questionLists || []), + { + questionListId, + ...params, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }, + ]) + } + + /** + * 更新题单处理函数 + */ + async function updateQuestionListHandle( + questionListId: number, + params: CreateOrUpDateQuestionListBody, + ) { + await adminQuestionListService.updateQuestionListService( + questionListId, + params, + ) + setQuestionLists((prev) => { + return prev?.map((item) => { + if (item.questionListId === questionListId) { + return { + ...item, + ...params, + updatedAt: new Date().toISOString(), + } + } + return item + }) + }) + } + + return { + questionLists, + loading, + deleteQuestionListHandle, + createQuestionListHandle, + updateQuestionListHandle, + } +} diff --git a/frontend/src/domain/questionList/index.ts b/frontend/src/domain/questionList/index.ts new file mode 100644 index 0000000..75cee6b --- /dev/null +++ b/frontend/src/domain/questionList/index.ts @@ -0,0 +1,21 @@ +import { useQuestionListItem } from './hooks/useQuestionListItem.ts' +import { useQuestionList2 } from './hooks/useQuestionList.ts' +import type { QuestionListCategory, QuestionListType } from './types/types.ts' +import { QuestionListParentNode } from './types/types.ts' + +import { useQuestionLists } from './hooks/useQuestionLists.ts' +import QuestionListTreeView from './components/QuestionListTreeView.tsx' +import { convertQuestionListToTreeStruct } from './utils' +import { useQuestionListItem2 } from './hooks/useQuestionListItem2.ts' + +export { + useQuestionList2, + useQuestionListItem, + useQuestionLists, + useQuestionListItem2, +} +export { QuestionListTreeView } +export type { QuestionListCategory, QuestionListType } +export { QuestionListParentNode } + +export { convertQuestionListToTreeStruct } diff --git a/frontend/src/domain/questionList/service/questionListService.ts b/frontend/src/domain/questionList/service/questionListService.ts new file mode 100644 index 0000000..9641ff0 --- /dev/null +++ b/frontend/src/domain/questionList/service/questionListService.ts @@ -0,0 +1,135 @@ +import { httpClient } from '../../../request' +import { + adminQuestionListApi, + questionListApi, +} from '../api/questionListApi.ts' +import { + CreateOrUpDateQuestionListBody, + QuestionListEntity, + QuestionListItemQueryParams, + QuestionListItemUserVO, + QuestionListItemVO, + SortQuestionListItemBody, +} from '../types/types.ts' + +export const adminQuestionListService = { + /** + * 获取题单服务 + */ + getQuestionListByIdService: (questionListId: number) => { + return httpClient.request( + adminQuestionListApi.getQuestionList, + { + pathParams: [questionListId], + }, + ) + }, + + /** + * 获取题单列表服务 + */ + getQuestionListService: () => { + return httpClient.request( + adminQuestionListApi.getQuestionLists, + ) + }, + + /** + * 创建题单服务 + */ + createQuestionListService: (params: CreateOrUpDateQuestionListBody) => { + return httpClient.request<{ + questionListId: number + }>(adminQuestionListApi.createQuestionList, { + body: params, + }) + }, + + /** + * 删除题单服务 + */ + deleteQuestionListService: (questionListId: number) => { + return httpClient.request<{ + questionListId: number + }>(adminQuestionListApi.deleteQuestionList, { + pathParams: [questionListId], + }) + }, + + /** + * 更新题单信息服务 + */ + updateQuestionListService: ( + questionListId: number, + params: CreateOrUpDateQuestionListBody, + ) => { + return httpClient.request(adminQuestionListApi.updateQuestionList, { + pathParams: [questionListId], + body: params, + }) + }, + + /** + * 获取题单项列表服务 + */ + getQuestionListItemService: (questionListId: number) => { + return httpClient.request( + adminQuestionListApi.getQuestionListItems, + { + pathParams: [questionListId], + }, + ) + }, + + /** + * 创建题单项 + */ + createQuestionListItemService: ( + questionListId: number, + questionId: number, + ) => { + return httpClient.request<{ + rank: number + }>(adminQuestionListApi.createQuestionListItem, { + body: { + questionListId, + questionId, + }, + }) + }, + + /** + * 删除题单项 + */ + deleteQuestionListItemService: ( + questionListId: number, + questionId: number, + ) => { + return httpClient.request(adminQuestionListApi.deleteQuestionListItem, { + pathParams: [questionListId, questionId], + }) + }, + + /** + * 题单项排序 + */ + sortQuestionListItemService: (body: SortQuestionListItemBody) => { + return httpClient.request(adminQuestionListApi.sortQuestionListItems, { + body: body, + }) + }, +} + +export const userQuestionListService = { + /** + * 获取题单项列表 + */ + getQuestionListByIdService: (query: QuestionListItemQueryParams) => { + return httpClient.request( + questionListApi.getQuestionListItems, + { + queryParams: query, + }, + ) + }, +} diff --git a/frontend/src/domain/questionList/types/types.ts b/frontend/src/domain/questionList/types/types.ts new file mode 100644 index 0000000..3712946 --- /dev/null +++ b/frontend/src/domain/questionList/types/types.ts @@ -0,0 +1,92 @@ +import { QuestionDetail, QuestionSummary } from '../../question' +import { UserQuestionStatus } from '../../question/types/service.ts' + +export enum QuestionListType { + COMMON_TYPE = 1, // 普通题单 + TRAINING_CAMP_TYPE = 2, // 训练营专属题单 +} + +export enum QuestionListParentNode { + COMMON = -1, + TRAINING_CAMP = -2, +} + +/** + * 题单实体 + */ +export interface QuestionListEntity { + questionListId: number + name: string + type: QuestionListType + description: string + createdAt: string + updatedAt: string +} + +/** + * 题单项实体 + */ +export interface QuestionListItemEntity { + questionListId: number + questionId: number + rank: number + createdAt: string + updatedAt: string +} + +/** + * 题单分类实体 + */ +export interface QuestionListCategory { + key: number + title: string + questionListId: number | undefined + children: QuestionListCategory[] | undefined +} + +/** + * 题单项详情 VO + */ +export interface QuestionListItemVO { + questionListId: number + question: QuestionSummary + rank: number +} + +export interface QuestionListItemUserVO { + questionListId: number + question: QuestionDetail + rank: number + userQuestionStatus: UserQuestionStatus +} + +/** + * 创建题单body实体 + */ +export interface CreateOrUpDateQuestionListBody { + name: string + description: string + type: number +} + +/** + * 题单排序服务 body 实体 + */ +export interface SortQuestionListItemBody { + questionListId: number + questionIds: number[] +} + +/** + * 题单项查询参数 + */ +export interface QuestionListItemQueryParams { + questionListId: number | undefined + page: number + pageSize: number +} + +/** + * 抽屉操作类型 + */ +export type QuestionListOptType = 'create' | 'update' diff --git a/frontend/src/domain/questionList/utils/index.ts b/frontend/src/domain/questionList/utils/index.ts new file mode 100644 index 0000000..2bfebd2 --- /dev/null +++ b/frontend/src/domain/questionList/utils/index.ts @@ -0,0 +1,52 @@ +import { + QuestionListCategory, + QuestionListEntity, + QuestionListParentNode, + QuestionListType, +} from '../types/types.ts' + +export function convertQuestionListToTreeStruct( + questionLists: QuestionListEntity[] | undefined, +): QuestionListCategory[] { + if (!questionLists) return [] + + const result = [ + { + key: QuestionListParentNode.COMMON, + title: '普通题单', + questionListId: undefined, + children: [], + }, + { + key: QuestionListParentNode.TRAINING_CAMP, + title: '专属题单', + questionListId: undefined, + children: [], + }, + ] as QuestionListCategory[] + + for (const questionList of questionLists) { + if (questionList.type === QuestionListType.COMMON_TYPE) { + if (result[0].children) { + result[0].children.push({ + key: questionList.questionListId, + title: questionList.name, + questionListId: questionList.questionListId, + children: undefined, + }) + } + } + if (questionList.type === QuestionListType.TRAINING_CAMP_TYPE) { + if (result[1].children) { + result[1].children.push({ + key: questionList.questionListId, + title: questionList.name, + questionListId: questionList.questionListId, + children: undefined, + }) + } + } + } + + return result +} diff --git a/frontend/src/domain/statistic/api/index.ts b/frontend/src/domain/statistic/api/index.ts new file mode 100644 index 0000000..d27a9f2 --- /dev/null +++ b/frontend/src/domain/statistic/api/index.ts @@ -0,0 +1,5 @@ +import { ApiList } from '../../../request' + +export const statisticApiList: ApiList = { + getStatistic: ['GET', '/api/statistic'], +} diff --git a/frontend/src/domain/statistic/components/StatisticTable.tsx b/frontend/src/domain/statistic/components/StatisticTable.tsx new file mode 100644 index 0000000..ab0cb15 --- /dev/null +++ b/frontend/src/domain/statistic/components/StatisticTable.tsx @@ -0,0 +1,73 @@ +import React, { useState } from 'react' +import { StatisticEntity } from '../types/types.ts' +import { Pagination, Table, TableProps } from 'antd' +import { useStatistic } from '../hooks/useStatistic.ts' + +const columns: TableProps['columns'] = [ + { + title: '日期', + dataIndex: 'date', + key: 'date', + }, + { + title: '登录人数', + dataIndex: 'loginCount', + key: 'loginCount', + }, + { + title: '今日注册', + dataIndex: 'registerCount', + key: 'registerCount', + }, + { + title: '累计注册', + dataIndex: 'totalRegisterCount', + key: 'totalRegisterCount', + }, + { + title: '今日笔记数', + dataIndex: 'noteCount', + key: 'noteCount', + }, + { + title: '今日提交笔记人数', + dataIndex: 'submitNoteCount', + key: 'submitNoteCount', + }, + { + title: '累计笔记数', + dataIndex: 'totalNoteCount', + key: 'totalNoteCount', + }, +] + +const StatisticTable: React.FC = () => { + const [page, setPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + + const { loading, statistic, pagination } = useStatistic(page, pageSize) + + return ( +
+
+
+ { + setPage(page) + setPageSize(pageSize) + }} + > +
+
+ ) +} + +export default StatisticTable diff --git a/frontend/src/domain/statistic/hooks/useStatistic.ts b/frontend/src/domain/statistic/hooks/useStatistic.ts new file mode 100644 index 0000000..902bce4 --- /dev/null +++ b/frontend/src/domain/statistic/hooks/useStatistic.ts @@ -0,0 +1,39 @@ +import { useEffect, useState } from 'react' +import { statisticService } from '../service/statisticService.ts' +import { Pagination } from '../../../request' +import { StatisticEntity } from '../types/types.ts' + +export function useStatistic(page: number, pageSize: number) { + /** + * 统计数据 + */ + const [statistic, setStatistic] = useState() + + /** + * 分页数据 + */ + const [pagination, setPagination] = useState() + + const [loading, setLoading] = useState(false) + + useEffect(() => { + async function fetchData() { + setLoading(true) + const { data, pagination } = await statisticService.getStatisticService({ + page, + pageSize, + }) + setLoading(false) + setStatistic(data) + setPagination(pagination) + } + + fetchData().then() + }, [page, pageSize]) + + return { + loading, + statistic, + pagination, + } +} diff --git a/frontend/src/domain/statistic/index.ts b/frontend/src/domain/statistic/index.ts new file mode 100644 index 0000000..949feed --- /dev/null +++ b/frontend/src/domain/statistic/index.ts @@ -0,0 +1,3 @@ +import StatisticTable from './components/StatisticTable.tsx' + +export { StatisticTable } diff --git a/frontend/src/domain/statistic/service/statisticService.ts b/frontend/src/domain/statistic/service/statisticService.ts new file mode 100644 index 0000000..cfd616c --- /dev/null +++ b/frontend/src/domain/statistic/service/statisticService.ts @@ -0,0 +1,14 @@ +import { httpClient } from '../../../request' +import { StatisticEntity } from '../types/types.ts' +import { statisticApiList } from '../api' + +export const statisticService = { + getStatisticService: (queryParams: { page: number; pageSize: number }) => { + return httpClient.request( + statisticApiList.getStatistic, + { + queryParams: queryParams, + }, + ) + }, +} diff --git a/frontend/src/domain/statistic/types/types.ts b/frontend/src/domain/statistic/types/types.ts new file mode 100644 index 0000000..76a1cad --- /dev/null +++ b/frontend/src/domain/statistic/types/types.ts @@ -0,0 +1,44 @@ +/** + * 统计信息接口,包含登录、注册、笔记等统计数据 + */ +export interface StatisticEntity { + /** + * 主键 ID + */ + id: number + + /** + * 当天登录次数 + */ + loginCount: number + + /** + * 当天注册人数 + */ + registerCount: number + + /** + * 累计注册总人数 + */ + totalRegisterCount: number + + /** + * 当天笔记数量 + */ + noteCount: number + + /** + * 当天提交的笔记数量 + */ + submitNoteCount: number + + /** + * 累计笔记总数量 + */ + totalNoteCount: number + + /** + * 统计日期 + */ + date: string // TypeScript 使用 string 表示日期时间 +} diff --git a/frontend/src/domain/user/api/userApi.ts b/frontend/src/domain/user/api/userApi.ts new file mode 100644 index 0000000..75c5c11 --- /dev/null +++ b/frontend/src/domain/user/api/userApi.ts @@ -0,0 +1,17 @@ +import { ApiList } from '../../../request' + +// 用户端的 apiList +export const userApiList: ApiList = { + register: ['POST', '/api/users'], + login: ['POST', '/api/users/login'], + logout: ['POST', '/api/users/logout'], + whoami: ['POST', '/api/users/whoami'], + getUser: ['GET', '/api/users/{userId}'], + updateMe: ['PATCH', '/api/users/me'], + uploadImage: ['POST', '/api/upload/image'], +} + +// 管理端的 apiList +export const adminUserApiList: ApiList = { + getUserList: ['GET', '/api/admin/users'], +} diff --git a/frontend/src/domain/user/components/LoginModal.tsx b/frontend/src/domain/user/components/LoginModal.tsx new file mode 100644 index 0000000..450274b --- /dev/null +++ b/frontend/src/domain/user/components/LoginModal.tsx @@ -0,0 +1,154 @@ +import React, { useState } from 'react' +import { Avatar, Button, Form, Input, message, Modal, Segmented } from 'antd' +import { + ALPHANUMERIC_UNDERSCORE, + ALPHANUMERIC_UNDERSCORE_CHINESE, + PASSWORD_ALLOWABLE_CHARACTERS, +} from '../../../base/regex' +import { useLogin } from '../hooks/useLogin.ts' +import { useRegister } from '../hooks/useRegister.ts' +import { useForm } from 'antd/es/form/Form' + +const LoginModal: React.FC = () => { + const [open, setOpen] = useState(false) + const [value, setValue] = useState('login') + const [loading, setLoading] = useState(false) + + const { loginHandle } = useLogin() + const { registerHandle } = useRegister() + + const [form] = useForm() + + async function onFinish(values: any) { + try { + setLoading(true) + if (value === 'login') { + await loginHandle(values) + message.success('登录成功') + } else if (value === 'register') { + await registerHandle(values) + message.success('注册成功') + } else { + message.error('走到这个分支表示出大问题辣!') + } + setLoading(false) + setOpen(false) + } catch (e: any) { + message.error(e.message) + } finally { + setLoading(false) + } + } + + const LoginForm = () => { + return ( +
+ + + + {value === 'register' && ( + + + + )} + + + + +
+ ) + } + + return ( +
+ setOpen(true)}> + 登录 + + setOpen(false)} + footer={null} + > +
+ setValue(value)} + > +
+
+ +
+
+
+ ) +} + +export default LoginModal diff --git a/frontend/src/domain/user/components/ProfileMenu.tsx b/frontend/src/domain/user/components/ProfileMenu.tsx new file mode 100644 index 0000000..897bb56 --- /dev/null +++ b/frontend/src/domain/user/components/ProfileMenu.tsx @@ -0,0 +1,65 @@ +import React from 'react' +import { NavLink } from 'react-router-dom' +import { Config, Logout, Permissions, User } from '@icon-park/react' +import { USER_CENTER, USER_HOME } from '../../../apps/user/router/config.ts' +import { useUser } from '../hooks/useUser.ts' +import { useApp } from '../../../base/hooks' +import { useLogout } from '../hooks/useLogout.ts' +import { Admin } from '../types/types.ts' + +const ProfileMenu: React.FC = () => { + const user = useUser() + const app = useApp() + /** + * 退出登录处理函数 + */ + const logout = useLogout() + const handleLogout = () => { + logout() + } + + const itemCss = + 'flex items-center w-full p-2 hover:bg-gray-100 rounded hover:text-neutral-700 gap-2' + + return ( +
+ + + 个人主页 + + + + 个人信息 + + {/* 只有管理员才能查看后台内容 */} + {user.isAdmin === Admin.ADMIN && ( + + + {!app.isAdminApp ? '后台管理' : '返回用户端'} + + )} +
+ + 退出登录 +
+
+ ) +} + +export default ProfileMenu diff --git a/frontend/src/domain/user/components/UserAvatarMenu.tsx b/frontend/src/domain/user/components/UserAvatarMenu.tsx new file mode 100644 index 0000000..46a9ec3 --- /dev/null +++ b/frontend/src/domain/user/components/UserAvatarMenu.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import { Avatar, Popover } from 'antd' +import ProfileMenu from './ProfileMenu.tsx' +import { useUser } from '../hooks/useUser.ts' +import { UserOutlined } from '@ant-design/icons' + +const UserAvatarMenu: React.FC = () => { + const user = useUser() + + return ( +
+ } + > + } + className="cursor-pointer" + > + U + + +
+ ) +} + +export default UserAvatarMenu diff --git a/frontend/src/domain/user/components/UserHomeProfile.tsx b/frontend/src/domain/user/components/UserHomeProfile.tsx new file mode 100644 index 0000000..45f367f --- /dev/null +++ b/frontend/src/domain/user/components/UserHomeProfile.tsx @@ -0,0 +1,54 @@ +import React from 'react' +import { UserVO } from '../types/types.ts' +import { Panel } from '../../../base/components' +import { Avatar } from 'antd' +import { UserOutlined } from '@ant-design/icons' + +interface UserHomeProfileProps { + user?: UserVO +} + +const UserHomeProfile: React.FC = ({ user }) => { + return ( + +
+
+ + + +
+
+ {user?.username} +
+ {user?.school && ( + + {user.school} + + )} + {user?.school && user?.email && ( + | + )} + {user?.email && ( + <> + + {user.email} + + + )} +
+ {user?.signature && ( + + {user.signature} + + )} +
+
+
+ ) +} + +export default UserHomeProfile diff --git a/frontend/src/domain/user/components/UserInfoForm.tsx b/frontend/src/domain/user/components/UserInfoForm.tsx new file mode 100644 index 0000000..afbc8fb --- /dev/null +++ b/frontend/src/domain/user/components/UserInfoForm.tsx @@ -0,0 +1,187 @@ +import React, { useEffect, useState } from 'react' +import { useUser } from '../hooks/useUser.ts' +import { + Avatar, + Button, + DatePicker, + Form, + GetProp, + Input, + message, + Select, + UploadFile, + UploadProps, +} from 'antd' +import { UserState } from '../types/types.ts' +import { useUserForm } from '../hooks/useUserForm.ts' +import dayjs from 'dayjs' +import { Upload } from 'antd' +import ImgCrop from 'antd-img-crop' +import { UploadChangeParam } from 'antd/es/upload/interface' + +const UserInfoForm: React.FC = () => { + /** + * 用户信息展示 + */ + const user = useUser() as UserState + + const [form] = Form.useForm() + const [editing, setEditing] = useState(false) + + const { updateUserInfo, updateUserAvatar } = useUserForm() + + const handleEditToggle = () => { + setEditing(!editing) + } + + useEffect(() => { + form.setFieldsValue({ + username: user.username, + gender: user.gender, + birthday: dayjs(user.birthday), + email: user.email, + school: user.school, + signature: user.signature, + }) + }) + + const handleSave = async (values: UserState) => { + const oldValues = user + // 提取差异字段 + const diff: Partial = {} + + // 这段确实需要特殊处理 birthday 字段 + Object.entries(values).forEach(([key, newValue]) => { + // @ts-expect-error ... + const oldValue = oldValues[key] + if (key === 'birthday') { + // 类型检查并格式化生日 + const newBirthday = newValue + ? dayjs(newValue as string | Date).format('YYYY-MM-DD') + : null + const oldBirthday = + dayjs(oldValues[key] as string | Date).format('YYYY-MM-DD') ?? null + if (newBirthday !== oldBirthday) { + // @ts-expect-error ... + diff[key] = newBirthday + } + } else if (newValue !== oldValue) { + // @ts-expect-error ... + diff[key] = newValue + } + }) + + // 如果没有修改任何字段,提示用户无需更新 + if (Object.keys(diff).length === 0) { + message.info('未更新任何字段') + return + } + + try { + await updateUserInfo(diff) + message.success('更新成功!') + } catch (e: any) { + message.error(e.message) + } finally { + setEditing(false) + } + } + + type FileType = Parameters>[0] + + const onPreview = async (file: UploadFile) => { + let src = file.url as string + if (!src) { + src = await new Promise((resolve) => { + const reader = new FileReader() + reader.readAsDataURL(file.originFileObj as FileType) + reader.onload = () => resolve(reader.result as string) + }) + } + const image = new Image() + image.src = src + const imgWindow = window.open(src) + imgWindow?.document.write(image.outerHTML) + } + + const uploadAvatarHandle = async (info: UploadChangeParam) => { + if (info.file.response?.code === 200) { + // 上传头像成功 + await updateUserAvatar(info.file.response.data.url) + } + } + + return ( +
+
+ + { + return import.meta.env.VITE_API_BASE_URL + '/api/users/avatar' + }} + onChange={uploadAvatarHandle} + onPreview={onPreview} + accept={'image/*'} + showUploadList={false} + > + + + +
+

{user.username}

+

{user.email}

+
+ +
+
+ + + + + + + + + + + + + + + + + + + {editing && ( + + + + + )} +
+
+ ) +} + +export default UserInfoForm diff --git a/frontend/src/domain/user/components/UserList.tsx b/frontend/src/domain/user/components/UserList.tsx new file mode 100644 index 0000000..23f14cf --- /dev/null +++ b/frontend/src/domain/user/components/UserList.tsx @@ -0,0 +1,147 @@ +import React, { useState } from 'react' +import { Button, Input, Pagination, Select, Table, Tag } from 'antd' +import { Admin, Banned } from '../types/types.ts' +import { UserListQueryParams } from '../types/serviceTypes.ts' +import { useUserList } from '../hooks/useUserList.ts' + +const UserList: React.FC = () => { + const [page, setPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + + const [filters, setFilters] = useState({ + page, + pageSize, + }) + + const { userList, pagination, loading } = useUserList(filters) + + // 表格列配置 + const columns = [ + { + title: '用户ID', + dataIndex: 'userId', + key: 'userId', + }, + { + title: '账号', + dataIndex: 'account', + key: 'account', + }, + { + title: '用户名', + dataIndex: 'username', + key: 'username', + }, + { + title: '是否管理员', + dataIndex: 'isAdmin', + key: 'isAdmin', + render: (isAdmin: Admin) => ( + + {isAdmin === Admin.ADMIN ? '是' : '否'} + + ), + }, + { + title: '是否封禁', + dataIndex: 'isBanned', + key: 'isBanned', + render: (isBanned: Banned) => ( + + {isBanned === Banned.BANNED ? '已封禁' : '正常'} + + ), + }, + ] + + return ( +
+ {/* 筛选区域 */} +
+ setFilters({ ...filters, userId: e.target.value })} + /> + setFilters({ ...filters, account: e.target.value })} + /> + setFilters({ ...filters, username: e.target.value })} + /> + + +
+
+ + +
+ + {/* 表格 */} + + + {/* 分页 */} + { + setPage(page) + setPageSize(pageSize) + setFilters({ + ...filters, + page: page, + pageSize: pageSize, + }) + }} + /> + + ) +} + +export default UserList diff --git a/frontend/src/domain/user/hooks/useLogin.ts b/frontend/src/domain/user/hooks/useLogin.ts new file mode 100644 index 0000000..b2fe2fc --- /dev/null +++ b/frontend/src/domain/user/hooks/useLogin.ts @@ -0,0 +1,45 @@ +import { useDispatch } from 'react-redux' +import { LoginBody } from '../types/serviceTypes.ts' +import { login } from '../../../store/appSlice.ts' +import { userService } from '../service/userService.ts' +import { message } from 'antd' +import { setUser } from '../../../store/userSlice.ts' +import { kamanoteUserToken } from '../../../base/constants' + +export function useLogin() { + const dispatch = useDispatch() + + async function handleUserAuth(token: string | undefined, data: any) { + if (!token) { + message.error('token is null') + throw new Error('token is null') + } + // 将 token 存储到 localStorage + localStorage.setItem(kamanoteUserToken, token) + // 存储用户信息 + dispatch(setUser(data)) + // 设置登录状态 + dispatch(login()) + } + + async function loginHandle(loginBody: LoginBody) { + const resp = await userService.loginService(loginBody) + const { token, data } = resp + await handleUserAuth(token, data) + } + + async function whoAmIHandle() { + try { + const resp = await userService.whoamiService() + const { data, token } = resp + await handleUserAuth(token, data) + } catch (e: unknown) { + console.log(e) + } + } + + return { + loginHandle, + whoAmIHandle, + } +} diff --git a/frontend/src/domain/user/hooks/useLogout.ts b/frontend/src/domain/user/hooks/useLogout.ts new file mode 100644 index 0000000..f1f5d81 --- /dev/null +++ b/frontend/src/domain/user/hooks/useLogout.ts @@ -0,0 +1,13 @@ +import { useDispatch } from 'react-redux' +import { resetUser } from '../../../store/userSlice.ts' +import { logout } from '../../../store/appSlice.ts' + +export function useLogout() { + const dispatch = useDispatch() + return () => { + // 清空 redux-user 中的用户信息 + dispatch(resetUser()) + // 将 redux-app 中的 isLogin 状态设置为 false + dispatch(logout()) + } +} diff --git a/frontend/src/domain/user/hooks/useRegister.ts b/frontend/src/domain/user/hooks/useRegister.ts new file mode 100644 index 0000000..a9683ec --- /dev/null +++ b/frontend/src/domain/user/hooks/useRegister.ts @@ -0,0 +1,43 @@ +import { RegisterBody } from '../types/serviceTypes.ts' +import { userService } from '../service/userService.ts' +import { useDispatch } from 'react-redux' +import { login } from '../../../store/appSlice.ts' +import { message } from 'antd' +import { kamanoteUserToken } from '../../../base/constants' +import { setUser } from '../../../store/userSlice.ts' +import type { UserEntity } from '../types/types.ts' + +export function useRegister() { + // 注册请求函数 + const dispatch = useDispatch() + + async function registerHandle(registerBody: RegisterBody) { + try { + const resp = await userService.registerService(registerBody) + if (resp) { + const { token, data } = resp + if (!token) { + message.error('token is null') + throw new Error('token is null') + } + // 将 token 存储到 localStorage + localStorage.setItem(kamanoteUserToken, token) + // 存储用户信息 + dispatch( + setUser({ + ...data, + ...registerBody, + } as UserEntity), + ) + // 设置登录状态 + dispatch(login()) + } + } catch (e: any) { + throw new Error(e.message) + } + } + + return { + registerHandle, + } +} diff --git a/frontend/src/domain/user/hooks/useUser.ts b/frontend/src/domain/user/hooks/useUser.ts new file mode 100644 index 0000000..5eef602 --- /dev/null +++ b/frontend/src/domain/user/hooks/useUser.ts @@ -0,0 +1,8 @@ +import { useSelector } from 'react-redux' +import { RootState } from '../../../store/store.ts' + +// 返回 Redux 中的个人信息 +// 针对于当前登录用户 +export function useUser() { + return useSelector((state: RootState) => state.user) +} diff --git a/frontend/src/domain/user/hooks/useUser2.ts b/frontend/src/domain/user/hooks/useUser2.ts new file mode 100644 index 0000000..19de424 --- /dev/null +++ b/frontend/src/domain/user/hooks/useUser2.ts @@ -0,0 +1,23 @@ +import { userService } from '../service/userService.ts' +import { useEffect, useState } from 'react' +import { UserVO } from '../types/types.ts' + +/** + * 获取其他用户信息 + */ +export function useUser2(userId: string) { + const [userVO, setUserVO] = useState() + + useEffect(() => { + async function fetchData() { + const { data } = await userService.getUserService(userId) + setUserVO(data) + } + + fetchData().then() + }, [userId]) + + return { + userVO, + } +} diff --git a/frontend/src/domain/user/hooks/useUserForm.ts b/frontend/src/domain/user/hooks/useUserForm.ts new file mode 100644 index 0000000..f05a2cd --- /dev/null +++ b/frontend/src/domain/user/hooks/useUserForm.ts @@ -0,0 +1,52 @@ +import { UserState } from '../types/types.ts' +import { userService } from '../service/userService.ts' +import { useDispatch } from 'react-redux' +import { setUser } from '../../../store/userSlice.ts' +import { useUser } from './useUser.ts' + +/** + * 更新个人信息 hook + */ +export function useUserForm() { + const dispatch = useDispatch() + const user = useUser() + + /** + * 更新个人信息 + */ + async function updateUserInfo(newUserInfo: Partial) { + // 将更新的个人信息字段发送给后端 + // await userService.updateMeService(newUserInfo) + // 将更新的内容提交到 store 中 + try { + await userService.updateMeService(newUserInfo) + dispatch( + setUser({ + ...user, + ...newUserInfo, + }), + ) + } catch (e: any) { + throw new Error(e.message) + } + } + + /** + * 更新用户头像 + */ + async function updateUserAvatar(newAvatarUrl: string) { + // 更新头像字段 + try { + await userService.updateMeService({ avatarUrl: newAvatarUrl }) + dispatch( + setUser({ + ...user, + avatarUrl: newAvatarUrl, + }), + ) + } catch (e: any) { + throw new Error(e.message) + } + } + return { updateUserInfo, updateUserAvatar } +} diff --git a/frontend/src/domain/user/hooks/useUserList.ts b/frontend/src/domain/user/hooks/useUserList.ts new file mode 100644 index 0000000..78eafd1 --- /dev/null +++ b/frontend/src/domain/user/hooks/useUserList.ts @@ -0,0 +1,55 @@ +import { useEffect, useState } from 'react' +import { UserEntity } from '../types/types.ts' +import { defaultPagination, Pagination } from '../../../request' +import { UserListQueryParams } from '../types/serviceTypes.ts' +import { adminUserService } from '../service/userService.ts' + +export function useUserList(query: UserListQueryParams) { + /** + * 加载数据中的标记 + */ + const [loading, setLoading] = useState(false) + + /** + * 用户列表 + */ + const [userList, setUserList] = useState([]) + + /** + * 分页信息 + */ + const [pagination, setPagination] = useState( + defaultPagination, + ) + + async function fetchUserList() { + setLoading(true) + const { data, pagination } = + await adminUserService.getUserListService(query) + setUserList(data) + setPagination(pagination) + setLoading(false) + } + + useEffect(() => { + async function fetchData() { + await fetchUserList() + } + + fetchData().then() + }, [ + query.userId, + query.username, + query.account, + query.isAdmin, + query.isBanned, + query.page, + query.pageSize, + ]) + + return { + loading, + userList, + pagination, + } +} diff --git a/frontend/src/domain/user/index.ts b/frontend/src/domain/user/index.ts new file mode 100644 index 0000000..46d8a9c --- /dev/null +++ b/frontend/src/domain/user/index.ts @@ -0,0 +1,17 @@ +import type { UserEntity, UserState } from './types/types.ts' +import { Gender, Admin, UserVO } from './types/types.ts' +import LoginModal from './components/LoginModal.tsx' +import UserAvatarMenu from './components/UserAvatarMenu.tsx' +import UserInfoForm from './components/UserInfoForm.tsx' +import { userService } from './service/userService.ts' +import { useLogin } from './hooks/useLogin.ts' +import UserList from './components/UserList.tsx' +import UserHomeProfile from './components/UserHomeProfile.tsx' +import { useUser2 } from './hooks/useUser2.ts' + +export type { UserEntity, UserState, UserVO } +export { Gender, Admin } +export { LoginModal, UserAvatarMenu, UserInfoForm, UserHomeProfile } +export { userService, useLogin } +export { UserList } +export { useUser2 } diff --git a/frontend/src/domain/user/service/userService.ts b/frontend/src/domain/user/service/userService.ts new file mode 100644 index 0000000..0741285 --- /dev/null +++ b/frontend/src/domain/user/service/userService.ts @@ -0,0 +1,81 @@ +import { httpClient } from '../../../request' +import { adminUserApiList, userApiList } from '../api/userApi.ts' +import { + LoginBody, + RegisterBody, + RegisterData, + UploadImageData, + UserListQueryParams, +} from '../types/serviceTypes.ts' +import { UserEntity, UserState, UserVO } from '../types/types.ts' + +/** + * userService + */ +export const userService = { + /** + * 用户注册接口 + */ + registerService: (body: RegisterBody) => { + return httpClient.request(userApiList.register, { + body: body, + }) + }, + + /** + * 用户登录接口 + */ + loginService: (body: LoginBody) => { + return httpClient.request(userApiList.login, { + body: body, + }) + }, + + /** + * 自动登录接口 + */ + whoamiService: () => { + return httpClient.request(userApiList.whoami) + }, + + /** + * 更新用户个人信息 + */ + updateMeService: (body: Partial) => { + return httpClient.request(userApiList.updateMe, { + body: body, + }) + }, + + /** + * 获取用户信息 + */ + getUserService: (userId: string) => { + return httpClient.request(userApiList.getUser, { + pathParams: [userId], + }) + }, + + /** + * 上传图片接口 + */ + uploadImageService: (body: FormData) => { + return httpClient.request(userApiList.uploadImage, { + body: body, + }) + } +} + +/** + * adminUserService + */ +export const adminUserService = { + /** + * 批量查询用户 + */ + getUserListService: (params: UserListQueryParams) => { + return httpClient.request(adminUserApiList.getUserList, { + queryParams: params, + }) + }, +} diff --git a/frontend/src/domain/user/types/serviceTypes.ts b/frontend/src/domain/user/types/serviceTypes.ts new file mode 100644 index 0000000..c8cb3f9 --- /dev/null +++ b/frontend/src/domain/user/types/serviceTypes.ts @@ -0,0 +1,40 @@ +/** + * 注册请求参数和返回参数 + */ +export type RegisterBody = { + username: string + account: string + password: string +} + +export type RegisterData = { + userId: string +} + +/** + * 登录时的请求参数和返回参数 + */ +export type LoginBody = { + account: string + password: string +} + +/** + * 上传图片返回的数据类型 + */ +export type UploadImageData = { + url: string +} + +/** + * 查询用户列表时的参数列表 + */ +export type UserListQueryParams = { + userId?: string + username?: string + account?: string + isAdmin?: number + isBanned?: number + page: number + pageSize: number +} diff --git a/frontend/src/domain/user/types/types.ts b/frontend/src/domain/user/types/types.ts new file mode 100644 index 0000000..d8a8165 --- /dev/null +++ b/frontend/src/domain/user/types/types.ts @@ -0,0 +1,49 @@ +export enum Gender { + MALE = 1, + FEMALE = 2, + OTHER = 3, +} + +export enum Banned { + UNBANNED = 0, + BANNED = 1, +} + +export enum Admin { + NOT_ADMIN = 0, + ADMIN = 1, +} + +/** + * 数据库中的用户实体 + */ +export interface UserEntity { + userId: string + account: string + password: string + username: string + gender: Gender + birthday: string + avatarUrl: string + email: string + school: string + signature: string + isBanned: Banned + isAdmin: Admin + lastLoginAt: string + createdAt: string + updatedAt: string +} + +/** + * Redux 中的用户状态 + */ +export type UserState = Omit< + UserEntity, + 'password' | 'isBanned' | 'createdAt' | 'updatedAt' | 'lastLoginAt' +> + +export type UserVO = Omit< + UserEntity, + 'updatedAt' | 'password' | 'birthday' | 'isAdmin' | 'account' | 'isBanned' +> diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..a5d328a --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,36 @@ +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' +import { ConfigProvider } from 'antd' +import zhCN from 'antd/locale/zh_CN' +import { BrowserRouter } from 'react-router-dom' +import { Provider } from 'react-redux' +import { store } from './store/store.ts' + +createRoot(document.getElementById('root')!).render( + + + + + + + , +) diff --git a/frontend/src/request/fetchClient.ts b/frontend/src/request/fetchClient.ts new file mode 100644 index 0000000..9dda0e8 --- /dev/null +++ b/frontend/src/request/fetchClient.ts @@ -0,0 +1,103 @@ +import { Code, HttpClient, Options, RequestTuple, Response } from './types.ts' +import { kamanoteUserToken } from '../base/constants' + +export default class FetchClient implements HttpClient { + private readonly baseURL = import.meta.env.VITE_API_BASE_URL + constructor() {} + + // 处理路径参数 + private processPathParams(path: string, pathParams: Array): string { + let paramIndex = 0 + // 替换路径中的占位符 + return path.replace(/\{(\w+)\}/g, () => { + const param = pathParams[paramIndex++] + if (param === undefined) { + throw new Error('Missing path parameter') + } + return encodeURIComponent(param) + }) + } + + async request( + requestTuple: RequestTuple, + options?: Options, + ): Promise> { + const [method, requestPath] = requestTuple + + // 最终的请求路径 + let requestURL = `${this.baseURL}${requestPath}` + + // 默认请求头 + const headers: HeadersInit = { + 'Content-Type': 'application/json', + ...(options?.headers || {}), + } + + // 如果存在 token,则需要携带 token + if (localStorage.getItem(kamanoteUserToken)) { + headers['Authorization'] = + `Bearer ${localStorage.getItem(kamanoteUserToken)}` + } + + // Fetch API 配置 + const fetchOptions: RequestInit = { + method, + headers, + } + + // 处理路径参数 + if (options?.pathParams) { + try { + requestURL = this.processPathParams(requestURL, options.pathParams) + } catch (err) { + console.error(err) + throw new Error('Failed to process path parameters') + } + } + + /** + * 处理 GET 请求的 query 参数 + */ + if (method === 'GET' && options?.queryParams) { + /** + * 对 queryParams 中的值为 undefined 的 key 进行过滤 + */ + const queryParams = Object.fromEntries( + Object.entries(options.queryParams).filter( + ([, value]) => value !== undefined, + ), + ) + const queryString = new URLSearchParams(queryParams).toString() + requestURL += queryString ? `?${queryString}` : '' + } + + // 处理 GET 方法以外的其它方法 + if (method !== 'GET' && options?.body) { + if (options.body instanceof FormData) { + // 如果是 FormData,移除 Content-Type + delete headers['Content-Type'] + fetchOptions.body = options.body + } else { + // TODO: JSON.stringify 会自动过滤 undefined 属性?? + fetchOptions.body = JSON.stringify(options.body) + } + } + + try { + const response = await fetch(requestURL, fetchOptions) + const result = (await response.json()) as Response + + if (result.code !== Code.SUCCESS) { + // 事先判断 + throw new Error(result.msg) + } + + return result + // 包装返回的结果,符合接口定义 + } catch (error) { + // 捕获错误并包装为统一响应格式 + console.error('error', error) + throw new Error(error instanceof Error ? error.message : 'Unknown error') + } + } +} diff --git a/frontend/src/request/index.ts b/frontend/src/request/index.ts new file mode 100644 index 0000000..1a8d585 --- /dev/null +++ b/frontend/src/request/index.ts @@ -0,0 +1,37 @@ +import { + HttpMethod, + RequestTuple, + Code, + Options, + Response, + Pagination, + ApiList, +} from './types' +import FetchClient from './fetchClient.ts' + +/** + * 导出 HTTP 请求相关的类型定义 + */ +export type { + HttpMethod, + Code, + RequestTuple, + Options, + Response, + Pagination, + ApiList, +} + +const defaultPagination: Pagination = { + page: 1, + pageSize: 10, + total: 10, +} + +export { defaultPagination } + +/** + * 导出 HTTP 请求的客户端 + */ +const httpClient = new FetchClient() +export { httpClient } diff --git a/frontend/src/request/types.ts b/frontend/src/request/types.ts new file mode 100644 index 0000000..3a3e89b --- /dev/null +++ b/frontend/src/request/types.ts @@ -0,0 +1,71 @@ +/** + * 网络请求类型 + */ +export type HttpMethod = + | 'GET' + | 'POST' + | 'PUT' + | 'DELETE' + | 'PATCH' + | 'OPTIONS' + | 'HEAD' + +type RequestPath = string + +export type RequestTuple = [HttpMethod, RequestPath] + +/** + * 网络请求参数 + */ +export type Options = { + headers?: Record + body?: FormData | Record + queryParams?: Record + pathParams?: Array +} + +/** + * 网络请求客户端接口 + */ +export interface HttpClient { + request: ( + requestTuple: RequestTuple, + options?: Options, + ) => Promise> +} + +/** + * API 列表格式 + */ +export type ApiList = { + [key: string]: RequestTuple +} + +/** + * 分页数据 + */ +export type Pagination = { + // 分页数据 + page: number + pageSize: number + total: number +} + +/** + * 响应码 + */ +export enum Code { // 状态码 + SUCCESS = 200, // 请求成功返回 200 + FAIL = 400, // TODO: 错误码需要和后端一起确定 +} + +/** + * 响应数据 + */ +export type Response = { + code: Code + msg: string + data: T + pagination?: Pagination // 分页查询时需要 + token?: string // 登录 / 认证时会返回 token +} diff --git a/frontend/src/store/appSlice.ts b/frontend/src/store/appSlice.ts new file mode 100644 index 0000000..0843149 --- /dev/null +++ b/frontend/src/store/appSlice.ts @@ -0,0 +1,44 @@ +import { createSlice } from '@reduxjs/toolkit' + +type AppState = { + isLogin: boolean // 记录登录状态 + isLoaded: boolean // 记录加载状态 + isAdminApp: boolean // 记录当前是否在管理端下 +} + +const initialAppState: AppState = { + isLogin: false, + isLoaded: false, + isAdminApp: false, +} + +const appSlice = createSlice({ + name: 'app', + initialState: initialAppState, + reducers: { + login: (state) => { + state.isLogin = true + }, + logout: (state) => { + state.isLogin = false + }, + loaded: (state) => { + state.isLoaded = true + }, + intoAdminApp: (state) => { + state.isAdminApp = true + }, + outAdminApp: (state) => { + state.isAdminApp = false + }, + }, +}) + +export const { + login, + logout, + loaded, + intoAdminApp, + outAdminApp, +} = appSlice.actions +export default appSlice.reducer diff --git a/frontend/src/store/store.ts b/frontend/src/store/store.ts new file mode 100644 index 0000000..e3d2fec --- /dev/null +++ b/frontend/src/store/store.ts @@ -0,0 +1,13 @@ +import { configureStore } from '@reduxjs/toolkit' +import userReducer from './userSlice.ts' +import appReducer from './appSlice.ts' + +export const store = configureStore({ + reducer: { + user: userReducer, + app: appReducer, + }, +}) + +export type RootState = ReturnType +export type AppDispatch = typeof store.dispatch diff --git a/frontend/src/store/userSlice.ts b/frontend/src/store/userSlice.ts new file mode 100644 index 0000000..fa4fa02 --- /dev/null +++ b/frontend/src/store/userSlice.ts @@ -0,0 +1,32 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import type { UserState } from '../domain/user' +import { Gender, Admin } from '../domain/user' + +const initialUserState: UserState = { + userId: '', + username: '', + account: '', + email: '', + avatarUrl: '', + gender: Gender.OTHER, + school: '', + signature: '', + birthday: '', + isAdmin: Admin.NOT_ADMIN, +} + +const userSlice = createSlice({ + name: 'user', + initialState: initialUserState, + reducers: { + setUser: (_, action: PayloadAction) => { + return action.payload + }, + resetUser: () => { + return initialUserState + }, + }, +}) + +export const { setUser, resetUser } = userSlice.actions +export default userSlice.reducer diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..ef951f0 --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,9 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + darkMode: 'selector', + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..358ca9b --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..db0becc --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/upload.sh b/frontend/upload.sh new file mode 100644 index 0000000..89f84be --- /dev/null +++ b/frontend/upload.sh @@ -0,0 +1,3 @@ +npm run build +scp -r ./dist ubuntu@43.136.59.48:/home/ubuntu/kamanote +rm -rf ./dist diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/kamanote-tech.sql b/kamanote-tech.sql new file mode 100644 index 0000000..bdc3c43 --- /dev/null +++ b/kamanote-tech.sql @@ -0,0 +1,324 @@ +-- MySQL dump 10.13 Distrib 8.3.0, for macos14.2 (arm64) +-- +-- Host: localhost Database: kamanote_tech +-- ------------------------------------------------------ +-- Server version 8.3.0 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `category` +-- + +DROP TABLE IF EXISTS `category`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `category` ( + `category_id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '分类 ID', + `name` varchar(32) NOT NULL COMMENT '分类名称', + `parent_category_id` int unsigned DEFAULT '0' COMMENT '上级分类 ID, 为 0 时表示当前分类是一级分类', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', + PRIMARY KEY (`category_id`), + KEY `idx_parent_category` (`parent_category_id`) +) ENGINE=InnoDB AUTO_INCREMENT=100036 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='分类表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `category` +-- + +LOCK TABLES `category` WRITE; +/*!40000 ALTER TABLE `category` DISABLE KEYS */; +INSERT INTO `category` VALUES (100000,'计算机基础 ',0,'2024-12-04 17:01:55','2024-12-04 17:02:47'),(100001,'计算机网络',100000,'2024-12-04 17:02:11','2024-12-04 17:02:11'),(100002,'操作系统',100000,'2024-12-04 17:02:26','2024-12-04 17:02:26'),(100003,'数据库',100000,'2024-12-04 17:02:39','2024-12-04 17:02:39'),(100004,'计算机组成原理',100000,'2024-12-04 17:03:12','2024-12-04 17:03:12'),(100005,'Java',0,'2024-12-04 17:03:31','2024-12-04 17:03:31'),(100006,'Java 语言基础',100005,'2024-12-04 17:03:51','2024-12-04 17:04:24'),(100007,'Java 面向对象编程(OOP)',100005,'2024-12-04 17:04:24','2024-12-04 17:05:08'),(100008,'Java 集合框架',100005,'2024-12-04 17:04:43','2024-12-04 17:04:43'),(100009,'Java 输入输出(I/O)',100005,'2024-12-04 17:05:44','2024-12-04 17:05:44'),(100010,'Java Web',100005,'2024-12-04 17:06:05','2024-12-04 17:06:05'),(100011,'Java 网络编程',100005,'2024-12-04 17:06:25','2024-12-04 17:06:25'),(100012,'Java 并发编程',100005,'2024-12-04 17:06:39','2024-12-04 17:06:39'),(100013,'Java 设计模式',100005,'2024-12-04 17:07:03','2024-12-04 17:07:03'),(100014,'Java 虚拟机(JVM)',100005,'2024-12-04 17:07:22','2024-12-04 17:07:22'),(100015,'Spring',100005,'2024-12-04 17:07:33','2024-12-04 17:07:33'),(100016,'Spring Boot',100005,'2024-12-04 17:07:49','2024-12-04 17:07:49'),(100017,'Spring Cloud',100005,'2024-12-04 17:08:02','2024-12-04 17:08:02'),(100018,'Java 性能优化',100005,'2024-12-04 17:08:16','2024-12-04 17:08:16'); +/*!40000 ALTER TABLE `category` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `collection` +-- + +DROP TABLE IF EXISTS `collection`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `collection` ( + `collection_id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '收藏夹 ID', + `name` varchar(32) NOT NULL COMMENT '收藏夹名称', + `description` varchar(255) DEFAULT NULL COMMENT '收藏夹描述', + `creator_id` bigint unsigned NOT NULL COMMENT '收藏夹创建者 ID', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', + PRIMARY KEY (`collection_id`) +) ENGINE=InnoDB AUTO_INCREMENT=400003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='收藏夹表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `collection` +-- + +LOCK TABLES `collection` WRITE; +/*!40000 ALTER TABLE `collection` DISABLE KEYS */; +INSERT INTO `collection` VALUES (400000,'收藏夹1','我的收藏夹1',100015,'2025-01-07 17:24:25','2025-01-07 17:24:25'),(400001,'我的收藏夹2',NULL,100015,'2025-01-07 17:39:47','2025-01-07 17:39:47'),(400002,'收藏夹',NULL,100015,'2025-01-07 19:58:01','2025-01-07 19:58:01'); +/*!40000 ALTER TABLE `collection` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `collection_note` +-- + +DROP TABLE IF EXISTS `collection_note`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `collection_note` ( + `collection_id` int unsigned NOT NULL COMMENT '收藏夹 ID', + `note_id` int unsigned NOT NULL COMMENT '笔记 ID', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', + PRIMARY KEY (`collection_id`,`note_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='收藏笔记表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `collection_note` +-- + +LOCK TABLES `collection_note` WRITE; +/*!40000 ALTER TABLE `collection_note` DISABLE KEYS */; +INSERT INTO `collection_note` VALUES (400001,300523,'2025-01-09 15:27:15','2025-01-09 15:27:15'),(400002,301006,'2025-01-07 19:58:12','2025-01-07 19:58:12'),(400002,301015,'2025-01-09 15:27:43','2025-01-09 15:27:43'); +/*!40000 ALTER TABLE `collection_note` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `note` +-- + +DROP TABLE IF EXISTS `note`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `note` ( + `note_id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '笔记 ID', + `author_id` bigint unsigned NOT NULL COMMENT '笔记作者 ID', + `question_id` int unsigned NOT NULL COMMENT '笔记对应的问题 ID', + `content` text NOT NULL COMMENT '笔记内容', + `like_count` int unsigned NOT NULL DEFAULT '0' COMMENT '点赞数', + `comment_count` int unsigned NOT NULL DEFAULT '0' COMMENT '评论数', + `collect_count` int unsigned NOT NULL DEFAULT '0' COMMENT '收藏数', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', + PRIMARY KEY (`note_id`), + KEY `idx_author_id` (`author_id`), + KEY `idx_question_id` (`question_id`), + KEY `idx_author_question` (`author_id`,`question_id`) +) ENGINE=InnoDB AUTO_INCREMENT=301018 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='笔记表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `note` +-- + +LOCK TABLES `note` WRITE; +/*!40000 ALTER TABLE `note` DISABLE KEYS */; +INSERT INTO `note` VALUES (300800,100102,200049,'HTTP/2就像一位转身后的舞者,带着更加优雅与灵活的姿态,显著提升了网页的传输性能,相比于HTTP/1.1,它有几个显著的改进。\n\n### 1. 多路复用(Multiplexing)\nHTTP/2最引人注目的改进之一是多路复用。有点像一条交通繁忙的高速公路,HTTP/2允许多个请求和响应在同一条连接上并行进行,而不是像HTTP/1.1那样只允许单个请求。这样就避免了“头阻塞”的问题,减少了数据的等待时间,让数据飞速到达。\n\n### 2. 头部压缩(HPACK)\nHTTP/2使用了一种名为HPACK的头压缩技术,想象一下,旅途中我们不再携带冗长的行李,而是将必须携带的东西压缩到最小化。通过压缩HTTP头部,减少了传输的数据量,这样每次请求和响应都能使用更少的带宽,大大提高了效率。\n\n### 3. 服务端推送(Server Push)\n就像在一家餐馆里,服务员不仅仅满足于点单,而是提前将客人可能需要的菜品一起端上。在HTTP/2中,服务端可以主动向客户端推送资源,即使客户端没有明确请求。这样一来,页面加载得更快,用户体验大大提升。\n\n### 4. 二进制分帧(Binary Framing)\nHTTP/2将数据传输转换为二进制格式,像个潮流的数字魔术师。这使得信息在传输时更高效,而且更易于解析,虽然我们看不见它背后的魔力,但每一个请求、响应和优先级都被拆分为小的帧,快速在网络中游走。\n\n### 5. 优先级和流控制\nHTTP/2允许开发者为不同的请求设定优先级,想象一下,交通信号灯可以根据需要调节,可以保证重要请求的快速通过。此外,它还提供流控制,确保流量的平衡与流畅,避免网络“拥堵”。\n\n总的来说,HTTP/2通过一系列巧妙的设计,让数据的传输变得迅猛而高效,犹如一场美丽的交响乐,每一个部分都协调而富有节奏地进行,给用户带来更流畅的体验。',0,0,0,'2024-12-06 11:51:30','2024-12-27 09:44:03'),(300801,100040,200874,'智能指针?这简直是现代C++程序员的救命稻草啊!你问在什么情况下选择使用智能指针?嗯,让我给你几条“不怕死,偏爱智能”的理由:\n\n1. **内存泄漏的噩梦**:如果你想避免成为内存泄漏的祭坛中一名无辜的牺牲者,那就赶紧用智能指针吧!指针从此不再孤独,它们会有一个小伙伴负责管理内存,让你过上无忧无虑的生活。\n\n2. **自动管理生命周期**:你知道,手动管理资源就像放着一锅炖菜却忘了加水,等待你的是一锅黑乎乎的焦炭。智能指针会在你不需要的时候自动释放内存,这样你就没有理由去担心“我是不是该回收那个指针”的问题了。\n\n3. **共享指针的强大**:当你有多个地方需要访问同一个对象,选择`std::shared_ptr`就像是给这个对象加了一个“众筹”的概念,让大家一起照顾这个小家伙,记住,谁最后不想要它了,它就会在合适的时候被回收。\n\n4. **避免悬空指针**:使用智能指针就像给指针系上了安全带,避免因为野指针而受伤!你不想在项目中到处跑的垃圾指针把你绊倒吧?\n\n5. **抛出异常的保护者**:如果你的代码里到处都是抛出异常的地方,智能指针就像一个小护卫,确保即使出错了,内存也不会踏上自我毁灭的道路。\n\n所以,除了要生活愉快之外,智能指针就是你的“选择之星”,教你如何优雅地活在C++的世界里!别再用那些古早的手动指针了,你不是超人,别试图用手去抓住空气!',0,0,0,'2024-12-06 11:51:36','2024-12-27 09:44:03'),(300802,100078,200082,'操作系统的文件共享机制就像是一个热闹的图书馆,里面有成千上万本书,但并不是每个人都能随意拿走所有的书。相反,图书馆通过一些巧妙的办法,确保不同的人能够高效而安全地访问相同的资源。\n\n### 文件共享机制的基本概念\n\n1. **共享访问**:多个用户或进程可以同时访问同一个文件,就像几位读者可以同时在图书馆里阅读同一本书。\n2. **互斥控制**:为了防止冲突和数据损坏,操作系统需要确保同时修改文件的进程不会干扰彼此。这就像图书馆设定一个规则,防止两个人同时在书上做标记,造成混乱。\n3. **权限管理**:操作系统能够控制哪些用户或程序可以访问这些文件,就像图书馆通过借书证来管理借阅图书的读者。\n\n### 实现方式\n\n要实现文件共享机制,操作系统通常采用以下几种方法:\n\n1. **文件系统设计**:\n - 操作系统通过文件系统的设计来管理文件,比如使用目录结构,确保文件可被快速找到和访问。\n\n2. **锁机制**:\n - 当某个进程需要写入文件时,它可以申请一个“写锁”,确保其他进程无法同时写入这个文件,类似于图书馆里一种预约制度,先来的人可以预约使用。\n\n3. **读写权限**:\n - 操作系统通过设置文件的访问权限(如读取、写入和执行权限),来确保只有被授权的用户才能访问特定的文件。这就像图书馆给不同的读者分配不同的借书权限。\n\n4. **版本控制**:\n - 对于一些需要频繁修改的文件,操作系统或应用程序会使用版本控制,记录文件的不同版本,方便用户回溯并避免丢失数据。\n\n5. **网络共享**:\n - 在网络环境中,文件共享可以通过网络文件系统(如NFS、SMB)实现。不同计算机之间可以通过网络访问共享的文件,就像不同城市的图书馆间有一种借阅协议,方便人们访问更多的书籍。\n\n6. **缓存机制**:\n - 为了提高性能,操作系统可能会对常用的文件进行缓存,减少对硬盘的直接访问。这有点类似于图书馆的助手,把最常借阅的书放在柜台前,方便读者快速找到。\n\n### 结语\n\n总的来说,文件共享机制是操作系统中一个至关重要的部分,它让我们能高效地在多用户环境中使用和管理文件。通过巧妙的设计和实现,操作系统就像一名优秀的图书馆管理员,不仅确保资源能够被共享,还能防止混乱和不必要的纠纷。',0,0,0,'2024-12-06 11:51:51','2024-12-27 09:44:03'),(300803,100115,200594,'`@EnableWebMvc` 注解是 Spring 框架中的一个强大工具,它的主要作用是启用 Spring MVC 的一系列功能,让开发者可以轻松地构建 Web 应用程序。在你的 Java 代码中施加这一魔法印记,你就可以进入一个程序化的奇幻世界。\n\n### `@EnableWebMvc` 的作用:\n\n1. **配置 Spring MVC**:\n - 这个注解会自动配置 DispatcherServlet,这是 MVC 模式的核心,负责处理所有的请求并将其分发到适当的控制器。\n\n2. **启用默认的 Spring MVC 功能**:\n - 包括视图解析器、消息转换器、以及格式化和验证功能。开启了这些功能,开发者可以更加专注于业务逻辑,无需为基础搭建而烦忧。\n\n3. **支持注解驱动的控制器**:\n - 通过这个注解,Spring MVC 可以识别以 `@Controller` 注解标记的类,并能将路由请求映射到相应的方法,这使得构建 RESTful 接口变得简单无比。\n\n4. **集成静态资源处理**:\n - `@EnableWebMvc` 有助于处理静态资源(如 CSS、JavaScript 和图片等),让这些资源的请求更为流畅,仿佛在和网站的灵魂对话。\n\n5. **启用拦截器支持**:\n - 你可以方便地注册拦截器,实现对请求的监控、预处理和后处理,提升应用的安全性和扩展性。\n\n### 如何启用 Spring MVC 的高级特性?\n\n1. **与 `@Configuration` 配合使用**:\n - 通常会将 `@EnableWebMvc` 放置在一个标有 `@Configuration` 的类上,形成一幅绝妙的框架图。这个类中可以定义各种服务和 bean,形成一个电力四射的应用环境。\n\n2. **结合自定义配置**:\n - 通过实现 `WebMvcConfigurer` 接口,可以对 MVC 的行为进行微调,增加自定义的拦截器、视图解析器或者消息转换器,创造出独一无二的互动体验。\n\n3. **自定义错误处理**:\n - 使用 `@ControllerAdvice` 和 `@ExceptionHandler` 注解,我们可以轻松处理全局的异常,让用户在探索网站时不会迷失方向。\n\n### 小结\n\n在这场编程的盛宴中,`@EnableWebMvc` 就像是一张通行证,带领开发者穿越复杂的 Web 开发世界。它开启了一扇全新的大门,让我们可以用更少的代码实现更复杂的功能,真正发挥出 Spring MVC 的魔力。将这一注解融入你的项目,让 Spring 的神奇与创意在你的代码中共舞吧!✨',0,0,0,'2024-12-06 11:52:02','2024-12-27 09:44:03'),(300804,100003,200569,'在Spring框架中,`BeanPostProcessor`接口允许我们在Bean的初始化前后对其进行操作。通过自定义的`BeanPostProcessor`,我们可以在Bean被创建和初始化的过程中修改其属性或改变其行为,增强应用程序的灵活性和可维护性。这种设计体现了面向切面编程(AOP)的思想,使得跨切关注点的处理变得更加优雅与透明。\n\n### 创建自定义的 BeanPostProcessor\n\n首先,我们需要创建一个实现`BeanPostProcessor`接口的类。这个类中的两个方法,`postProcessBeforeInitialization`和`postProcessAfterInitialization`,分别在Bean的初始化之前和之后被调用。\n\n```java\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.config.BeanPostProcessor;\n\npublic class CustomBeanPostProcessor implements BeanPostProcessor {\n\n @Override\n public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {\n // 在Bean初始化之前修改其属性\n if (bean instanceof SomeBean) {\n SomeBean someBean = (SomeBean) bean;\n someBean.setSomeProperty(\"Modified Value\");\n }\n return bean; // 返回修改后的或原始的bean\n }\n\n @Override\n public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {\n // 在Bean初始化之后修改其行为\n return bean; // 可以返回修改后的bean或者原始的bean\n }\n}\n```\n\n在这里,我们假设我们想要修改一个名为`SomeBean`的Bean的某个属性。`postProcessBeforeInitialization`方法会在`SomeBean`实例经过所有配置但尚未初始化之前被调用。\n\n### 注册 BeanPostProcessor\n\n接下来,我们需要将自定义的`BeanPostProcessor`注册到Spring上下文中。这可以通过Java配置或XML配置来完成。\n\n#### Java Config\n\n```java\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class AppConfig {\n\n @Bean\n public CustomBeanPostProcessor customBeanPostProcessor() {\n return new CustomBeanPostProcessor();\n }\n\n @Bean\n public SomeBean someBean() {\n return new SomeBean();\n }\n}\n```\n\n#### XML Config\n\n```xml\n\n\n \n \n\n```\n\n### 使用 BeanPostProcessor\n\n当Spring容器启动并创建`SomeBean`的实例时,会触发`CustomBeanPostProcessor`中的方法,允许我们在Bean的生命周期中插入定制的逻辑。无论是修改属性,还是增强功能,均会在Bean被使用之前实现。\n\n### 深思\n\n通过自定义`BeanPostProcessor`,我们拥有了强大的能力去影响Bean的创建和初始化过程,这样的设计不仅提高了模块之间的隔离性,也增强了代码的可测试性。在真实的开发中,我们不断追求代码的简洁与高效,而Locale的配置与管理,例如通过条件化Bean的初始化,正是这种追求的体现。正如生活中我们在不断调整自己的行为和态度以适应环境,`BeanPostProcessor`允许我们的应用在不断变化的需求中灵活自如地调整自身。想象一下,无论是前期的预见性调整,还是后期的适应性改造,都可以通过简单的配置与实现来完成,这不仅是框架设计的优雅,更是开发者哲学的一种Embodiment。',0,0,0,'2024-12-06 11:52:15','2024-12-27 09:44:03'),(300805,100115,200504,'哦,JVM中的对象头就像一张身份证,里头的内容可丰富了,简直可以开个对象“个人信息大会”了!\n\n1. **对象的元数据**:这部分是指向类元数据的一个指针,基本上就是告诉你这个对象属于哪个类,相当于你的名字和民族。\n\n2. **哈希码(Hash Code)**:每个对象都有一个独特的哈希码,用来在哈希表中快速查找。也就是说,可能在你最亲密的朋友面前,你也是唯一的,虽然你们背后的黑历史还是不少。\n\n3. **GC分代信息**:JVM会标记对象是属于“年轻代”、“老年代”还是“永久代”,相当于给对象投票,看它在哪个年龄段。年轻小伙儿和经历丰富的老者可不一样。\n\n4. **锁信息**(如果有的话):如果对象被锁住了,比如你在抢某个资源时可能被锁定,那这部分的内容就会出现,告诉你锁的状态和持有者,真是个“监控狂”。\n\n5. **对齐填充**:为了让内存对齐更高效,可能会占用一些额外的字节空间,就像你钱包里放了几张随意的发票。\n\n总结来说,对象头就好比是一个对象的“身份证”和“简历”,包括了它的身份、状态、背景及“社交关系”。不过,要是你看到这些信息就已经头晕目眩,那可以考虑拉上对象一起去“个人信息保护局”维护一下。',0,0,0,'2024-12-06 11:52:22','2024-12-27 09:44:03'),(300806,100006,200249,'你有没有注意到ListIterator和Iterator在功能上的不同呢?Iterator主要用于单向遍历集合,而ListIterator不仅可以双向遍历,还能在遍历过程中对列表进行修改、插入和删除操作,这是不是显著提升了它的灵活性?\n\n另外,你知道ListIterator提供了一些额外的方法,比如add()、set()和previous()吗?这些方法的存在是否让你觉得在某些情况下使用ListIterator会更方便呢?\n\n想一想,在处理列表时,单向遍历是否能满足所有需求?又或者,双向遍历与修改功能对于复杂操作来说,是否更具优势?',0,0,0,'2024-12-06 11:52:27','2024-12-27 09:44:03'),(300807,100097,200433,'在Java中,可以通过多种方式实现延迟任务(Scheduled Task)。以下是一些常用的方法:\n\n### 1. 使用`java.util.Timer`和`java.util.TimerTask`\n\n`Timer`类提供了一种简单的方式来调度任务,使用`TimerTask`来表示延迟任务。\n\n```java\nimport java.util.Timer;\nimport java.util.TimerTask;\n\npublic class DelayTaskExample {\n public static void main(String[] args) {\n Timer timer = new Timer();\n \n TimerTask task = new TimerTask() {\n @Override\n public void run() {\n System.out.println(\"延迟任务执行!\");\n }\n };\n \n // 延迟3秒后执行任务\n timer.schedule(task, 3000);\n }\n}\n```\n\n### 2. 使用`ScheduledExecutorService`\n\n`ScheduledExecutorService`是Java并发包的一部分,提供了更强大和灵活的调度功能。它可以执行延迟任务,也可以定期执行任务。\n\n```java\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\n\npublic class ScheduledTaskExample {\n public static void main(String[] args) {\n ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);\n \n Runnable task = () -> System.out.println(\"延迟任务执行!\");\n \n // 延迟3秒后执行任务\n scheduler.schedule(task, 3, TimeUnit.SECONDS);\n \n // 关闭调度器\n scheduler.shutdown();\n }\n}\n```\n\n### 3. 使用Spring框架的`@Scheduled`\n\n如果你在使用Spring框架,可以利用`@Scheduled`注解来轻松地实现调度任务:\n\n```java\nimport org.springframework.scheduling.annotation.Scheduled;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class ScheduledTask {\n \n @Scheduled(fixedDelay = 3000) // 每3秒执行一次\n public void executeTask() {\n System.out.println(\"延迟任务执行!\");\n }\n}\n```\n\n在使用Spring时,确保在主类中启用任务调度功能:\n\n```java\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.scheduling.annotation.EnableScheduling;\n\n@SpringBootApplication\n@EnableScheduling\npublic class Application {\n public static void main(String[] args) {\n SpringApplication.run(Application.class, args);\n }\n}\n```\n\n### 总结\n\n根据你的需求和环境,可以选择不同的方式来实现延迟任务。`java.util.Timer`适合简单的任务,而`ScheduledExecutorService`和Spring的`@Scheduled`提供了更高的灵活性和强大的功能。无论你选择哪个方法,都可以在Java的世界中轻松调度任务,创造出独特的功能!✨',0,0,0,'2024-12-06 11:52:36','2024-12-27 09:44:03'),(300808,100035,201054,'在处理不同平台的文件路径和编码差异时,可以借鉴以下几个原则和方法:\n\n1. **路径分隔符的统一**:不同操作系统使用不同的路径分隔符,例如Windows使用反斜杠`\\`,而Linux和macOS则使用斜杠`/`。为了让代码具备跨平台的能力,可以使用编程语言提供的库函数来处理路径。例如,在Python中,可以使用`os.path.join()`来构建路径,这样就可以自动根据运行环境选择正确的分隔符。\n\n ```python\n import os\n\n file_path = os.path.join(\'folder\', \'subfolder\', \'file.txt\')\n ```\n\n2. **相对路径与绝对路径**:在处理文件时,可以选择使用相对路径而非绝对路径,这样有助于减少路径依赖性。相对路径与工作目录相关,因此在不同的环境下调用时更具灵活性。\n\n3. **编码的一致性**:在文件读写时,编码方式的选择至关重要。许多操作系统和编程语言的默认编码可能不同。在Python中,使用UTF-8编码可以更广泛地兼容各种文字。在读取文件时,明确指定编码可以避免因编码不一致而导致的错误。\n\n ```python\n with open(\'file.txt\', \'r\', encoding=\'utf-8\') as file:\n content = file.read()\n ```\n\n4. **测试与验证**:在多平台环境中工作时,应尽量在代表性的平台上进行测试。通过创建一系列测试用例,能够验证路径处理和编码操作的可靠性,从而及时发现潜在问题。\n\n5. **使用高级库**:在很多情况下,使用专门处理文件路径和编码的库可以简化代码并提高可移植性。例如,Python的`pathlib`库提供了对象导向的路径操作方法,能够自动处理平台差异。\n\n ```python\n from pathlib import Path\n\n file_path = Path(\'folder\') / \'subfolder\' / \'file.txt\'\n ```\n\n通过以上方式,我们不仅能够有效地处理编码和路径差异,更能在复杂多变的计算环境中,建立起一套简洁、清晰且高效的文件管理策略。这不仅是技术层面的解决方案,更是一种对“适应”与“和谐”的深刻理解与实践。',0,0,0,'2024-12-06 11:52:45','2024-12-27 09:44:03'),(300809,100077,200445,'抽象工厂模式(Abstract Factory Pattern)在Java中的实现方式就像是一位优雅的导演,帮助我们将不同类型的产品生成过程解耦,让代码的结构变得更加清晰且容易扩展。下面让我们细致入微地拆解一下它是如何做到这一点的。\n\n### 1. **角色分明**\n在抽象工厂模式中,我们通常会有几个关键角色:\n\n- **抽象工厂(AbstractFactory)**:定义了创建各种产品的接口。就像是一座魔法工厂,负责生成各种产品,但不关心具体的生成过程。\n \n- **具体工厂(ConcreteFactory)**:实现了抽象工厂的接口,负责具体产品的生成。\n\n- **抽象产品(AbstractProduct)**:定义了产品的接口。\n\n- **具体产品(ConcreteProduct)**:实现了抽象产品接口的具体类,是我们最终要使用的产品。\n\n这个角色分明的结构,确保了不同的产品和工厂之间没有直接的依赖关系,使得系统更加灵活。\n\n### 2. **解耦的核心**\n解耦的关键在于,客户端代码(使用产品的地方)只依赖于抽象工厂和抽象产品,而不与具体的工厂和产品直接交互。这就像是在监听音乐会,听众只需要关注音乐本身,而不需要知道乐队成员是怎样合作的。\n\n```java\n// 抽象产品\ninterface ProductA {\n void use();\n}\n\n// 具体产品\nclass ProductA1 implements ProductA {\n public void use() {\n System.out.println(\"使用产品A1\");\n }\n}\n\n// 抽象工厂\ninterface AbstractFactory {\n ProductA createProductA();\n}\n\n// 具体工厂\nclass ConcreteFactory1 implements AbstractFactory {\n public ProductA createProductA() {\n return new ProductA1();\n }\n}\n\n// 客户端代码\npublic class Client {\n public static void main(String[] args) {\n AbstractFactory factory = new ConcreteFactory1();\n ProductA product = factory.createProductA();\n product.use();\n }\n}\n```\n\n### 3. **灵活的扩展性**\n假设未来我们需要生产另一种产品,只需添加新的具体工厂和具体产品,而不需要修改原有的客户端代码。这种扩展,比如加入`ConcreteFactory2`和`ProductA2`,非常简单,犹如在大楼旁边加一道新的窗户,结构依然坚固。\n\n### 4. **聚焦于接口而非实现**\n通过依赖于抽象的工厂以及产品,客户端降低了对具体实现的依赖,只需关注接口。这种聚焦让代码更易于测试和维护,仿佛是在操控一台遥控器,而不再需深入到电路的复杂。\n\n### 总结\n通过以上方式,抽象工厂模式在Java中的实现,成功地将产品的创建和使用分离,创造了一个松散耦合的环境。我们不仅能够更轻松地控制对象的创建过程,还能在不影响现有代码的情况下,添加新的产品族。而这,正是软件设计的精髓所在!',0,0,0,'2024-12-06 11:52:52','2024-12-27 09:44:03'),(300810,100055,201003,'你知道吗,C++中的模板推导机制其实是为了让你在使用模板时,不必每次都明确指定类型,这样是不是很方便呢?让我们来看看,当你定义一个模板函数时,编译器是如何根据调用时传入的参数类型来推导出模板参数的。\n\n首先,模板参数可以是类型参数,也可以是非类型参数。你是不是想了解编译器在推导过程中会考虑哪些方面呢?比如,编译器会优先考虑函数参数的类型,简单来说,调用模板时的实参类型会与模板定义中的形参类型一一对应。\n\n还有,你认为在模板推导中,有没有什么特殊的情况需要特别注意呢?例如,当你在调用模板时传入多种类型参数时,编译器能否合理地进行推导?而且,有些情况下,比如模板参数为引用类型或常量,推导的规则就会有所不同,这样一来,你是不是觉得编写更加复杂了呢?\n\n总之,掌握了模板推导的机制,能够让你在使用C++时更加灵活。你准备好在实际编码中运用这些技巧了吗?',0,0,0,'2024-12-06 11:52:57','2024-12-27 09:44:03'),(300811,100095,200425,'当然可以!让我们用一种有趣且生动的方式来解释一下 Java 中的内存溢出(OutOfMemoryError)和内存泄漏(Memory Leak)。\n\n### 内存溢出(OutOfMemoryError)\n\n想象一下你的厨房,里面有一个大冰箱,你的家人总是喜欢往冰箱里塞东西。起初,它装得下所有的食材,甚至有一些额外的空间。但随着时间推移,你不断往里面添加食物,冰箱开始变得拥挤,最终再也装不下新的食材。\n\n在 Java 中,内存溢出就像这个拥挤的冰箱。当 Java 虚拟机(JVM)中的堆内存满了,无法再分配更多的内存时,就会抛出 `OutOfMemoryError`。这通常是由于应用程序消耗了过多的内存,可能是因为创建了大量对象、无限循环、或是资源没有得到合理管理。此时,程序就像那个满满的冰箱,无法再承载新的食物。\n\n### 内存泄漏(Memory Leak)\n\n而内存泄漏就像厨房里的一些原材料被放在角落,忘记被使用。你明明知道这些食材本来可以用来做美味的饭菜,但它们却在角落里待着,既占空间又无法被使用。\n\n在编程中,内存泄漏是一种情况,程序不再需要某些对象,但由于某些原因(比如仍然存在对它们的引用),这些对象没有被垃圾回收(GC)回收。就如同那些被遗忘的食材,它们依旧占用着内存,导致可用内存逐渐减少,最终可能导致内存溢出。\n\n### 小结\n\n- **内存溢出(OOM)**:像冰箱装得满满的,无法再添加任何食物,抛出 `OutOfMemoryError`。\n- **内存泄漏**:就像厨房里的食材被遗忘,虽然不再需要,但它们依旧占用原本可以被其他食材使用的空间。\n\n因此,良好的内存管理就像管理你的厨房,确保每样东西都有它的位置,定期清理不再需要的东西,从而保持厨房的整洁和高效运转!',0,0,0,'2024-12-06 11:53:04','2024-12-27 09:44:03'),(300812,100030,200850,'当然可以!表达式模板(Expression Templates)是C++的一种高级编程技术,主要用于优化数学表达式的计算。它的核心思想是延迟计算,利用模板和操作符重载来实现高效的表达式构建和求值。\n\n### 背景\n\n在一些数值计算、线性代数等领域,处理大量的数学运算是常见的需求。传统上,直接计算可能会导致许多中间结果的产生,从而浪费内存和计算资源。表达式模板的出现,就是为了应对这种情况,通过将表达式的构建与求值过程结合起来,避免不必要的中间数据存储。\n\n### 基本原理\n\n表达式模板利用C++的模板特性和运算符重载,将表达式表示为一种树结构。这种结构能够在实际进行求值之前,将整个表达式的计算计划(或计算顺序)构建出来,从而实现更优的代码生成与执行。\n\n#### 例子\n\n假设你有一个简单的矩阵类,并且想要支持矩阵的相加操作。通常情况下,你可能会直接实现一个 `add` 函数,如下所示:\n\n```cpp\nMatrix operator+(const Matrix& lhs, const Matrix& rhs) {\n // ... 实际的加法实现\n}\n```\n\n这样做的问题在于每次进行加法时都可能产生一个新的矩阵,对内存的使用和计算效率都有一定影响。\n\n而使用表达式模板的思路,你可以定义一个表达式类型:\n\n```cpp\ntemplate\nclass Expression {\n // 表示一个通用的数学表达式\n};\n\ntemplate\nclass Matrix {\npublic:\n Expression operator+(const Expression& rhs) const {\n return AddExpression(this, &rhs);\n }\n // ...\n};\n```\n\n当你写这样的代码时:\n\n```cpp\nMatrix A, B, C;\nC = A + B; // 这里不会立即计算\n```\n\n实际的计算可以在最后需要结果的时候再进行,利用编译器的优化能力,减少了不必要的内存分配。\n\n### 用途\n\n1. **内存效率**:减少中间结果的生成,降低内存开销。\n2. **性能提升**:通过减少不必要的计算,提高运行速度。\n3. **代码的可读性与维护性**:表达式模板允许我们使用类似数学的语法,使得代码更加直观。\n4. **灵活性**:可以轻松扩展以支持更多复杂运算,比如点积、矩阵和向量的运算等。\n\n### 总结\n\n表达式模板是一个强大而灵活的工具,特别适合需要大量数学运算的场景。虽然学习与实现可能需要一些时间和精力,但一旦掌握,你将会在出来的程序性能和可读性上获得显著的提升。继续加油,相信你能够对此有更深入的理解和应用!',0,0,0,'2024-12-06 11:53:12','2024-12-27 09:44:03'),(300813,100099,200514,'锁膨胀听起来像是某种超市促销活动,实际上它在JVM(Java虚拟机)中表示的是一种锁的优化过程。要想理解这个神奇的概念,我们首先需要了解Java中的锁是怎么工作的。\n\n在JVM中,当多个线程尝试访问同一个共享资源时,线程需要通过锁来进行同步,避免数据的不一致性。Java使用的锁有不同的层次,从轻量级锁到重量级锁,锁膨胀主要是指从轻量级锁升级为重量级锁的过程。\n\n### 锁的层次:\n1. **无锁**:不需要任何锁的状态,线程可以自由访问资源。\n2. **轻量级锁**:采用自旋锁机制,不会立即阻塞线程,而是在内部尝试获取锁,适用于多线程竞争不激烈的场景。\n3. **重量级锁**:当轻量级锁竞争激烈时,JVM会将其升级为重量级锁,此时会导致线程进入阻塞状态,资源的开销也会随之增加。\n\n### 为什么需要锁膨胀?\n1. **性能优化**:在多线程环境中,如果读多写少,竞争比较少,轻量级锁能大幅度提升性能。而当确实需要同步时,锁膨胀能保证数据的一致性。\n2. **避免过早阻塞**:轻量级锁在低竞争情况下表现良好,只有在真正需要阻塞的时候才进行膨胀,避免了不必要的性能损失。\n3. **应对高竞争情况**:当锁竞争变得激烈后,简单的轻量级锁已经无法满足需求,锁膨胀可以帮助我们提升锁的能力,保证程序正常运行。\n\n总的来说,锁膨胀是在锁的管理上进行的一种智慧的决策,目的是为了在保持高效执行的同时,也能应对竞争的挑战。这样一来,开发者就可以安心编写代码,而不必担心在锁的世界里迷失方向!',0,0,0,'2024-12-06 11:53:22','2024-12-27 09:44:03'),(300814,100101,200941,'在C++11中,引入了对多线程编程的强大支持,其中`thread`、`mutex`和`lock_guard`是三种重要的组件,它们共同构成了并行编程的基础。\n\n### thread\n`std::thread`是C++11为多线程提供的基础类。它允许程序通过创建新的线程来并发执行任务。每个`std::thread`对象代表一个线程,线程可以通过调用类的构造函数传入一个可调用对象(如函数或Lambda表达式)。\n\n**使用示例:**\n```cpp\n#include \n#include \n\nvoid printHello() {\n std::cout << \"Hello from thread!\" << std::endl;\n}\n\nint main() {\n std::thread t(printHello); // 创建新线程\n t.join(); // 等待线程完成\n return 0;\n}\n```\n\n### mutex\n`std::mutex`是互斥锁(mutex)的实现,主要用于保护共享数据,保证同一时间只有一个线程能够访问某个资源。通过加锁和解锁,`std::mutex`提供了一种安全的方式来避免数据竞争。\n\n**使用示例:**\n```cpp\n#include \n#include \n#include \n\nstd::mutex mtx;\n\nvoid printEven(int id) {\n mtx.lock();\n std::cout << \"Even ID: \" << id << std::endl;\n mtx.unlock();\n}\n\nvoid printOdd(int id) {\n mtx.lock();\n std::cout << \"Odd ID: \" << id << std::endl;\n mtx.unlock();\n}\n\nint main() {\n std::thread t1(printEven, 2);\n std::thread t2(printOdd, 1);\n t1.join();\n t2.join();\n return 0;\n}\n```\n\n### lock_guard\n`std::lock_guard`是一个RAII风格的封装器,用于管理`std::mutex`的生命周期。它在构造时自动锁定互斥锁,在析构时自动释放锁,因此它能有效地避免因异常或早退出导致的死锁问题。使用`lock_guard`可以使代码更简洁且安全。\n\n**使用示例:**\n```cpp\n#include \n#include \n#include \n\nstd::mutex mtx;\n\nvoid print(int id) {\n std::lock_guard guard(mtx); // 自动锁定\n std::cout << \"Thread \" << id << \" is running.\" << std::endl;\n // 析构时自动释放锁\n}\n\nint main() {\n std::thread t1(print, 1);\n std::thread t2(print, 2);\n t1.join();\n t2.join();\n return 0;\n}\n```\n\n### 总结\n通过`std::thread`可以轻松启动多个线程,`std::mutex`提供了保护共享资源的机制,而`std::lock_guard`则确保锁定和解锁的简洁与安全。这些工具代表了C++11对并行编程的支持,更进一步引导我们思考在复杂的并发环境中,如何设计出既高效又安全的程序结构。正是通过这些简单而精确的工具,我们能够在程序中增添动态的生命力,开辟出更为广阔的思维维度与实现可能性。',0,0,0,'2024-12-06 11:53:39','2024-12-27 09:44:03'),(300815,100062,200016,'当然可以,拥塞控制就像是高速公路上的交通警察,负责确保车流畅通,不让交通堵塞,让司机们有个愉快的驾驶体验。下面我用几种常见的方法来给你讲讲拥塞控制是怎么实现的:\n\n1. **慢启动(Slow Start)**:想象一下,一个孩子在学骑自行车。他一开始可能会非常小心,只敢蹬几下,随着骑得越来越稳,蹬得也越来越快。这就是慢启动的意义,初始时发送少量数据包,随着网络的承载能力增加,再逐步增加数据包的数量。\n\n2. **拥塞避免(Congestion Avoidance)**:当那个孩子渐渐熟练了,他开始考虑如何避免摔倒。这个阶段,发送的数据包就会增加得比较缓慢,以免突然速度过快导致网络“摔倒”。通常通过控制窗口大小来保持数据流量的平稳。\n\n3. **快重传和快恢复(Fast Retransmit and Fast Recovery)**:如果他摔倒了,怎么办?当然是迅速爬起来继续骑行!在网络中,快重传会迅速重新发送丢失的数据包,而快恢复则是利用已经成功接收的数据包,迅速调整数据的发送率,避免再次发生拥塞。\n\n4. **拥塞通知(Explicit Congestion Notification, ECN)**:想象一下,这孩子因为看到前方有障碍物而提前减速。网络中的拥塞通知机制会让发送方和接收方知道当前的网络状况,从而灵活调整发送速率,避免了拥堵。\n\n所以,拥塞控制就像是个交通管制员,既要保证数据流量畅通,又要灵活应对可能的瓶颈,高效地让信息在网络中“自由驰骋”。所以,下次当网络出问题时,记得把责任推给拥塞控制哦!',0,0,0,'2024-12-06 11:53:53','2024-12-27 09:44:03'),(300816,100054,200203,'在Java中,自定义异常就像为你的代码创造了一把独特的钥匙,可以帮助你更好地处理特定的错误情况。自定义异常的作用是能够精确描述程序中出现的特定错误,并提供更多的上下文信息,让你能更轻松地处理异常情况。\n\n### 如何自定义异常\n\n创建自定义异常主要有两个步骤:\n\n1. **定义异常类**:你需要创建一个新的类,这个类要扩展Java的`Exception`类(或`RuntimeException`,根据你的需要)。通常,构造函数会接收一个错误消息和可选的原因。\n\n2. **使用这个异常**:在你的代码中,适当的位置抛出这个自定义异常。\n\n下面是一个具体的示例:\n\n```java\n// 第一步:定义自定义异常类\npublic class MyCustomException extends Exception {\n // 无参构造函数\n public MyCustomException() {\n super(\"发生了自定义异常\");\n }\n\n // 带参构造函数\n public MyCustomException(String message) {\n super(message);\n }\n}\n\n// 第二步:在代码中使用这个自定义异常\npublic class TestCustomException {\n public static void main(String[] args) {\n try {\n checkValue(0); // 这里故意传入一个无效值\n } catch (MyCustomException e) {\n System.out.println(\"捕获到异常: \" + e.getMessage());\n }\n }\n\n public static void checkValue(int value) throws MyCustomException {\n if (value <= 0) {\n throw new MyCustomException(\"值必须大于零\"); // 抛出自定义异常\n }\n System.out.println(\"值是: \" + value);\n }\n}\n```\n\n### 自定义异常的作用\n\n1. **增强可读性**:通过使用清晰的异常名称,代码的可读性大大提高,其他开发者能快速理解可能发生的错误。\n\n2. **精细化控制**:你可以根据业务需求添加特定的错误处理逻辑,能够优雅地处理不同的异常情况。\n\n3. **提供丰富的上下文**:自定义异常可以携带更多信息,比如错误代码、上下文数据等,让调试和日志记录更加有效。\n\n4. **分离关注点**:通过自定义异常,你能够把正常业务逻辑与异常处理逻辑分离,让代码整体更清晰。\n\n总之,自定义异常在Java中是一个强大的工具,使得代码能够更加健壮及易于维护。就像一位优秀的卫兵,能够在关键时刻做出准确的反应,保障代码的稳定运行。',0,0,0,'2024-12-06 11:54:04','2024-12-27 09:44:03'),(300817,100105,200711,'线程池就像一个精心调度的工厂车间,每个线程都是一位工人。当任务像潮水般涌来时,线程池能够高效地管理这些工人,以便在确保质量的同时,提升生产效率。要让这个车间运转得更流畅,我们需要合理配置一些常用参数:\n\n1. **核心线程数(corePoolSize)**:\n - 这是线程池中始终保持活跃的最低线程数量。想象一下,这是工厂的基本班底,不论任务量如何,他们都在默默工作。\n\n2. **最大线程数(maximumPoolSize)**:\n - 这是线程池能够容纳的最大线程数量。当任务量激增时,工厂可以及时增加工人数量,从而避免任何延误。\n\n3. **线程存活时间(keepAliveTime)**:\n - 这是指当线程池中的线程超过核心线程数时,闲置的线程在多长时间内会被释放。过长的存活时间像是让工人无所事事,造成资源浪费;过短则可能导致过于频繁的招聘和解雇。\n\n4. **任务队列(workQueue)**:\n - 这是用于存放待处理任务的地方,类似于工厂的输送带,有多种类型可供选择,比如有界队列、无界队列等。选择合适的队列可以帮助平衡系统负载。\n\n5. **线程工厂(threadFactory)**:\n - 负责创建新线程的工厂,可以设定线程的命名、优先级等特性。这个参数关乎工人本身的素质和技能。\n\n6. **拒绝策略(handler)**:\n - 当线程池无法处理新任务时,采用的策略。这就像工厂遇到过载时的应对方案,可以选择丢弃任务、抛出异常、或者将任务另行转移到其他工厂。\n\n### 如何合理配置线程池以提高系统性能?\n\n- **分析任务特性**:\n - 理解任务的性质是关键。例如,如果任务是CPU密集型,核心线程数可以设定为CPU核心数的数目;如果是IO密集型任务,可以适当增加核心线程数,以利用等待时间。\n\n- **考虑系统资源**:\n - 评估服务器的硬件资源,包括CPU、内存和IO带宽。确保线程池的配置不会造成资源的过度争用。\n\n- **动态调整**:\n - 根据实际运行情况监控和调整线程池参数。有些框架支持动态调整线程池,可以根据实时负载调整核心线程数和最大线程数。\n\n- **测试与优化**:\n - 通过压测和负载测试,观察不同配置下的系统表现,找到最佳的设置。例如,可以记录响应时间、资源利用率、任务完成率等指标。\n\n- **合理使用任务队列**:\n - 根据任务的特性选择合适的队列类型,界定队列的最大长度,以避免因过多待处理任务导致的响应延迟。\n\n通过合理配置这些参数,线程池可以如同一台高效运转的机器,不仅能处理大量任务,还能保持系统的平稳与响应灵敏,最终提升整体性能与用户体验。',0,0,0,'2024-12-06 11:54:14','2024-12-27 09:44:03'),(300818,100089,200607,'你是否考虑过使用Spring Security来增强Spring Boot应用程序的安全性呢?Spring Security提供了一整套功能来保护你的应用,比如认证和授权。你是否想过如何配置用户身份验证,或者设置访问控制规则呢?\n\n是不是觉得添加HTTPS支持很重要呢?这能够加密传输的数据,确保用户的信息安全。你有没有考虑使用JWT(JSON Web Tokens)进行无状态的认证呢?这样能否简化你的认证流程?\n\n另外,想过如何防止常见的安全威胁,例如CSRF(跨站请求伪造)和XSS(跨站脚本攻击)吗?在Spring Security中是否有相应的配置可以帮助你抵御这些攻击呢?\n\n最后,你有没有思考过使用角色和权限来细化访问控制,从而提升应用的安全策略呢?这些安全措施的组合怎么才能最有效地保护你的应用程序呢?',0,0,0,'2024-12-06 11:54:20','2024-12-27 09:44:03'),(300819,100031,201109,'在C++中使用sockets进行UDP通信是一项基本的网络编程技能,尤其是在处理广播和组播时,其意义更加深远。通过这种方式,设备之间能够进行高效的信息交流,形成了一种动态的、实时的交互方式。下面我将引导你了解如何实现这一过程。\n\n### 一、基本的UDP通信\n\n在使用UDP(User Datagram Protocol)之前,我们需要了解其特性:无连接、不可靠、面向报文。与TCP相比,UDP缺少建立连接和确认送达的步骤,因此它在一些对延迟敏感的应用中非常适用。\n\n#### 1. 创建UDP Socket\n\n首先,你需要创建一个socket。以下是C++中创建UDP socket的示例:\n\n```cpp\n#include \n#include \n#include // for sockaddr_in, inet_addr\n#include // for socket\n#include // for close\n\n#define PORT 8080\n#define BUF_SIZE 1024\n\nint main() {\n int sockfd;\n struct sockaddr_in servaddr, cliaddr;\n\n // 创建socket\n sockfd = socket(AF_INET, SOCK_DGRAM, 0);\n if (sockfd < 0) {\n std::cerr << \"Socket creation failed!\" << std::endl;\n return -1;\n }\n\n // 填充服务器信息\n memset(&servaddr, 0, sizeof(servaddr));\n servaddr.sin_family = AF_INET;\n servaddr.sin_addr.s_addr = INADDR_ANY; // 接受任何发送到该机器上的数据\n servaddr.sin_port = htons(PORT);\n\n // 绑定\n if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {\n std::cerr << \"Bind failed!\" << std::endl;\n return -1;\n }\n\n // ... 这里可以进行数据接收或发送\n}\n```\n\n### 二、处理广播\n\n广播是一种将数据发送给同一网络上所有主机的技术。为了实现UDP广播,可以按照以下步骤操作:\n\n#### 1. 设置Socket选项\n\n在代码中,我们设置`SO_BROADCAST`选项,以允许发送广播数据包:\n\n```cpp\nint broadcastEnable = 1;\nsetsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable));\n```\n\n#### 2. 发送广播消息\n\n```cpp\nstruct sockaddr_in broadcastAddr;\nbroadcastAddr.sin_family = AF_INET;\nbroadcastAddr.sin_port = htons(PORT);\nbroadcastAddr.sin_addr.s_addr = inet_addr(\"255.255.255.255\"); // 广播地址\n\nconst char *message = \"Hello, UDP Broadcast!\";\nsendto(sockfd, message, strlen(message), 0, (const struct sockaddr *)&broadcastAddr, sizeof(broadcastAddr));\n```\n\n### 三、处理组播\n\n组播则是让特定的一组主机接收数据。我们需要加入一个组播组并发送数据。\n\n#### 1. 加入组播组\n\n首先,我们创建一个组播socket,并使用`ip_mreq`结构加入组播组:\n\n```cpp\nip_mreq mreq;\nmreq.imr_multiaddr.s_addr = inet_addr(\"239.255.255.250\"); // 组播地址\nmreq.imr_interface.s_addr = htonl(INADDR_ANY); // 本地接口\n\nsetsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq));\n```\n\n#### 2. 发送组播消息\n\n组播发送也是非常简单的:\n\n```cpp\nstruct sockaddr_in multicastAddr;\nmulticastAddr.sin_family = AF_INET;\nmulticastAddr.sin_port = htons(PORT);\nmulticastAddr.sin_addr.s_addr = inet_addr(\"239.255.255.250\"); // 组播地址\n\nconst char *message = \"Hello, UDP Multicast!\";\nsendto(sockfd, message, strlen(message), 0, (const struct sockaddr *)&multicastAddr, sizeof(multicastAddr));\n```\n\n### 四、接收数据\n\n接收发送到广播或组播的消息与常规的UDP接收几乎相同,只需在绑定之后使用`recvfrom`函数即可获取数据。\n\n```cpp\nchar buffer[BUF_SIZE];\nsocklen_t len = sizeof(cliaddr);\nint n = recvfrom(sockfd, buffer, BUF_SIZE, 0, (struct sockaddr *)&cliaddr, &len);\nbuffer[n] = \'\\0\'; // 添加字符串结束符\nstd::cout << \"Received: \" << buffer << std::endl;\n```\n\n### 总结\n\n这样,我们就简要介绍了如何在C++中使用UDP套接字进行通信,涉及到广播和组播的实现。在这个快速发展的时代,网络通信不断地塑造着我们生活的方式,而这些技术背后的哲理则在于——将信息高效而及时地传递给需要它的人。这不仅是技术的展示,更是沟通的艺术。希望你能在探索中找到这一领域的乐趣与价值。',0,0,0,'2024-12-06 11:54:32','2024-12-27 09:44:03'),(300820,100039,200864,'哦,迭代器在STL(标准模板库)中可是个超级明星!想象一下,迭代器就像一名优秀的导游,带你在容器的无尽世界中游览。无论你是在哈希表里探险,还是在向量的海洋中畅游,迭代器都可以帮你从一个元素移步到另一个元素,轻松自如。\n\n具体来说,迭代器的角色主要有几个:\n\n1. **访问元素**:就像用手指戳着书页的边缘,迭代器可以让我们一个个地访问容器中的元素,而不需要知道它们到底藏在什么地方。\n\n2. **抽象化容器**:通过迭代器,我们可以不关心容器的具体实现,比如是数组、链表还是其他——只需使用一致的方式访问,它们的保密工作做得相当到位。\n\n3. **支持算法**:STL里的算法(例如排序、查找等)都依赖于迭代器。这就好比,你要去参加一场派对,迭代器就是你的邀请函,凭着它,你就能顺利进入派对,尽情享受!\n\n4. **统一操作接口**:不同类型的容器(如向量、列表、集合等)都有自己的迭代器,这就像不同口味的冰淇淋,每一种都有自己独特的魅力,但使用的勺子都是同一种。\n\n所以,简而言之,迭代器就像把STL的各大明星连接起来的大厨,掌控着一切食材的流动,让你的代码变得更加灵活便捷。要说迭代器的重要性,那简直就是“没有它,你就像是失去了所有的配料,根本做不出好菜”!',0,0,0,'2024-12-06 11:54:40','2024-12-27 09:44:03'),(300821,100070,201044,'在C++中使用动态链接库(DLL)是一个非常实用的技巧,它可以帮助你将程序的不同部分分开,以便于维护和重用。让我们一起通过一个生动的例子来了解如何使用DLL吧!\n\n### 第一步:创建DLL\n\n首先,我们需要创建一个简单的DLL。让我们想象一下,我们想要制作一个可以提供数学运算的DLL。\n\n1. **创建一个新的项目**:在你的IDE(如Visual Studio)中,选择创建一个新的“动态链接库项目”。\n\n2. **编写DLL代码**:\n ```cpp\n // MathLibrary.cpp\n #include \"MathLibrary.h\"\n\n // 导出函数\n extern \"C\" __declspec(dllexport) int Add(int a, int b) {\n return a + b;\n }\n\n extern \"C\" __declspec(dllexport) int Subtract(int a, int b) {\n return a - b;\n }\n ```\n\n3. **创建头文件**:\n ```cpp\n // MathLibrary.h\n #pragma once\n\n extern \"C\" __declspec(dllexport) int Add(int a, int b);\n extern \"C\" __declspec(dllexport) int Subtract(int a, int b);\n ```\n\n4. **编译DLL**:构建你的DLL项目,IDE会生成一个`.dll`文件,通常和`*.lib`文件一起生成,这个`.lib`文件是链接时所需要的。\n\n### 第二步:在应用程序中使用DLL\n\n接下来,我们将创建一个新的控制台应用程序,来使用我们刚刚创建的DLL。\n\n1. **创建一个新的控制台项目**。\n\n2. **引用DLL和头文件**:\n - 将DLL和LIB文件复制到控制台项目的工作目录,或者在项目属性中设置DLL的位置。\n - 包含DLL的头文件。\n ```cpp\n // main.cpp\n #include \n #include \"MathLibrary.h\" // 记得包含我们刚才创建的头文件\n\n int main() {\n int sum = Add(5, 3);\n int difference = Subtract(5, 3);\n\n std::cout << \"5 + 3 = \" << sum << std::endl;\n std::cout << \"5 - 3 = \" << difference << std::endl;\n\n return 0;\n }\n ```\n\n3. **链接DLL**:\n - 在项目的链接器设置中添加生成的`.lib`文件。这告诉编译器在链接时查找有关DLL的定义。\n\n4. **运行程序**:\n - 运行你的控制台应用程序,你将看到输出:\n ```\n 5 + 3 = 8\n 5 - 3 = 2\n ```\n\n### 结尾\n\n随着这简单的步骤,你已经成功创建并使用了一个动态链接库(DLL)。这个DLL就像是一个魔法宝盒,里面装着你需要的功能,而你的主程序只需打开它,取出需要的工具便能完成各式各样的工作。通过使用DLL,你的程序将更加模块化,而维护与更新会变得简单许多!',0,0,0,'2024-12-06 11:54:50','2024-12-27 09:44:03'),(300822,100076,200889,'在使用STL(标准模板库)的过程中,我面临的最大挑战之一是对各种容器和算法的熟悉程度。虽然STL提供了强大的数据结构和算法,但是面对多样化的选择,我时常会感到一种选择的困惑。\n\n例如,当我想要存储一组有序的数据时,面对`std::vector`、`std::list`和`std::set`时,我需要仔细权衡它们各自的特点和适用场景。`std::vector`在随机访问时表现优异,但在插入和删除操作上却显得力不从心;而`std::set`则提供了方便的自动排序功能,但由于底层实现的原因,性能上无法与`std::vector`抗衡。如何在这些容器中选择出最适合的那一个,真是一门艺术。\n\n另外,STL中的算法也十分丰富。初次接触时,面对如`std::sort`、`std::copy`、`std::transform`等函数,我常常一头雾 water,不知道何时该使用哪个,甚至在参数传递时可能掉入类型不匹配的陷阱中。记得有一次,我试图用`std::sort`排序一个复杂对象的数组,结果由于忘记提供比较函数,程序愉快地告诉我比较操作不合法,那一刻我恨不得找个地洞钻进去。\n\n不过,克服了这些挑战后,我对STL的使用越来越得心应手。无论是在存储、操作数据,还是使用算法进行处理,最终我都领悟到:STL背后并不是简单的容器和算法,而是一整套高效和优雅的思维方式,能够大大简化编程的难度,让我的代码更具表现力和可读性。',0,0,0,'2024-12-06 11:54:56','2024-12-27 09:44:03'),(300823,100080,201018,'静态库和动态库就像两位在编程世界中各自有着独特魅力的角色,他们为程序的构建和运行提供了不同的方式和特点。\n\n**静态库**,仿佛是一个强壮的战士。他在编译时与程序代码紧密结合,形成一个独立的可执行文件。这意味着,当你运行程序时,静态库的所有代码都已经被“铸造”进了这个程序中,像一座坚不可摧的城堡,所有需要的资源都在房子里,不必依赖外界。这样的优势在于你不必担心运行时缺少库文件,然而缺点是如果静态库需要更新,你必须重新编译整个程序,就像旧装修的房子要大动干戈才能焕然一新。\n\n**动态库**,则更像是一位灵活的舞者,它在运行时被加载。动态库的文件(比如 Windows 的 DLL 文件或者 Linux 的 .so 文件)是独立的,程序和库之间通过链接在运行时建立连接。这就像你在聚会上需要音乐时,舞者迅速从一旁的音响里播放自己最爱的旋律。动态库的优势在于可以实现代码共享与模块化,比如多个程序可以同时使用同一个动态库,减少了内存的消耗。缺点则是运行时可能会遇到“缺失”的问题,如果某个程序需要的动态库没在系统中,就会出错,像是舞会没了音乐,气氛就变得尴尬。\n\n总结来说,静态库在编译时就嵌入到程序里,形成完整的独立产品,而动态库则在运行时加载,灵活多变,但也需要额外的关注和管理。选择哪种库,往往取决于你的项目需求和对灵活性的偏好。',0,0,0,'2024-12-06 11:55:04','2024-12-27 09:44:03'),(300824,100106,200022,'内容分发网络(CDN)是一种通过分布在不同地理位置的服务器网络来优化互联网内容传输的技术。其核心目的是提高用户访问速度、增强内容可用性和可靠性,并减少源服务器的负担。\n\n从哲理的角度来看,CDN体现了“分散与聚合”的辩证关系。互联网内容的庞大和复杂性需要一种有效的方式来进行管理和传递。CDN通过将内容复制并存储在离用户更近的节点上,从而减少数据传输的距离和延迟,简化了信息获取的过程。这使得更广泛的信息能够在瞬息万变的网络环境中迅速到达每一个角落,仿佛信息的流动被赋予了新的生命。\n\n在网络传输中,CDN的作用主要体现在以下几个方面:\n\n1. **速度提升**:通过将内容缓存于接近用户的服务器,CDN显著提高了加载速度,改善了用户体验。\n\n2. **负载均衡**:分散的服务器可以共同承担高流量请求的压力,避免单一服务器的拥堵和故障,从而提高系统的整体稳定性。\n\n3. **安全性增强**:CDN通常集成了安全防护机制,可以防止DDoS攻击等网络威胁,保护用户和内容提供者的利益。\n\n4. **可靠性与容错性**:即使某个节点出现故障,CDN可以通过其他节点继续提供服务,确保内容的持续可用性。\n\n5. **全球覆盖**:CDN可以有效服务全球用户,使内容在世界各地可以以尽可能一致的速度被访问,从而打破地理限制。\n\n因此,CDN不仅是一种技术手段,更是一种深刻的思维方式,它促使我们在信息传递的过程中思考如何更为高效、平衡和智能地使用资源。正如阳光照射在每一个角落,CDN致力于让信息的光芒完美传递,照亮每一个渴求知识与连接的心灵。',0,0,0,'2024-12-06 11:55:11','2024-12-27 09:44:03'),(300825,100035,200825,'移动语义是什么呢?简单来说,就是让计算机理解和处理移动设备上的内容和上下文。想象一下,一个手机APP就像一位贴心的侍者,能在你最需要的时候给你推送最有用的信息。\n\n至于原理,可以说是由多个技术组合而成的“黑科技”。它通常依赖于机器学习、自然语言处理、位置服务等技术。就像你在星巴克点一杯冲击感官的拿铁,语义分析会根据你的位置和历史活动,推测你可能会需要什么——当然,只要它不把你当成一个只喝美式的“家伙”就好。\n\n移动语义的作用嘛,首先是提供个性化服务,例如在购物APP里根据你的购买习惯推送商品,省时省力。再者,它也可以让用户在不同设备和平台之间无缝切换,提高效率——就像是给你一个万能的“连接器”,让你不再因为设备不同而烦恼。\n\n总的来说,移动语义就是科技界的“快递员”,帮助你快速找到所需的信息,当然偶尔也会送错快递,让你收到个“连裤袜”而不是你期待的最新游戏。这就是它的魅力所在!',0,0,0,'2024-12-06 11:55:17','2024-12-27 09:44:03'),(300826,100035,201225,'虚函数表,听起来像是某种高科技的菜单,其实它是一种在面向对象编程中用于支持多态性的机制。你可以把它想象成一个餐厅的点餐系统,里面有不同的菜肴(虚函数),每个菜肴可以根据不同的厨师(对象)做出不同的风味。\n\n每当你有一个基类和一个或多个派生类时,基类会有一个虚函数表(vtable),里面存储着所有虚函数的地址。当你通过基类指针调用一个虚函数时,程序会查找这个表并找到实际应该调用的派生类的实现,就像服务员拿出菜单确定你要吃的菜肴一样。\n\n总之,虚函数表就像是程序的“厨师推荐”,让你在需要的时候,以最合适的方式享受到丰富的多态盛宴!只不过,没有胃疼的风险,毕竟它不会让你吃坏肚子,只会让你的代码变得更加灵活! 🍽️😄',0,0,0,'2024-12-06 11:55:23','2024-12-27 09:44:03'),(300827,100033,200532,'你有没有想过,为什么在一个复杂的应用中,有时候需要将一些横切关注点(像日志记录、安全控制、事务管理等)与核心业务逻辑分离开来?Spring AOP(面向切面编程)正是为了解决这个问题而存在的。\n\n它的主要目标是在不改变现有代码结构的情况下,能够动态地插入这些横切关注点。这是不是就让你觉得,应用程序的维护性和可重用性大大增强了呢?通过AOP,可以集中管理这些跨越多个模块的功能,减少代码重复,同时也让业务逻辑更加清晰。\n\n你觉得,除了提升模块化程度,Spring AOP是否还有其他的优点呢?比如说,降低了项目耦合度,增强了系统的灵活性,你认同吗?',0,0,0,'2024-12-06 11:55:27','2024-12-27 09:44:03'),(300828,100005,200139,'哦,CPU中的寄存器就像是厨房里的各种调料瓶,各有各的功能,让你的“运算大餐”更加美味可口!下面来看看这些寄存器们的“调料”名称和它们的作用吧:\n\n1. **通用寄存器(GPR)**:就像厨房里常用的盐和胡椒,随时待命、各司其职。它们用来存放数据和地址,CPU可以用它们来放置临时数据,增减算术运算的结果。\n\n2. **指令寄存器(IR)**:这是个专心的厨师,只负责读取当前正在执行的指令。当CPU要执行一段代码时,它就是‘指令的搬运工’,负责将那些指令送到CPU的其它部分。\n\n3. **程序计数器(PC)**:这是个追踪者,它就像个运动员,时刻告诉CPU下一步该去哪个“跑道”执行下一条指令。它指向下一条指令的地址,确保不迷路!\n\n4. **堆栈指针寄存器(SP)**:想象成一个小帮手,专门管理厨房里的“高压锅”。在程序执行时它跟踪堆栈的顶部位置,帮助处理函数调用和返回。\n\n5. **基址寄存器(BP)**:这是个定位小能手,帮助CPU找到调用过程中局部变量的位置,确保你不会在同一个菜肴上加两次蒜。\n\n6. **标志寄存器(FR)或状态寄存器**:这就像厨师的情绪指标,记录算术运算后的状态,告诉CPU是加溢出、零结果还是负数等等,决定接下来的“烹饪策略”。\n\n7. **浮点寄存器**:这是个高档调料瓶,专门存放浮点数(小数)。进行复杂的数学运算时,它必须要在哦上,不然就会成麻辣鸡翅了。\n\n每个寄存器都有自己的“调味习惯”,让CPU在进行各种运算时更加高效。当这些寄存器结合在一起时,简直就成了一道“运算大餐”的完美调味!希望你的计算厨房一切顺利!🍳💻',0,0,0,'2024-12-06 11:55:37','2024-12-27 09:44:03'),(300829,100097,201028,'在C++中,内联函数(inline function)是一个建议,告知编译器将函数的调用替换为函数体的代码。这通常可以提高性能,尤其是在频繁调用的小函数中。然而,内联展开的控制并非总是精确和明确的。以下是一些控制内联展开的方法和技巧:\n\n1. **`inline` 关键字**:使用 `inline` 关键字告诉编译器你希望将该函数内联。当编译器看到这个关键字时,它会尽可能地尝试将函数内联展开。然而,编译器仍然可以选择忽略这一建议。\n\n ```cpp\n inline int add(int a, int b) {\n return a + b;\n }\n ```\n\n2. **链接性(Linkage)**:内联函数需要在多个翻译单元中保持一致,因此通常在头文件中定义。确保你在使用内联函数的地方包括了其声明。\n\n3. **函数大小**:一般来说,较小的函数更容易被内联展开。编译器可能会根据函数的大小和复杂性来决定是否执行内联。一个复杂或体积较大的函数即使被标记为 `inline`,也可能被编译器选择不内联。\n\n4. **编译器优化选项**:编译器的优化设置可能影响内联的展开。例如,使用 `-O2` 或 `-O3` 等编译器选项会开启不同级别的优化,从而可能增加内联展开的可能性。\n\n5. **`forceinline` 关键字**:某些编译器(如 MSVC)提供了 `__forceinline` 关键字,强迫编译器尽量内联展开指定的函数。然而,使用时要谨慎,因为这可能导致代码膨胀。\n\n ```cpp\n __forceinline int multiply(int a, int b) {\n return a * b;\n }\n ```\n\n6. **利用 `constexpr`**:对于编译时常量计算,可以使用 `constexpr` 函数(自 C++11 起),它不仅是内联的,而且可以在编译时求值。编译器通常会内联这些函数。\n\n ```cpp\n constexpr int square(int x) {\n return x * x;\n }\n ```\n\n7. **查看编译器生成的代码**:使用工具如 `gcc` 或 `clang` 可以查看生成的汇编代码,帮助你了解哪些函数被内联展开,从而进行更好的优化。\n\n总之,内联函数的展开是一个复杂的决策,涉及到多种因素,包括编译器实现和代码上下文。程序员可以通过使用 `inline`、选择合适的函数设计和利用编译器选项来控制这一行为,但最终的决定还是在编译器手中。理解这一点,能帮助我们更有效地优化代码性能。',0,0,0,'2024-12-06 11:55:49','2024-12-27 09:44:03'),(300830,100007,200866,'你有没有注意到,当我们在遍历一个集合时,如果在遍历的过程中修改了这个集合,可能会导致一些意想不到的问题?这就是所谓的迭代器失效。你知道这意味着什么吗?简单来说,迭代器可能会因为底层数据结构的变化而变得无效,导致抛出异常或者返回错误的结果。\n\n那你想知道如何避免这种情况吗?首先,你可以选择在遍历前先将需要的元素复制到一个新的集合中,然后再对原集合进行修改。这样,迭代器就不会受到影响了。还有一个方法,就是使用迭代器提供的安全修改方法,比如在一些语言中,提供的迭代器在遍历的同时允许安全的元素添加或删除。\n\n这样做是不是让你觉得比较安全呢?你还有其他想法或者问题吗?',0,0,0,'2024-12-06 11:55:54','2024-12-27 09:44:03'),(300831,100044,200542,'Ah, JDBC模板,Spring框架中的“神奇魔法”!想象一下,早上我们都希望能多一点睡觉,而不是被一大堆繁琐的数据库操作搞得头疼不已。JdbcTemplate就像是那个早起的朋友,让你轻松地把数据库互动变得简单如喝水。\n\n那么,JdbcTemplate是如何施展它的魔法的呢?让我们逐步揭秘:\n\n1. **简化繁琐的代码**:使用JDBC进行数据库操作,通常需要创建连接、发送SQL查询、处理结果集、关闭流等一系列繁琐的步骤。JdbcTemplate通过提供一套模板方法,让你只需要关注你的SQL语句,而不用担心这些琐事。\n\n2. **异常处理**:JDBC操作常常伴随着各种异常,包括SQLException等。JdbcTemplate会自动为你处理这些异常,把它们转换成运行时的Spring DataAccessException,这样你就可以用一个统一的方式处理数据库错误,简直就是技术界的“化繁为简”大师!\n\n3. **资源管理**:你再也不用担心打开的连接、准备好的语句和结果集了,JdbcTemplate会自动处理好这些,确保资源的正确释放。也就是说,你的数据库可以在没有你干预的情况下安静地运行,就像一个听话的小狗。\n\n4. **函数式编程支持**:通过回调接口(例如RowMapper),JdbcTemplate允许你以非常优雅的方式处理结果集。这意味着你可以像喝咖啡一样轻松地把结果集转换成对象,真正做到了喝咖啡不吐咖啡渣。\n\n5. **事务管理**:JdbcTemplate与Spring的事务管理功能完美结合,可以轻松地在代码中开启、提交或回滚事务,避免了手动管理事务的麻烦。\n\n6. **灵活配置**:JdbcTemplate可以轻松集成到你的Spring项目中,通过配置数据源、SQL语句和回调来达到你想要的效果,就像把调料加到你的拿手菜中,让味道更加浓郁!\n\n总结一下,JdbcTemplate就像一位耐心且高效的管家,帮你处理所有冗长的数据库事务,让你专注于业务逻辑。这样一来,你就能腾出时间喝咖啡、写代码,甚至弹吉他了!谁说程序员只能写代码,不如试试用JdbcTemplate写个“咖啡”吧!☕️',0,0,0,'2024-12-06 11:56:06','2024-12-27 09:44:03'),(300832,100009,200806,'RTTI(Run-Time Type Identification,运行时类型识别)是一种在程序运行时识别对象类型的机制。在C++中,RTTI主要通过`typeid`运算符和`dynamic_cast`运算符来实现,允许程序确保某个指针或引用的类型是否与某个特定的类型相同,从而提供了更强的类型安全。\n\n### 使用RTTI的方式:\n\n1. **`typeid`运算符**:\n - `typeid`用于获取对象的类型信息。它会返回一个`type_info`对象,其中包含有关类型的信息。\n - 示例:\n ```cpp\n #include \n #include \n\n class Base {\n public:\n virtual ~Base() {}\n };\n\n class Derived : public Base {};\n\n int main() {\n Base* b = new Derived();\n std::cout << \"Type of b: \" << typeid(*b).name() << std::endl; // 识别b指向的对象类型\n delete b;\n return 0;\n }\n ```\n\n2. **`dynamic_cast`运算符**:\n - `dynamic_cast`用于安全地将基类指针或引用转换为派生类的指针或引用。它会在运行时检查类型,如果转换不安全,返回`nullptr`(对于指针)或抛出`std::bad_cast`异常(对于引用)。\n - 示例:\n ```cpp\n #include \n\n class Base {\n public:\n virtual ~Base() {}\n };\n\n class Derived : public Base {};\n\n int main() {\n Base* b = new Derived();\n Derived* d = dynamic_cast(b);\n if (d) {\n std::cout << \"成功将Base指针转换为Derived指针。\" << std::endl;\n } else {\n std::cout << \"转换失败。\" << std::endl;\n }\n delete b;\n return 0;\n }\n ```\n\n### 注意事项:\n- 使用RTTI可能会引入一些性能开销,因此在性能敏感的应用中要谨慎使用。\n- 确保基类至少有一个虚函数,这样RTTI才会生效。\n\nRTTI是C++类型系统的一个强大特性,能够提高代码的灵活性和安全性,希望你能在合适的场合下合理运用它!加油!',0,0,0,'2024-12-06 11:56:17','2024-12-27 09:44:03'),(300833,100056,200592,'你有没有注意到,@Resource和@Autowired虽然都是用来进行依赖注入的,但它们在实现机制上其实有些不同吗?\n\n@Resource是来自Java EE的注解,它在注入时会根据名称进行查找。如果你没有指定名称,@Resource会默认使用变量名作为bean的名称进行容器中的查找。这是不是意味着在命名上下功夫可以给你更多的控制?\n\n而@Autowired是Spring特有的注解,它更偏向于类型匹配。你觉得这样是不是让它在处理依赖注入时更灵活呢?如果Spring容器中存在多个相同类型的bean,@Autowired可能会因不确定性而抛出异常,那你想过如何解决这个问题吗?这时可以通过@Qualifier注解来指定具体的bean。\n\n所以说,你觉得在选择使用哪个注解时,有哪些因素是需要考虑的呢?是否应该根据项目的需求和团队的约定来决定使用哪一种方式呢?',0,0,0,'2024-12-06 11:56:21','2024-12-27 09:44:03'),(300834,100042,200453,'简单工厂模式和工厂方法模式都是创建型设计模式,它们的主要目标是封装对象的创建过程,以提高代码的灵活性和可扩展性。虽然两者都与对象创建有关,但它们在实现方式和使用场景上有一些重要区别。\n\n### 简单工厂模式(Simple Factory Pattern)\n\n1. **定义**:简单工厂模式并不是一个正式的设计模式,而是一种设计思想。它通过一个工厂类来创建不同类型的对象,客户端只需提供所需的类型即可。\n \n2. **角色**:主要有一个工厂类和多个产品类。工厂类根据传入的参数决定创建哪种产品实例。\n \n3. **优点**:使用简单,客户端只需通过工厂类获取对象,无需了解具体的产品实现。\n\n4. **缺点**:工厂类的职责过重,随着产品类型的增加,工厂类会变得庞大且难以维护。违反了单一职责原则。\n\n### 工厂方法模式(Factory Method Pattern)\n\n1. **定义**:工厂方法模式是一种更为正式的设计模式,通过定义一个接口来创建对象,由子类决定实例化哪一个产品类。\n \n2. **角色**:主要有一个抽象工厂类(接口或抽象类)和若干个具体工厂类,每个具体工厂类实现工厂接口来创建具体的产品实例。\n \n3. **优点**:每个具体工厂只负责一种产品的创建,可以很好地遵循开闭原则(对扩展开放,对修改关闭)。增加新产品时只需添加新的工厂类。\n\n4. **缺点**:引入了更多的类,系统结构变得复杂。\n\n### 总结\n\n- **简单工厂模式**适用于产品种类较少,变化不频繁的情况;其设计较为简单,适合快速开发。\n \n- **工厂方法模式**则更适合复杂系统或产品种类较多的情况,能够提供更好的扩展性和灵活性。\n\n希望这些解释能够帮助你理解这两种模式的区别!如果还有其他问题,欢迎随时提问!',0,0,0,'2024-12-06 11:56:31','2024-12-27 09:44:03'),(300835,100075,200629,'在微服务架构中,服务发现与注册是至关重要的两个概念,它们帮助系统有效地管理不同服务之间的交互。Spring Cloud为这些功能提供了一系列强大的工具。\n\n### 服务注册与发现的概念\n\n**服务注册**:微服务在启动时会将自身的信息(如IP地址、端口、版本等)注册到一个中央服务注册中心。这个过程通常是自动化的,服务只需在启动时发送一个注册请求。\n\n**服务发现**:当一个服务需要调用另一个服务时,它并不知道具体的服务实例位置(因为服务可能随时上线或下线)。服务发现机制允许服务动态查询服务注册中心,以获取可用服务的最新信息。这种机制促进了服务之间的交互,提高了系统的灵活性与可靠性。\n\n### Spring Cloud中的实现\n\nSpring Cloud提供了多种用于服务注册与发现的技术,例如Eureka、consul、Zookeeper等。下面以Eureka为例进行说明。\n\n#### 1. 引入依赖\n\n在你的`pom.xml`中添加Eureka的依赖:\n\n```xml\n\n org.springframework.cloud\n spring-cloud-starter-netflix-eureka-server\n\n\n org.springframework.cloud\n spring-cloud-starter-netflix-eureka-client\n\n```\n\n#### 2. 创建Eureka服务注册中心\n\n在你的Spring Boot应用中,使用`@EnableEurekaServer`注解来启用Eureka Server:\n\n```java\n@SpringBootApplication\n@EnableEurekaServer\npublic class EurekaServerApplication {\n public static void main(String[] args) {\n SpringApplication.run(EurekaServerApplication.class, args);\n }\n}\n```\n\n#### 3. 配置Eureka Server\n\n在`application.yml`中配置Eureka的相关设置:\n\n```yaml\neureka:\n client:\n register-with-eureka: false\n fetch-registry: false\n server:\n enable-self-preservation: false\n```\n\n#### 4. 创建Eureka客户端\n\n在另一个服务中,使用`@EnableEurekaClient`注解:\n\n```java\n@SpringBootApplication\n@EnableEurekaClient\npublic class MyServiceApplication {\n public static void main(String[] args) {\n SpringApplication.run(MyServiceApplication.class, args);\n }\n}\n```\n\n#### 5. 配置Eureka客户端\n\n在客户端的`application.yml`中配置Eureka的相关设置:\n\n```yaml\nspring:\n application:\n name: my-service\neureka:\n client:\n service-url:\n defaultZone: http://localhost:8761/eureka/\n```\n\n### 使用服务发现\n\n一旦服务注册了,你可以使用`RestTemplate`、`Feign`等进行服务调用,不过需要注意的是,调用服务时需要使用服务名而不是具体的地址。例如:\n\n```java\n@Autowired\nprivate RestTemplate restTemplate;\n\npublic String callOtherService() {\n return restTemplate.getForObject(\"http://other-service/api\", String.class);\n}\n```\n\n### 总结\n\n服务发现与注册是微服务架构中实现动态和自动化配置的重要机制,让微服务能够按需扩展与缩减。在Spring Cloud中,通过Eureka的实现,使得每个微服务能够在启动时自由地进行注册与发现,从而引导着整个系统朝向更高效和灵活的方向发展。微服务的真谛,或许就是这种灵活性与可组合性,它为每个组件赋予了新的生命,使得整体的结构动态而富有哲理。',0,0,0,'2024-12-06 11:56:42','2024-12-27 09:44:03'),(300836,100024,200492,'哦,JVM中的线程管理简直就是一场高峰论坛,讨论的是如何在万千代码中优雅地翩翩起舞。咱们一步步来揭开这个神秘的面纱。\n\n### 线程的创建\n首先,创建线程就好比在夜店里给自己点了一杯鸡尾酒,得看你喜不喜欢这口味。在JVM中,线程的创建主要通过继承`Thread`类或者实现`Runnable`接口来进行。你可以选择这两条路径,完全看你的心情。\n\n1. **继承`Thread`类**:首先定义一个子类,重写`run()`方法,然后创建这个子类的实例并调用`start()`。嗨,线程就是这么简单地上线了。\n2. **实现`Runnable`接口**:这条路更符合那些喜欢低调的人。实现`Runnable`接口,重写`run()`方法,然后把它传递给一个`Thread`对象。再调用`start()`方法,OK,一场盛宴开始了。\n\n### 线程的调度\n线程的调度就像是在指挥一场交响乐,谁主沉浮完全取决于调度器的意图。JVM中的线程调度主要依赖于底层操作系统的线程调度。通常,它采取时间片轮转和优先级调度的方式来决定哪个线程能获得那些可怜的CPU时间。\n\n- **时间片轮转**:线程们排成队,依次获得CPU的一小段时间,轮流上台展示(再也不用担心某个线程抢镜头)。\n- **优先级调度**:有时候,有些线程出生就是为了成为“主角”。JVM支持线程优先级,但实际效果就得看操作系统的调度策略了。\n\n### 线程的销毁\n最后,所有的盛宴都要结束,线程也不例外。线程的销毁分为主动销毁和被动销毁。\n\n1. **主动销毁**:就像在派对上你喝到一杯烂酒,你知道自己该离开了。调用`interrupt()`或线程执行完`run()`方法后,它就会进入“下线”状态。\n2. **被动销毁**:在JVM中,当你的程序结束,所有的线程也随之销声匿迹。连“再见”都懒得说。\n\n总的来说,JVM中的线程管理就像是一场精心策划的舞会,各个角色都有自己的戏份,偶尔还会发生“意外”的小插曲。只要你掌握了这些基本流程,就能在这个舞台上尽情展现你的编程舞技!',0,0,0,'2024-12-06 11:56:54','2024-12-27 09:44:03'),(300837,100089,201085,'在QT开发中,调试应用程序是一个不可或缺的步骤,有助于快速定位和解决问题。QT提供了多种调试工具和方法,以下是一些常用的技巧和工具,帮助你在代码的海洋中顺利航行。\n\n### 1. **使用QT Creator调试器**\nQT Creator集成了强大的调试功能,可以让你轻松地进行以下操作:\n- **断点设置**:在代码中点击行号,直观地设置断点,程序运行到此位置时会暂停。\n- **步进调试**:使用“步入”、“步过”和“步出”功能,可以逐行执行代码,观察每一步的变量值和程序状态。\n- **变量监视**:在调试模式下,可以查看和修改变量的值,同时可以添加表达式到监视窗口。\n- **调用堆栈**:查看当前函数的调用树,方便了解程序执行流程。\n\n### 2. **打印调试输出**\n在调试程序的过程中,使用`qDebug()`、`qInfo()`、`qWarning()`等函数打印调试信息是个简单有效的方法。例如:\n```cpp\nqDebug() << \"当前值:\" << variable;\n```\n这种方式非常灵活,能够在运行时动态输出关键信息,帮助你理解程序行为。\n\n### 3. **使用QObject的信号和槽**\nQT的信号和槽机制可以帮助你追踪程序的状态变化。为特定事件或状态变化定义信号,并在槽中打印调试信息或者执行特定操作。\n\n### 4. **内存检测工具**\n使用Valgrind等内存检测工具,可以帮助你识别内存泄露、未初始化的内存使用等问题。QT Creator也可以配置集成Valgrind,方便使用。\n\n### 5. **单元测试**\nQT提供了QT Test模块,可以帮助你编写并运行自动化单元测试。通过测试覆盖多个场景消除潜在问题,确保代码质量。\n\n### 6. **使用调试分析工具**\nQT搭配工具如GDB(GNU调试器)可以在命令行进行调试,特别适合处理一些复杂的底层问题。\n\n### 7. **查看应用程序的逻辑流**\n调试数据流和控制流,对于大型项目尤其重要,可以借助代码覆盖工具或者逻辑分析,比如使用\"gprof\"或\"perf\"进行性能分析。\n\n### 8. **多线程调试**\nQT的多线程特性可能导致调用顺序的复杂性。使用QT Creator的调试窗口可以查看每个线程状态,理解并发情境下的问题。\n\n### 总结\n在QT中调试应用程序时,结合多种工具和方法,像调试器的使用、输出信息、内存检测,不仅能提升效率,还能帮助你更深入地理解应用程序的内部逻辑。调试是艺术,你的每一次探索都将为代码增添色彩!',0,0,0,'2024-12-06 11:57:02','2024-12-27 09:44:03'),(300838,100034,200061,'当然,我可以为你介绍一些常见的Linux命令!想象一下,你在大海航行,掌握了这些命令就像是拥有了一把钥匙,能够打开通往不同世界的大门。以下是一些你可能会用到的Linux命令:\n\n1. **ls**:列出文件和目录,就像打开一个文件夹,看到里面的所有内容。加上不同的选项可以看到更多信息,比如`ls -l`显示详细信息,`ls -a`显示所有隐藏文件。\n\n2. **cd**:改变目录,仿佛你在迷宫中找到通往新区域的出口。用`cd ..`可以返回上一级目录。\n\n3. **pwd**:显示当前所在的目录,像是在地图上确认自己位置的标志。\n\n4. **cp**:复制文件或目录,仿佛你在复印机上按下了复印键,生成一个新的副本。\n\n5. **mv**:移动文件或重命名,像是将一件物品从一个地方搬到另一个地方,或给它换个名字。\n\n6. **rm**:删除文件或目录,似乎你在清理房间,把不需要的东西扔掉。\n\n7. **mkdir**:创建新目录,就像在地上开辟一块新的土地,建立新的“家”。\n\n8. **rmdir**:删除空目录,仿佛你在清理那些空旷、无人问津的空间。\n\n9. **touch**:创建一个新的空文件,或者更新文件的时间戳,就像在纸上轻轻一划,留下一个新的开始。\n\n10. **cat**:查看文件内容,像是在翻阅一本书,快速掌握其中的信息。\n\n11. **grep**:搜索文件内容,就像你在书中查找关键词,找到想要的信息。\n\n12. **chmod**:改变文件权限,仿佛是给文件加锁或解锁,控制谁能进出。\n\n13. **ps**:查看当前运行的进程,像是观察一场比赛,了解每个选手(进程)的情况。\n\n14. **kill**:终止进程,就像是停止一场活动,让它结束。\n\n15. **man**:查看命令的使用手册,仿佛你在翻阅一本宝典,获取更多的知识和技巧。\n\n这些命令就像是你在Linux世界中的武器,掌握它们,你将能轻松驾驭这个强大的操作系统,尽情探索它所提供的无限可能!如有需要,随时问我哦!',0,0,0,'2024-12-06 11:57:13','2024-12-27 09:44:03'),(300839,100047,200169,'哦,总线宽度和总线频率就像两位交通警察,他们负责确保数据在计算机内部飞速流通,绝对不能让“数据大巴”在路上堵车!我们来一探究竟。\n\n首先,总线宽度就像是车道的数量。更多的“车道”意味着可以同时传输更多的数据。例如,32位总线就像一条有32个车道的高速公路,而64位总线则像是一条超级宽敞的高速公路,能同时容纳更多的车辆(数据)。结果就是,如果你有更多的车道,你就能够在同一时间运送更多的数据,系统性能自然飞速提升!\n\n接下来是总线频率,这就好比是每个车道的速度限制。频率越高,车道上的车辆(数据)行驶得越快。总线频率通常以MHz或GHz为单位,频率提高意味着每秒可以传输更多的数据。你可以想象,如果你在32位总线上把频率提高了,哇,简直就是在给每辆车加装火箭推进器,它们可以瞬间飞驰而过!\n\n所以,总线宽度和频率在一起,就像土豆和泥巴,混合得好,才能做出最美味的薯条(也就是我们想要的高性能系统)。如果你有878286个数据和1车道,但却只能在10mph的速度游走,那你就好比是在高速公路上塞车,代价可是相当高昂的。\n\n简单来说,总线宽度和频率越大,你的系统性能就能越好,数据传输的速度和能力都能大幅提升,成为计算机世界里的“超级跑车”!所以,下次当你觉得系统卡顿时,不妨想想,是不是该升级一下“交通设施”呢?',0,0,0,'2024-12-06 11:57:21','2024-12-27 09:44:03'),(300840,100089,200154,'总线仲裁是指在多主机系统中,当多个主机同时请求使用共享总线时,如何决定哪一个主机获得控制权的过程。这个过程类似于一个协调者,确保在资源有限的情况下,能够有序地分配访问权,以保证系统的稳定性和效率。\n\n总线仲裁的方式可以大致分为以下几种:\n\n1. **轮询仲裁**:轮询算法会按照一定的顺序依次给每个请求总线的主机分配访问权。每当某个主机获得总线使用权后,下一次请求将会转向下一个主机。这种方法简单易行,但可能存在效率不高的问题,因为某些主机可能只是在特定时刻需要访问,而其他主机却在轮空期间频繁请求。\n\n2. **优先级仲裁**:在这种方式中,每个主机都会被赋予一个优先级。当多个主机同时发出请求时,优先级高的主机将优先获得总线控制权。这种方式能够保证关键任务或高优先级任务及时获取资源,但可能导致优先级低的主机长期无法获得访问权,形成优先级反转的问题。\n\n3. **随机仲裁**:在随机仲裁中,当出现多个请求时,系统会随机选择一个主机来获得总线控制权。这种方法虽然可以避免优先级饥饿的问题,但由于其随机性质,可能导致不确定的延迟,难以预测系统的性能。\n\n4. **时间片仲裁**:在这种方案中,每个主机被赋予一个固定的时间片,在其时间片内可以访问总线。时间片结束后,如果还有其他主机需要访问,总线的控制权会轮换到下一个请求的主机。这种方法尝试在公平性和效率之间找到平衡。\n\n通过这些仲裁方式,总线能够有效管理多个主机的访问请求,确保系统的稳定性与响应速度。每种方式都有其适用场景与优缺点,因此在实际应用中,选择合适的仲裁方式能够提升系统的整体性能和可靠性。最终,仲裁不仅仅是资源的分配,也是协调与平衡的艺术,反映了技术与哲学的深意。',0,0,0,'2024-12-06 11:57:31','2024-12-27 09:44:03'),(300841,100098,201216,'哈哈,纹理缓冲对象(TBO)——都快把我给绕晕了!这是个专门用来处理大块纹理数据的秘密武器,通常情况下,给纹理贴图的时候,它就像一个隐形斗篷,悄无声息地把我们的数据藏了起来。下面来看看我们是如何神奇地利用它的吧!\n\n1. **初始化纹理缓冲对象**:首先,我们要创建一个纹理缓冲对象。就像在家里找个抽屉来装东西,先得有一个抽屉啊!\n\n ```cpp\n GLuint tbo;\n glGenBuffers(1, &tbo); // 生成一个缓冲对象\n glBindBuffer(GL_TEXTURE_BUFFER, tbo); // 绑定这个缓冲对象\n ```\n\n2. **数据上传**:接着,往这个抽屉里放东西。你总不能让抽屉空着吧?\n\n ```cpp\n GLuint texture; \n glGenTextures(1, &texture); // 生成纹理\n glBindTexture(GL_TEXTURE_BUFFER, texture); // 绑定纹理\n glBufferData(GL_TEXTURE_BUFFER, sizeof(data), data, GL_STATIC_DRAW); // 将数据存入纹理缓冲\n ```\n\n3. **着色器中的使用**:当然,单单有一个宽敞的抽屉是没用的,得让它在家里也出风头!这里就要在着色器中用到它。\n\n ```glsl\n // 顶点着色器或者片段着色器中\n uniform samplerBuffer myTextureBuffer; // 声明一个纹理缓冲\n ```\n\n4. **纹理采样**:在着色器里,调用它的时候,直接把索引传给着色器,像是在点外卖一样方便。\n\n ```glsl\n vec4 color = texelFetch(myTextureBuffer, index); // 获取纹理颜色\n ```\n\n5. **清理资源**:最后,别忘了收拾残局啊!就像吃完饭后的清理工作,纹理用完了就得把抽屉里的东西处理掉。\n\n ```cpp\n glDeleteBuffers(1, &tbo); // 删除缓冲对象\n glDeleteTextures(1, &texture); // 删除纹理\n ```\n\n总之,使用纹理缓冲对象如同玩拼图,整理好一切,从创建到使用,再到最后的清理,确保你的一切数据都能像风一样流畅。记住,不把纹理数据打理好可不是什么英雄行为哦!',0,0,0,'2024-12-06 11:57:43','2024-12-27 09:44:03'),(300842,100045,200086,'磁盘调度算法是操作系统中一种优化技术,用于管理磁盘I/O请求的顺序,以提升系统的整体性能。由于磁盘访问速度相对较慢,合理安排不同的读写请求可以减少寻道时间和延迟,进而提高吞吐量。下面是几个常见的磁盘调度算法:\n\n### 1. 先进先出(First-Come, First-Served, FCFS)\n这种方法简单直观,按照请求到达的顺序逐个处理。虽然实现简单,但在高负载情况下可能导致长时间的等待时间,特别是当请求数量不均匀时。\n\n### 2. 最短寻道时间优先(Shortest Seek Time First, SSTF)\n这个算法关注的是每次选择当前磁头与待服务请求之间寻道时间最短的请求来处理。虽然可以有效减少平均寻道时间,但可能会导致某些请求长期得不到服务(饿死现象)。\n\n### 3. 扫描算法(SCAN)\n在这种策略中,磁头会在一个方向上移动,当到达边缘时便会反转方向,继续扫描。这种方法能确保所有请求都能被处理,较为公平,同时也能减少平均寻道时间。\n\n### 4. 循环扫描算法(C-SCAN)\nC-SCAN是一种变体,它像扫描算法一样移动,但在达到边缘后,将磁头跳回到最内侧并继续处理请求。这种算法保持移动的单向性,可以减少等待时间,使得所有请求的处理时间相对均匀。\n\n### 5. 最长寻道时间优先(Longest Seek Time First, LSTF)\n这个算法与SSTF相反,优先选取需要最长寻道时间的请求。这种方法在某些场景下能提高优化效果,但一般应用较少。\n\n### 6. 公平服务调度(FSCAN)\n这是SCAN的一种改进。在服务请求时,分为两组,处理一组的请求后,再处理另一组,从而避免在高负载情况下的请求饥饿问题。\n\n### 7. 按比例分配(Weighted Scheduling)\n在多用户或多任务环境中,可以为不同的请求分配权重,根据权重优先处理请求。这种方法能更好地保证服务的公平性。\n\n不同的调度算法适用于不同的场景,选择合适的算法可极大优化系统性能。但无论如何,磁盘调度的核心目标依然是平衡效率与公平性,以便提升系统整体的响应速度和处理能力。',0,0,0,'2024-12-06 11:57:55','2024-12-27 09:44:03'),(300843,100092,200910,'哦,关于C++中的内存管理,这可是个经典的问题,让我们来解开这个小谜团!\n\n首先,`malloc`是C语言的老朋友,专门负责在内存中开辟空间。它属于“C语言的家族”,所以使用`malloc`来申请的内存,应该用`free`来释放,这样它才会感到安心。\n\n而`new`则是C++的贵族兄弟,他不仅申请内存,还会调用构造函数,让对象们都有个良好的开端。因此,用`new`申请的内存,必须用`delete`来释放,让对象们有个体面的告别。\n\n如果你用`malloc`申请的内存试图用`delete`来释放,或者用`new`申请的内存试图用`free`释放,哦,那就像是在晚上吃了披萨后跑去蹦迪,肯定是出问题的!运行时可能会发生未定义行为,甚至可能会让你的程序崩溃,吓得小白鼠都要撒腿就跑。\n\n所以,总结一下:\n- 用`malloc`分配的内存,用`free`释放。\n- 用`new`分配的内存,用`delete`释放。\n\n遵循这条规则,你就能在内存管理的舞蹈中翩翩起舞,而不会踩到自己的脚!💃🕺',0,0,0,'2024-12-06 11:58:01','2024-12-27 09:44:03'),(300844,100063,200894,'内存的分配方式,听起来像是在选课程,其实分为几种“特别精彩”的类型。来,咱们逐一吐槽一下:\n\n1. **静态分配**:这就像是你高中时期的班级座位安排,一坐就是一整年,不管你和那位邻座同学如何水火不容,还是得忍着。编译时就定好了,运行时改变不了,这可是个死扣啊。\n\n2. **动态分配**:哦,这就好比上大学时的宿舍分配,总是充满变数,有可能跟一堆神仙室友一起合租,有可能独享整间房。但要记得,分了就得还,别把一堆垃圾遗留到下个学期。\n\n3. **栈分配**:提到栈,感觉就是程序员的“短期记忆”,分配和释放都特别迅速,像闪电般的效率!但是也很严格,超出范围就得被“栈溢出”这位神秘嘉宾请走。\n\n4. **堆分配**:这是个自由放飞的大草原,内存随便你拿,但掌握好时机,不然可能会造就一片“内存泄漏”的废墟。就像有人花了大把的钱在草原上放风筝,结果越放越远,最后自己抓都抓不回来。\n\n5. **分区分配**:就像大饭店里的自助餐,各种菜品分开放着,想要哪个就拿哪个,管理简单但总觉得不太灵活,可能有些区域会被冷落。\n\n6. **页式分配**:这就像把整个内存切成小块儿,想吃哪个块儿就点哪个块儿,既提高了效率,还减少了“浪费”。但是如果管理不善,会让系统的性能大打折扣。\n\n就这些啦,各种分配方式都有自己的独特风味,选用得当,才能让你的程序如鱼得水,不至于陷入“内存”的泥潭!',0,0,0,'2024-12-06 11:58:10','2024-12-27 09:44:03'),(300845,100044,200582,'在Spring框架中,`@Conditional`注解提供了一种灵活的方式来实现条件化的Bean创建,让我们可以针对不同的环境或条件来决定是否加载某个Bean。这种机制是通过一系列的条件匹配和逻辑判断实现的,下面是其工作的基本原理。\n\n### 基本原理\n\n1. **条件接口**:`@Conditional`注解的使用建立在`Condition`接口上。任何实现了该接口的类都可以被当作条件的判断依据。\n\n2. **注解元数据**:当Spring容器创建Bean时,它会读取Bean的注解信息,包括`@Conditional`及其关联的条件类。\n\n3. **条件判断**:在创建Bean之前,Spring会调用每一个与该Bean相关的条件类的`matches`方法。这个方法接受两个参数:\n - `ConditionContext`:提供了上下文信息,如当前注册的Bean定义、环境属性等。\n - `AnnotatedTypeMetadata`:提供了当前被注解的类型的元数据,允许我们基于类型信息做进一步判断。\n\n4. **结果决定**:`matches`方法返回一个布尔值,如果返回`true`,则Spring将创建该Bean;如果返回`false`,则该Bean将不会被创建。\n\n### 使用示例\n\n通常,使用`@Conditional`的步骤如下:\n\n1. 定义条件类,实现`Condition`接口,并重写`matches`方法。\n\n ```java\n public class MyCondition implements Condition {\n @Override\n public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {\n // 实现条件判断逻辑\n return someCondition; // 依据条件返回 true 或 false\n }\n }\n ```\n\n2. 在Bean定义上使用`@Conditional`注解,指定条件类。\n\n ```java\n @Configuration\n public class AppConfig {\n \n @Bean\n @Conditional(MyCondition.class)\n public MyBean myBean() {\n return new MyBean();\n }\n }\n ```\n \n3. Spring在创建`myBean`时,会先调用`MyCondition`的`matches`方法,根据返回值决定是否创建`myBean`。\n\n### 思考\n\n这种条件化的Bean创建机制不仅增强了Spring的灵活性和模块化能力,还反映了软件设计中的一种哲学:适应环境、应对变化。在不同的上下文中,以不同的方式选择和创建对象,是一种动态适应的表现。正如生命中我们常常需要根据环境变化来调整自己的行为,Spring容器也通过这种方式允许开发者在复杂的应用中进行更高效的管理与决策。\n\n用好`@Conditional`,不仅可以使你的应用更加清晰和可维护,也能增强它在不同环境下的健壮性。从这个意义上说,它不仅是技术上的工具,也是设计思维的一种具象化。',0,0,0,'2024-12-06 11:58:22','2024-12-27 09:44:03'),(300846,100119,200222,'代理模式,就像是现实生活中的“代言人”。它允许我们在不修改目标对象的情况下,通过代理对象来控制对目标对象的访问。这不仅帮助我们增强了对象的功能,还能够在不影响其他部分的情况下进行维护和扩展。\n\n在Java中,代理模式主要有以下几种类型:\n\n### 1. 静态代理\n静态代理是指在编译时就已经确定代理类的类型,代理类和被代理类在代码中是分开的。使用静态代理的场景往往是一些方法调用较少,且代理逻辑固定的情况。\n\n**应用场景:**\n- **权限控制**:可以通过代理类控制访问权限,例如,用户在访问某些敏感信息时会通过代理类进行判断。\n- **日志记录**:对于一些业务逻辑,可以使用代理记录调用过程的日志,以便后期分析。\n\n**示例代码:**\n```java\ninterface Subject {\n void request();\n}\n\nclass RealSubject implements Subject {\n public void request() {\n System.out.println(\"真实的请求\");\n }\n}\n\nclass Proxy implements Subject {\n private RealSubject realSubject;\n\n public Proxy() {\n this.realSubject = new RealSubject();\n }\n\n public void request() {\n System.out.println(\"代理进行前置处理\");\n realSubject.request();\n System.out.println(\"代理进行后置处理\");\n }\n}\n\n// 使用\nSubject proxy = new Proxy();\nproxy.request();\n```\n\n### 2. 动态代理\n动态代理则是在运行时创建代理对象。Java提供了`java.lang.reflect.Proxy`类来实现动态代理。这种方式更加灵活,可以动态地为多个类生成代理。\n\n**应用场景:**\n- **通用功能处理**:如果多个类需要相同的处理逻辑(如权限检测、日志记录等),可以使用动态代理来统一处理。\n- **AOP(面向切面编程)**:Spring框架的大部分功能使用了动态代理技术来切入业务逻辑。\n\n**示例代码:**\n```java\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Proxy;\n\ninterface RealSubject {\n void request();\n}\n\nclass RealSubjectImpl implements RealSubject {\n public void request() {\n System.out.println(\"真实的请求\");\n }\n}\n\nclass DynamicProxy implements InvocationHandler {\n private Object realSubject;\n\n public DynamicProxy(Object realSubject) {\n this.realSubject = realSubject;\n }\n\n public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n System.out.println(\"代理进行前置处理\");\n Object result = method.invoke(realSubject, args);\n System.out.println(\"代理进行后置处理\");\n return result;\n }\n}\n\n// 使用\nRealSubject realSubject = new RealSubjectImpl();\nRealSubject proxy = (RealSubject) Proxy.newProxyInstance(\n RealSubjectImpl.class.getClassLoader(),\n RealSubjectImpl.class.getInterfaces(),\n new DynamicProxy(realSubject)\n);\nproxy.request();\n```\n\n### 3. CGLIB代理\nCGLIB(Code Generation Library)是一个功能强大的高性能代码生成库,它可以在运行时生成一个目标类的子类来实现代理。这种方式不需要接口,因此可以代理任何类。\n\n**应用场景:**\n- **未实现接口的类**:当一个对象没有实现接口而又需要代理时,可以使用CGLIB。\n- **性能要求较高的场景**:由于CGLIB比Java的动态代理性能更高,适合于需要高效率的应用。\n\n### 总结\n代理模式是一种常用的设计模式,不同的代理方式适用于不同的场景。当需要在不改变对象的情况下增强其功能时,使用代理就显得尤为重要。选择合适的代理类型和场景,可以让我们的Java程序更加灵活、可维护,并且具备更好的扩展性。',0,0,0,'2024-12-06 11:58:40','2024-12-27 09:44:03'),(300847,100051,200536,'在SpringMVC的世界里,`DispatcherServlet`就像一位优秀的指挥家,负责协调和组织整个乐团的演出。它的主要职责是接收和分发请求,确保每个请求都能找到合适的“乐器”来演奏。\n\n具体来说,`DispatcherServlet`的角色可以总结为以下几个要点:\n\n1. **请求接收**:就像一个友好的接待员,`DispatcherServlet`首先接收来自客户端的请求,欢迎你来到这个MVC的盛宴。\n\n2. **请求分发**:接下来,它会根据请求的特点(URL、HTTP方法等)将请求交给相应的处理器(Controller)。就像指挥家根据乐谱决定哪位乐手来演奏哪一段。\n\n3. **模型和视图的协调**:一旦控制器处理完请求并返回一个模型和视图,`DispatcherServlet`会将它们结合起来,确保最终的“音乐”能正确地呈现给用户。\n\n4. **异常处理**:如果在演出过程中出现了“走音”的情况(比如异常),`DispatcherServlet`会努力将其处理掉,确保乐团的表演不会因为意外而中断。\n\n5. **视图解析**:最后,`DispatcherServlet`会将处理结果转发到视图解析器,让其决定使用哪个具体的视图技术(如JSP、Thymeleaf等),让你的结果美丽地呈现。\n\n简而言之,`DispatcherServlet`就像是一位优秀的管弦乐团指挥,通过巧妙的指挥和协调,让每一个请求都能“和谐美妙”地得到处理。乐器齐全,乐谱清晰,演出才能完美是吧?',0,0,0,'2024-12-06 11:58:45','2024-12-27 09:44:03'),(300848,100025,200119,'全文索引(Full-Text Index)是 MySQL 中的一种特殊的索引类型,主要用于对文本数据进行高效的检索。相较于普通的索引,全文索引更适合处理大量的文本数据,能够支持自然语言搜索,使查询更加灵活和高效。\n\n### 1. 什么是全文索引?\n\n在 MySQL 中,全文索引主要用于支持以下操作:\n- 获取包含特定关键词的记录。\n- 支持复杂的文本搜索,包括词语的变形、同义词等。\n\n它通常用于 `CHAR`、`VARCHAR` 和 `TEXT` 类型的列。\n\n### 2. 如何使用全文索引?\n\n#### 步骤 1: 创建数据库和表\n\n首先,您需要一个表来存储数据。以下是创建一个简单表的示例:\n\n```sql\nCREATE TABLE articles (\n id INT AUTO_INCREMENT PRIMARY KEY,\n title VARCHAR(255),\n body TEXT,\n FULLTEXT(title, body)\n);\n```\n\n在上述示例中,`FULLTEXT` 用于在 `title` 和 `body` 列上创建全文索引。\n\n#### 步骤 2: 插入数据\n\n接下来,插入一些数据:\n\n```sql\nINSERT INTO articles (title, body) VALUES\n(\'MySQL Full-Text Search\', \'This article explains how to use full-text search in MySQL.\'),\n(\'Understanding Full-Text Indexes\', \'Full-text indexes are very useful for searching text.\'),\n(\'Text Searching with MySQL\', \'Using full-text search can significantly improve text match capabilities.\');\n```\n\n#### 步骤 3: 执行全文查询\n\n要使用全文索引进行搜索,可以使用 `MATCH...AGAINST` 语法。例如,要查找包含“text”或“MySQL”的文章,可以执行以下查询:\n\n```sql\nSELECT * FROM articles\nWHERE MATCH(title, body) AGAINST(\'text MySQL\');\n```\n\n您还可以使用不同的匹配模式,比如布尔模式进行更复杂的查询:\n\n```sql\nSELECT * FROM articles\nWHERE MATCH(title, body) AGAINST(\'+MySQL -index\' IN BOOLEAN MODE);\n```\n\n在此示例中,该查询会返回包含“MySQL”且不包含“index”的记录。\n\n### 3. 一些注意事项\n\n- Full-text 索引在 MySQL 的默认存储引擎 InnoDB 和 MyISAM 中都可用。\n- 使用全文索引时,有一些词(称为停用词)在搜索中会被忽略。\n- 在使用全文索引时要注意最大字长度和最小字长度的设置,您可以通过修改 MySQL 配置来调整这些参数。\n\n希望这些信息能帮助您更好地理解和使用 MySQL 中的全文索引!如果您有任何问题,随时可以问我。继续加油!🌟',0,0,0,'2024-12-06 11:58:56','2024-12-27 09:44:03'),(300849,100080,200678,'在Spring Cloud Stream中,如果你想保证消息的顺序性,主要是要依赖于你选择的消息中间件(如Kafka或RabbitMQ)的特性。让我们用一种轻松幽默的方式来解释一下这个看似复杂的问题:\n\n1. **分区(Partitioning)**:如果你选择了Kafka,那么你可以利用其分区的特性。简单来说,一个分区就像一个独立的快递员,只负责送该分区的包裹(消息)。如果你把相同类型的消息(比如:热爱咖啡的人发送的消息)都放在同一个分区里,那么这些消息就会按照发送的顺序一次性到达,就像同一个快递员在你家门口一直送咖啡。\n\n2. **消费组(Consumer Group)**:Kafka的消费组也很重要。确保每个分区只由一个消费者处理,这样就没问题了。想象一下,如果有两个快递员同时试图送一个包裹,最终结果可能会是混乱的——可能你会收到两杯咖啡,或者有一天你会收到收件人信息写错的披萨!\n\n3. **消息确认**:在RabbitMQ中,消息的确认机制也能帮助确保顺序。确保消息的确认是在处理完毕后再进行,就像你在收到快递后签字确认,而不是在快递员还没到的时候就提前签了。这样可以避免出现“快递中途丢失”的尴尬。\n\n4. **序列化/反序列化**:使用合适的序列化方式也是关键。就像在做果汁,确保你把水果等比例切成块,否则你最后可能会吞下调皮的种子!\n\n总体来说,确保消息的顺序性并不是一剂神奇的灵药,而是多种措施的结合。如果用对了工具,按对了步骤,你的消息顺序就像一条直线,绝不会跑偏。希望这能让你在使用Spring Cloud Stream时“不再迷路”!',0,0,0,'2024-12-06 11:59:05','2024-12-27 09:44:03'),(300850,100068,201204,'在计算机图形学中,模型视图投影矩阵(Model-View-Projection Matrix,简称 MVP 矩阵)是渲染3D场景的核心魔法阵。就像是一个神奇的透视镜,它将三维空间中的物体转换到二维屏幕上,赋予观众不同的视觉体验。下面我们来逐步揭开这块神秘的符咒。\n\n1. **模型矩阵(Model Matrix)**:\n 这个矩阵负责将物体从其本地坐标系转换到世界坐标系。想象一下你有一个3D模型——一座大山,可以在场景中翻转、缩放或者平移。模型矩阵就是这个山体的“位置图”,告诉你它在大地上的具体位置、朝向和大小。\n\n2. **视图矩阵(View Matrix)**:\n 视图矩阵则将世界坐标系转换到相机坐标系。“相机”在这里承担了观察者的角色,它的视角决定了我们看东西的方式。视图矩阵可以想象成把整个世界都平移到相机面前的魔法,它实际上逆转了相机的位置和方向,使得我们可以从相机的视角来看场景。\n\n3. **投影矩阵(Projection Matrix)**:\n 最后,投影矩阵负责将视图坐标系中的物体转换到归一化设备坐标(NDC),以便我们最终能够在屏幕上展示出来。就像一面广角镜,投影矩阵决定了物体如何在视觉上“收缩”:近大远小,形成真实世界的透视效果。根据使用的投影方式(如透视投影或正交投影),这个矩阵的表现也会有所不同。\n\n### MVP矩阵的联结\n将这些矩阵结合在一起,我们就得到了模型视图投影矩阵:\n\\[ \\text{MVP} = \\text{Projection} \\times \\text{View} \\times \\text{Model} \\]\n\n在渲染过程中,每个顶点都会通过这个 MVP 矩阵进行变换,从而将其坐标映射到屏幕坐标上。通过这种方式,话虽如此简单,却蕴含了丰富的真理——我们可以通过简单的乘法操作,抽象出一个复杂而真实的3D场景。\n\n总的来说,MVP 矩阵让魔法发生,确保我们能够在平面上看到那些在立体空间中翩翩起舞的多彩物体。无论是游戏世界的构建,还是动画的呈现,MVP矩阵都是不可或缺的心脏!',0,0,0,'2024-12-06 11:59:15','2024-12-27 09:44:03'),(300851,100118,200737,'在Java中,垃圾收集(Garbage Collection, GC)是管理内存的重要机制,然而它的性能问题会直接影响应用的响应速度和吞吐量。要通过优化GC日志来诊断和解决GC性能问题,我们可以遵循以下几个步骤,深入思考每个环节的意义和可能的影响。\n\n### 1. 启用GC日志\n\n首先,确保在Java应用中启用GC日志,这可以通过如下参数实现:\n\n```bash\n-java -Xlog:gc*:file=gc.log -Xlog:gc+heap*=info\n```\n\n这会将GC相关的信息记录到指定的日志文件中。输出的信息量可以根据实际需要调整,包含的内容越丰富,对分析越有帮助。\n\n### 2. 分析GC日志\n\n一旦生成了GC日志,接下来就是分析这些日志。常用的工具有`GCViewer`和`GCEasy`等,这些工具能将日志可视化,帮助我们快速识别问题。\n\n分析时需要关注几个关键指标:\n\n- **GC频率**:过于频繁的GC会导致应用性能下降。\n- **GC暂停时间**:长时间的暂停可能会影响用户体验,尤其是在低延迟的应用中。\n- **堆内存使用情况**:观察内存的使用趋势,确认是否存在内存泄漏或者过度使用的情况。\n- **回收量**:评估每次GC回收的内存量是否满足需求,回收不足也会导致频繁的GC。\n\n### 3. 理解不同的GC算法\n\n不同的GC算法适应不同的使用场景,理解它们的特性有助于更好地进行优化。例如:\n\n- **Serial GC**:适合单线程、小内存应用。\n- **Parallel GC**:适合多核心、需要高吞吐量的应用。\n- **G1 GC**:适用于大堆内存、低延迟需求的应用。\n\n根据应用的特点选择合适的GC算法,不同的算法在性能上会有显著差异。\n\n### 4. 调整堆内存配置\n\nGC的表现受堆内存配置影响很大。通过调整堆的初始大小(`-Xms`)和最大大小(`-Xmx`),以及年轻代和老年代的比例(`-XX:NewRatio`),可以显著改变GC的行为。通常,增加年轻代的大小可以减少Minor GC的频率,而合理配置老年代的大小则可以减少Full GC的发生。\n\n### 5. 监测应用行为与GC紧密结合\n\n因此,实时监测应用的使用情况与GC日志结合分析十分重要。使用工具如`JVisualVM`、`JConsole`等可以直观地看到内存的使用情况、线程状态等,从而更全面地理解GC对应用的影响。\n\n### 6. 进行性能优化\n\n在了解GC的行为后,可以根据分析结果,考虑实施相应的改进措施,例如:\n\n- **优化代码**:通过减少不必要的对象创建,优化数据结构,降低垃圾产生。\n- **使用对象池**:对于频繁创建和销毁的对象,考虑使用对象池来减少GC压力。\n- **调整GC策略**:如果分析发现特定的GC策略不适合应用,也可以考虑调整GC算法或其参数。\n\n### 7. 持续监控与调整\n\n性能优化是一个持续的过程,应用环境和用户访问行为可能变化,因此需要定期分析GC日志,监控GC性能,并不断调整优化策略。只有这样,我们才能在复杂的运行环境中保持应用的高效运行。\n\n总而言之,通过优化GC日志来诊断和解决GC性能问题,需要深入分析日志、理解GC原理以及监控应用行为,并针对发现的问题进行灵活调整。这一过程不仅是技术的探索,更是对思维与问题解决能力的磨砺。',0,0,0,'2024-12-06 11:59:26','2024-12-27 09:44:03'),(300852,100030,200615,'分页和排序在Spring Boot中就像你在餐厅里点外卖,既想要美味的食物,又想要快速送到。好消息是,Spring Data JPA已经为我们准备好了,让我们轻松地搞定这些烦恼。\n\n首先,确保添加Spring Data JPA依赖。假设你已经在用Maven,pom.xml里加上:\n\n```xml\n\n org.springframework.boot\n spring-boot-starter-data-jpa\n\n```\n\n接下来,搭建一个基础的Entity。假设我们有个 `User` 类:\n\n```java\n@Entity\npublic class User {\n @Id\n @GeneratedValue(strategy = GenerationType.IDENTITY)\n private Long id;\n private String name;\n // 其他字段、getter、setter自己加~\n}\n```\n\n然后,我们需要一个Repository,来执行数据库操作:\n\n```java\npublic interface UserRepository extends JpaRepository {\n}\n```\n\n现在我们要实现分页和排序的魔法。创建一个Service,用来处理分页请求:\n\n```java\n@Service\npublic class UserService {\n\n @Autowired\n private UserRepository userRepository;\n\n public Page getUsers(int page, int size, String sort) {\n Pageable pageable = PageRequest.of(page, size, Sort.by(sort));\n return userRepository.findAll(pageable);\n }\n}\n```\n\n哇哦,简简单单就搞定了!`PageRequest.of(page, size, Sort.by(sort))` 就是你的点餐单,告诉厨房你要几号桌(页),点多少菜(大小),以及按照什么顺序上菜(排序)。\n\n最后,你可以在你的Controller中调用这个Service:\n\n```java\n@RestController\n@RequestMapping(\"/users\")\npublic class UserController {\n\n @Autowired\n private UserService userService;\n\n @GetMapping\n public ResponseEntity> getUsers(@RequestParam int page, \n @RequestParam int size, \n @RequestParam String sort) {\n Page users = userService.getUsers(page, size, sort);\n return ResponseEntity.ok(users);\n }\n}\n```\n\n这样,当你请求 `/users?page=0&size=10&sort=name` 时,Spring Boot会乖乖地把结果以分页和排序的形式返回给你,就像一个听话的外卖小哥。\n\n总之,Spring Boot搞定分页和排序比你想象的要简单得多。只要记得点快递时不要总是选“超快送”,否则你可能会急得像个孩子似的等不及!',0,0,0,'2024-12-06 11:59:35','2024-12-27 09:44:03'),(300853,100083,200789,'访问修饰符是用于控制类中成员(属性和方法)访问权限的一种机制。在C++中,访问修饰符定义了类的成员在类的外部和内部如何被访问。\n\nC++中常见的访问修饰符有三种:\n\n1. **public(公共)**:使用 `public` 修饰符的成员可以被任何地方访问,包括类外部。它就像一个开放的花园,任何人都可以随意进入。\n\n ```cpp\n class MyClass {\n public:\n int publicVar;\n void publicMethod() {\n // 公开方法\n }\n };\n ```\n\n2. **private(私有)**:使用 `private` 修饰符的成员只能在类的内部访问,外部无法直接访问。它就像一个神秘的房间,只有房主可以进入。\n\n ```cpp\n class MyClass {\n private:\n int privateVar;\n void privateMethod() {\n // 私有方法\n }\n };\n ```\n\n3. **protected(保护)**:`protected` 修饰符的成员在类内部和派生类中可以访问,但在类外部不可访问。它就像一个封闭的花园,只有特定的访客(派生类)可以入内。\n\n ```cpp\n class Base {\n protected:\n int protectedVar;\n };\n\n class Derived : public Base {\n public:\n void accessBase() {\n protectedVar = 10; // 可以访问\n }\n };\n ```\n\n总结一下,访问修饰符在C++中扮演着重要的角色,它们有助于实现封装和信息隐藏,使代码更加安全和可维护。你可以根据需要灵活地选择合适的修饰符,让你的类如同一个精巧的乐器,只有合适的人能演奏出优美的旋律。',0,0,0,'2024-12-06 11:59:42','2024-12-27 09:44:03'),(300854,100035,200720,'哦,谈到Java中的并发集合,简直就像把传统集合和超级英雄放在一起!让我们看看这些并发集合是如何超越传统集合的吧!\n\n1. **高并发性能**: 传统集合在多线程中有一个小问题——它们会争夺同一个锁,这就好像所有的超级英雄都想在同一个时刻跳出窗户,结果导致了拥堵。而ConcurrentHashMap等并发集合通过分段锁定(Segment Locking)将锁定区域分开,因此,多个线程可以并行访问不同的段,好比是每个超级英雄有自己的窗口可以跳出去,避免了碰撞!\n\n2. **读操作几乎无锁**: 并发集合的读操作通常不需要加锁,这就像是在图书馆里,无数人可以同时阅读同一本书,而不需要轮流。ConcurrentHashMap 可以在大多数情况下允许多个线程同时读取数据,不用互相打扰。\n\n3. **写操作的优化**: CopyOnWriteArrayList是一个特殊的家伙,它在写的时候,其实是把一个新的数组复制出来,然后替换掉旧数组。这种“写时复制”的策略就像是一群消费者在收银台等候,而收银员总是有人在工作,等会儿再把钱交给他,避免了对老客户的干扰。\n\n4. **线程安全和可见性**: 使用并发集合,数据的可见性问题会减少,就像是一个热锅上的蚂蚁,来回跳动而不会被别人忽视。JVM会确保在多线程环境下,修改后的数据可以被其他线程及时看到。\n\n5. **避免过多的阻塞**: 并发集合设计得好,让阻塞的情况变得少之又少。这就像是每个超级英雄都有的飞行能力,能在空中避开那些碍事的交通堵塞。\n\n综上所述,Java中的并发集合如同经过特别训练的超级英雄,在多线程环境中奋力冲刺,表现得比传统集合更完美。如果你想在代码的战场上打儿卖儿,就选择并发集合,争取成为英雄中的英雄吧!🚀',0,0,0,'2024-12-06 11:59:49','2024-12-27 09:44:03'),(300855,100083,200382,'在Java中,实现HTTP请求的异步处理可以通过多种方式来完成。以下是几种常用的方法,适合不同的场景和需求:\n\n### 1. 使用`CompletableFuture`\n\n`CompletableFuture`是Java 8引入的一个强大工具,可以轻松地处理异步计算。你可以使用它来发送HTTP请求并在请求完成后处理响应。\n\n```java\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.util.concurrent.CompletableFuture;\n\npublic class AsyncHttpRequest {\n public static void main(String[] args) {\n CompletableFuture futureResponse = CompletableFuture.supplyAsync(() -> {\n try {\n URL url = new URL(\"https://api.example.com/data\");\n HttpURLConnection conn = (HttpURLConnection) url.openConnection();\n conn.setRequestMethod(\"GET\");\n int responseCode = conn.getResponseCode();\n\n if (responseCode == HttpURLConnection.HTTP_OK) {\n // 处理响应\n return \"Response received successfully!\";\n } else {\n return \"Failed: \" + responseCode;\n }\n } catch (Exception e) {\n e.printStackTrace();\n return \"Exception occurred: \" + e.getMessage();\n }\n });\n\n // 添加回调处理响应\n futureResponse.thenAccept(response -> {\n System.out.println(\"Response: \" + response);\n });\n\n // 等待异步操作完成\n futureResponse.join();\n }\n}\n```\n\n### 2. 使用`ExecutorService`\n\n你也可以使用`ExecutorService`来管理异步任务。通过提交任务到线程池,你可以并行处理多个HTTP请求。\n\n```java\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\npublic class AsyncHttpRequestWithExecutor {\n public static void main(String[] args) {\n ExecutorService executor = Executors.newFixedThreadPool(10);\n\n executor.submit(() -> {\n try {\n URL url = new URL(\"https://api.example.com/data\");\n HttpURLConnection conn = (HttpURLConnection) url.openConnection();\n conn.setRequestMethod(\"GET\");\n int responseCode = conn.getResponseCode();\n\n if (responseCode == HttpURLConnection.HTTP_OK) {\n System.out.println(\"Response received successfully!\");\n } else {\n System.out.println(\"Failed: \" + responseCode);\n }\n } catch (Exception e) {\n e.printStackTrace();\n }\n });\n\n // 关闭线程池\n executor.shutdown();\n }\n}\n```\n\n### 3. 使用第三方库(如OkHttp)\n\n如果你想要更简单且功能更强大的异步HTTP请求,可以使用像OkHttp这样的库。以下是一个使用OkHttp实现异步HTTP请求的示例:\n\n```java\nimport okhttp3.Call;\nimport okhttp3.Callback;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport okhttp3.Response;\n\nimport java.io.IOException;\n\npublic class AsyncHttpRequestWithOkHttp {\n public static void main(String[] args) {\n OkHttpClient client = new OkHttpClient();\n\n Request request = new Request.Builder()\n .url(\"https://api.example.com/data\")\n .build();\n\n client.newCall(request).enqueue(new Callback() {\n @Override\n public void onFailure(Call call, IOException e) {\n System.out.println(\"Request Failed: \" + e.getMessage());\n }\n\n @Override\n public void onResponse(Call call, Response response) throws IOException {\n if (response.isSuccessful()) {\n System.out.println(\"Response: \" + response.body().string());\n } else {\n System.out.println(\"Failed: \" + response.code());\n }\n }\n });\n }\n}\n```\n\n### 小结\n\n以上方法各有优缺点,选择适合你的项目和需求的方式来实现HTTP请求的异步处理。使用`CompletableFuture`和`ExecutorService`可以在标准Java中实现,而使用像OkHttp这样的库可以大大简化代码,并提供更多功能和更好的性能。快去试试吧!🌟',0,0,0,'2024-12-06 12:00:03','2024-12-27 09:44:03'),(300856,100028,200737,'哦,GC日志的优化,听起来像是要给垃圾回收器做个深度清洁,让它不会再在你的应用程序里随便丢垃圾!别担心,我会逐条教你怎么优化这份“日志”,让你的GC性能问题有个大翻身。\n\n1. **开启GC日志**:首先,想要诊断问题,你得把GC日志打开,是的,就像你想知道家里谁偷吃了零食一样。在JVM启动时加上这些参数:\n ```\n -Xloggc: -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps\n ```\n 这就像给你的小动物装了GPS,随时能追踪它们的“行踪”。\n\n2. **分析GC日志**:用一些工具分析这些日志,比如**GCViewer**或者**GCEasy.io**。差不多就是把你的日记交给侦探,让他们找出谁是小偷。看哪些停顿时间长,频繁的GC事件,或者堆内存的使用情况。\n\n3. **观察暂停时间**:停顿时间过长,可以说是你生活中的“吃饭时间”被无限延长,放弃工作狂人计划,合理设置堆大小和新生代比例,让GC工作更高效。\n ```\n -Xms -Xmx -XX:NewRatio=\n ```\n\n4. **选择合适的GC算法**:Java有几种不同的GC算法,比如G1、Parallel GC和ZGC……就像你在选择饭店一样。如果你是个“低延迟”追求者,那不妨试试ZGC或者 Shenandoah。\n\n5. **避开“Full GC”**:Full GC就像是家庭大扫除,费时费力,还没完没了。确保你有足够的堆内存,偶尔清理和优化代码中无用对象的引用,让它们“淘气”起来,少些无谓的暂停。\n\n6. **监测应用行为**:使用监测工具(像Prometheus + Grafana),你得时刻盯着应用程序的状态。毕竟,谁知道啥时候又会发作呢?\n\n7. **定期复查**:GC优化不是一次性的事情,就像健身一样,得坚持,不然一停下来又回到“原形”(肥胖),定期查看GC的日志和性能数据,才能保持“身材”。\n\n总之,优化GC就是个麻烦活,但操心几步,慢慢调试,最终会发现你的“新生代”会变得更加高效,性能问题自然解决了!赶快行动吧,别再等着垃圾发酵了!',0,0,0,'2024-12-06 12:00:11','2024-12-27 09:44:03'),(300857,100111,200513,'在Java虚拟机(JVM)中,安全点检查(Safepoint Check)是一个至关重要的机制,用于确保在进行某些关键操作(如垃圾回收、线程暂停等)时,所有的执行线程都处于一个安全的状态。当虚拟机需要对堆内存进行管理时,它不能在任何时刻都去处理对象的内存,因为这可能导致数据的不一致性或者丢失。\n\n### 安全点的概念\n\n安全点是执行过程中虚拟机能够安全地暂停线程的特定位置。所有线程在执行到这些点时,JVM可以保证它们的状态不会影响到内存的整体结构。这意味着在进行某些操作时,所有线程可以在安全点停下来,以便进行必要的操作。\n\n### 安全点检查的过程\n\n1. **插入安全点**:当编译器将字节码转换为机器指令时,JVM会在合适的位置插入安全点。这些位置通常是一些不会引发异常的点,例如方法调用、循环头部等。当执行到这些点,线程将检查是否需要进行安全点检查。\n\n2. **检查条件**:每当线程执行到安全点的指令时,它会检查当前的状态。在这个状态下,JVM会询问是否有必要进行安全点操作。例如,如果进行了垃圾回收或者其他必要的维护任务,线程将准备暂停。\n\n3. **进入安全点**:一旦确定需要进入安全点,线程会进行一系列的操作,将自身的状态保存下来。此时,线程会被标记为可暂停状态,并会在适当的时间点被实际暂停。\n\n4. **处理线程**:在所有的线程都被暂停之后,JVM可以安全地执行必要的操作,如清理内存、进行垃圾回收、执行优化等。\n\n5. **恢复线程**:在操作执行完成后,JVM会将所有线程恢复,它们可以继续从之前的安全点恢复执行。恢复后的线程会更新其状态,继续未完成的任务。\n\n### 重要性\n\n安全点检查在多线程和内存管理中非常重要,确保虚拟机操作的原子性和保证数据一致性。没有这个机制,可能会导致内存泄露、崩溃,甚至更糟糕的事情。\n\n总之,安全点检查机制如同一个精密的交通信号灯,确保不同线程之间的高效、顺畅与安全,保驾护航着每一个Java应用的稳定运行。',0,0,0,'2024-12-06 12:00:22','2024-12-27 09:44:03'),(300858,100034,200752,'你有没有注意到在大型项目中,代码的可维护性和可移植性是多么重要?C++中的条件编译指令,如`#ifdef`、`#ifndef`和`#endif`,主要是为了帮助我们在编译时根据特定条件选择性地包含代码段,这样做有什么好处呢?\n\n首先,使用`#ifdef`指令可以让你检查某个宏是否被定义。如果这个宏已定义,那么随后的代码将被编译,那这在什么情况下会特别有用呢?比如,有时候我们想为不同的操作系统或编译器提供特定的实现,难道不应该让代码更加灵活吗?\n\n而`#ifndef`则是用于判断一个宏是否未定义,想过这个指令如何帮助我们防止重复定义头文件吗?这可以避免许多潜在的错误,比如重复声明类或函数,这样的保护措施是不是相当重要?\n\n最后,`#endif`用于结束之前的条件编译,它标志着代码块的结束。当你使用这些条件编译指令时,是否会觉得程序结构变得更加清晰了呢?\n\n所以说,条件编译指令让我们的代码在不同的环境中能够自如地适应,难道这样的灵活性不是它们存在的主要意义吗?',0,0,0,'2024-12-06 12:00:29','2024-12-27 09:44:03'),(300859,100002,200872,'RAII(Resource Acquisition Is Initialization,资源获取即初始化)原则可谓是C++界的“老司机”,简直就是让程序员们在资源管理的浪潮中乘风破浪的秘诀。这个原则强调在对象的构造时获取资源,在对象的析构时释放资源,简直就是把“资源”这个大麻烦打包得乖乖的,不听话就让你自己走人!\n\n在STL(标准模板库)中,RAII的大招被用得淋漓尽致。比如说,你在使用`std::vector`或`std::unique_ptr`这些有点名气的家伙时,完全不用担心资源泄露的问题,因为这些聪明的小家伙在创建的时候就开始了自己的“资源管理”职业生涯。对象生了,资源也随之配齐;对象亡了,资源也一并罢工,毫不留情。\n\n举个简单的例子,当你创建一个`std::vector`时,它在构造的时候会申请这么多空间。当你把它扔掉、释放的时候,它也会把申请的空间干脆利落地归还。STL中的智能指针(比如`std::unique_ptr`和`std::shared_ptr`)也秉承了这个原则,确保你在忘记释放内存时,它们还是能帮你把那个“麻烦”的资源给清理掉。\n\n所以说,RAII就是让你在忙于编程的时候,具备“无后顾之忧”的极致体验,不用担心内存、文件描述符那些煩心事。使用RAII就像是把你的代码写得优雅又帅气,万一有人要挑错,你可以毫不犹豫地甩出“我遵循RAII原则”这张牌,简直是编程界的“王炸”!',0,0,0,'2024-12-06 12:00:35','2024-12-27 09:44:03'),(300860,100093,200066,'哦,进程状态,简直就像一场真人秀,时刻在变化中!我们来看一下主要的几种状态吧:\n\n1. **就绪状态**(Ready):哎呀,进程在这儿等着呢,随时准备上场表演,只等操作系统给个信号,来个CPU时间片就好了。\n\n2. **运行状态**(Running):这下子可以尽情秀了!进程正在CPU上运行,上演自己的“独角戏”。\n\n3. **阻塞状态**(Blocked):哦不,进程被卡住了!可能在等I/O操作完成,像是在排队买奶茶,前面的顾客怎么就是不走呢?\n\n4. **新建状态**(New):进程刚出生,还没来得及上场,正在后台进行初始化。一切准备就绪后就能进入就绪状态。\n\n5. **终止状态**(Terminated):哎,谢幕了,进程表演结束。可能被系统杀掉,也可能自己感谢观众走了。\n\n那么,状态之间是如何转换的呢?\n\n- **从新建到就绪**:进程被创建,像是“打怪升级”第一步,做好准备上场了。\n \n- **从就绪到运行**:操作系统给了CPU时间,哗,一个“剑指龙门”的跃动!\n\n- **从运行到阻塞**:哦,这时候可能是遇到I/O请求了,进程悲催地去排队,心想:我真的只想快点结束这戏。\n\n- **从阻塞到就绪**:I/O操作完成,终于可以从排队中解脱出来,等待再次上场。\n\n- **从运行到就绪**:时间片到,CPU把话筒交还给调度程序。进程只好乖乖退场,像个把戏表演者等待下次机会。\n\n- **从运行到终止**:终究有结束的一天,或许是“完美谢幕”,或许是被突然插票的“白富美”给轰走。\n\n看到没,进程的生命周期就像是过山车,瞬息万变,让人目不暇接!谁说编程无趣?这可是高级黑的娱乐界哦!',0,0,0,'2024-12-06 12:00:47','2024-12-27 09:44:03'),(300861,100090,201014,'要在C++中确保一个函数在每个编译单元中只出现一次,有一种常见的方法就是使用`inline`关键字。这样做的效果就像是在告诉编译器:“嘿,伙计,不要重复我的这个函数,哪怕你看到它了100次!” \n\n实际上,你可以将该函数定义为`inline`,这样编译器在不同的编译单元之间就不会因为重复定义而报错(虽然这并不意味着一定只有一个实例)。这种方法特别适合小型的函数实现,比如那些只用来计算简单结果的函数,因为它们会在每次使用时被“内联”,避免了调用开销。\n\n当然,这不是完美的解决方案,因为如果你的函数逻辑很复杂,编译器可能会完全忽视`inline`的建议,仍然为每个编译单元生成一份。\n\n如果你想要更绝对的控制方式,可以使用“单例模式”,但是对于普通函数来说,常规地使用`inline`或者将实现放在头文件中是比较常见的。此外,确保使用`#include`保护(如`#pragma once`或“头文件保护宏”)来避免包含多次也是很重要的。\n\n综上所述,你的代码片段可能看起来像这样:\n\n```cpp\n// my_function.hpp\n#ifndef MY_FUNCTION_HPP\n#define MY_FUNCTION_HPP\n\ninline void my_function() {\n // 函数实现\n}\n\n#endif // MY_FUNCTION_HPP\n```\n\n然后在你的cpp文件中随意引入,但要记得这只是一个保证函数实现位置相同的简单做法,真正要强制设计某些东西保持单实例的还是单例模式的使用。再不济,还是空口白话的给编译器打下招呼:“记得!我只要一个就好!”',0,0,0,'2024-12-06 12:00:53','2024-12-27 09:44:03'),(300862,100069,201089,'在QT中,使用`QTreeView`和`QStandardItemModel`来展示层次化数据是一种常见的做法。`QTreeView`提供了一个可视化的树形结构,而`QStandardItemModel`则负责管理这个树形数据的结构和内容。以下是实现这一功能的一些关键步骤:\n\n### 步骤一:导入必需的模块\n\n首先,确保你已经导入了QT的相应模块:\n\n```cpp\n#include \n#include \n#include \n#include \n```\n\n### 步骤二:创建数据模型\n\n你需要创建一个`QStandardItemModel`实例,并且通过添加`QStandardItem`来构建层次化的数据结构。\n\n```cpp\nQStandardItemModel *model = new QStandardItemModel;\n\n// 设置表头\nmodel->setHorizontalHeaderLabels(QStringList() << \"Item Name\" << \"Description\");\n\n// 创建根项\nQStandardItem *rootItem = model->invisibleRootItem();\n\n// 创建子项(根据需要构建层次结构)\nQStandardItem *item1 = new QStandardItem(\"Item 1\");\nitem1->appendRow(new QStandardItem(\"Sub Item 1-1\"));\nitem1->appendRow(new QStandardItem(\"Sub Item 1-2\"));\n\nQStandardItem *item2 = new QStandardItem(\"Item 2\");\nitem2->appendRow(new QStandardItem(\"Sub Item 2-1\"));\n\nrootItem->appendRow(item1);\nrootItem->appendRow(item2);\n```\n\n### 步骤三:设置QTreeView\n\n创建`QTreeView`并将其与模型关联。\n\n```cpp\nQTreeView *treeView = new QTreeView;\ntreeView->setModel(model);\n```\n\n### 步骤四:显示视图\n\n最后,创建一个应用程序并显示视图。\n\n```cpp\nQApplication app(argc, argv);\ntreeView->setWindowTitle(\"QTreeView Example\");\ntreeView->show();\n\nreturn app.exec();\n```\n\n### 完整示例\n\n将上述步骤合并为一个完整的示例:\n\n```cpp\n#include \n#include \n#include \n#include \n\nint main(int argc, char *argv[]) {\n QApplication app(argc, argv);\n\n // 创建树视图\n QTreeView *treeView = new QTreeView;\n\n // 创建模型\n QStandardItemModel *model = new QStandardItemModel;\n model->setHorizontalHeaderLabels(QStringList() << \"Item Name\" << \"Description\");\n\n // 创建根项\n QStandardItem *rootItem = model->invisibleRootItem();\n\n // 创建层次化数据\n QStandardItem *item1 = new QStandardItem(\"Item 1\");\n item1->appendRow(new QStandardItem(\"Sub Item 1-1\"));\n item1->appendRow(new QStandardItem(\"Sub Item 1-2\"));\n\n QStandardItem *item2 = new QStandardItem(\"Item 2\");\n item2->appendRow(new QStandardItem(\"Sub Item 2-1\"));\n\n rootItem->appendRow(item1);\n rootItem->appendRow(item2);\n\n // 设置模型到树视图\n treeView->setModel(model);\n treeView->setWindowTitle(\"QTreeView Example\");\n treeView->show();\n\n return app.exec();\n}\n```\n\n### 思考\n\n在构建层次化数据时,我们不仅是在组织数据,更是在创造一种结构,使得信息能够以更为直观且便捷的方式被理解和访问。每一个子项都仿佛是我们生活中的一个小故事,它们共同构成了更为复杂的局面。通过这种方式,我们不仅传达了信息,也展示了思维与组织的力量。真正的智慧在于能够从复杂中提炼出简单的道理。',0,0,0,'2024-12-06 12:01:03','2024-12-27 09:44:03'),(300863,100030,200420,'在Java中,线程泄漏就像是你家厨房的油烟机不工作,结果你在做饭时烟雾弥漫,最终只剩下了一片狼藉。为了避免线程泄漏,我们需要进行一些“厨房清洁”活动。下面是一些检测和处理线程泄漏的技巧:\n\n### 如何检测线程泄漏?\n\n1. **监控线程数**:\n 使用 `ThreadMXBean` 来监控当前活跃线程的数量,看看是否不断增加。就像看着你的电费账单,不应该突然暴涨!\n\n ```java\n ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();\n int threadCount = threadMXBean.getThreadCount();\n System.out.println(\"Current thread count: \" + threadCount);\n ```\n\n2. **JVisualVM**:\n 使用 Java 自带的 `JVisualVM` 工具,它能帮你监控线程状态,查看线程的生命周期,甚至能追溯和分析线程的堆栈。如果你不小心把厨房的锅盖盖上了,JVisualVM 会告诉你蒸汽何时来了。\n\n3. **线程dump**:\n 使用命令 `jstack` 查看当前线程的信息,检查有没有长期没有执行的线程活动。就像是查找厨房里是否有多余的锅碗瓢盆。\n\n### 如何处理线程泄漏?\n\n1. **合理使用线程池**:\n 使用线程池(例如 `ExecutorService`)来管理线程,而不是每次需要时都创建新线程。这就像是雇了一个厨师而不是每次自己下厨,既节省资源又减少负担。\n\n ```java\n ExecutorService executorService = Executors.newFixedThreadPool(10);\n ```\n\n2. **确保线程结束**:\n 在线程执行完成后,确保合适地结束它们。如果你做完一餐了,就记得把锅洗干净,而不是让它静静地呆在那儿。使用 `shutdown()` 或 `shutdownNow()` 方法来优雅地结束线程池。\n\n ```java\n executorService.shutdown();\n ```\n\n3. **使用 `Future` 来跟踪任务**:\n 提交任务时使用 `Future`,这样能获取任务的状态,确保在不需要时及时取消任务。\n\n ```java\n Future future = executorService.submit(() -> {\n // YOUR CODE HERE\n });\n future.cancel(true);\n ```\n\n4. **定期清理无用的引用**:\n 在持有线程引用的地方,定期清理无用的线程,比如在应用关闭时释放对那些已经完成或不再需要的线程的引用。\n\n只要遵循这些步骤,你就能在Java中有效地检测和处理线程泄漏,保持程序的“厨房”干净整洁不再油烟四起!',0,0,0,'2024-12-06 12:01:14','2024-12-27 09:44:03'),(300864,100118,200914,'在C++中,`vector` 是一个动态数组容器,用于存储可变大小的元素集合。了解 `vector` 的内存实现有助于你更好地使用它,充分利用其特性。\n\n1. **内存分配**:`vector` 在内部使用一块动态分配的连续内存来存储元素。这意味着,当你创建一个 `vector` 对象时,它会在堆上分配一块空间以存放元素。\n\n2. **容量和大小**:\n - **size**:表示当前 `vector` 中存储的元素数量。\n - **capacity**:表示 `vector` 在不需要重新分配内存的情况下,可以存储的元素数量。`capacity` 通常大于或等于 `size`。\n\n3. **扩展和缩减**:\n - 当你向 `vector` 中添加超出其当前 `capacity` 的元素时,`vector` 会自动分配更大的内存(通常是当前容量的两倍),并将原有元素复制到新内存中,然后释放掉旧的内存。\n - 这种内存管理方式确保了 `vector` 在大多数情况下能够高效地处理动态大小。\n\n4. **内存释放**:当 `vector` 被销毁或清空时,它会自动释放之前分配的内存。因此,在使用完 `vector` 后,你不需要手动释放内存,这使得内存管理更加简便。\n\n5. **元素的存储**:`vector` 的元素是以连续的内存块存储的,这样可以利用指针进行高效的随机访问。\n\n通过这种内存管理方式,`vector` 提供了灵活性与效率的平衡。虽然在扩展时可能会有一些性能开销,但整体上,`vector` 是一个非常高效且易于使用的容器。\n\n你若还有更深入的问题,或者其他的疑问,随时欢迎你提出来,一起交流学习!',0,0,0,'2024-12-06 12:01:21','2024-12-27 09:44:03'),(300865,100042,200757,'在C++中,引用折叠(Reference Collapsing)是一个关键概念,尤其是在处理模板和类型推导时。它描述了在不同情况下,引用类型如何相互结合以形成最终的引用类型。引用折叠在C++11引入的右值引用和移动语义的背景下变得尤为重要。\n\n引用折叠的规则如下:\n\n1. `T& &` 被折叠为 `T&`\n2. `T& &&` 被折叠为 `T&`\n3. `T&& &` 被折叠为 `T&`\n4. `T&& &&` 被折叠为 `T&&`\n\n这样的设计使得我们在设计函数模板和泛型代码时,可以更灵活地处理各种类型的引用。\n\n### 示例\n\n让我们通过一个简单的示例来说明引用折叠的效果。\n\n```cpp\n#include \n#include \n\ntemplate\nvoid printType() {\n if constexpr (std::is_same::value) {\n std::cout << \"T is int&\" << std::endl;\n } else if constexpr (std::is_same::value) {\n std::cout << \"T is int&&\" << std::endl;\n } else {\n std::cout << \"T is some other type\" << std::endl;\n }\n}\n\ntemplate\nvoid deduceRefFold(T&& arg) {\n printType();\n}\n\nint main() {\n int a = 5;\n\n // lvalue reference\n deduceRefFold(a); // 输出 \"T is int&\"\n\n // rvalue reference\n deduceRefFold(5); // 输出 \"T is int&&\"\n\n // forwarding reference (universal reference)\n deduceRefFold(std::move(a)); // 输出 \"T is int&\"\n\n return 0;\n}\n```\n\n在这个示例中,`deduceRefFold` 函数接受一个转发引用(即 C++11 中的 universal reference),基于传入参数的类型,它会将 `arg` 的类型推导为具体的引用类型。这展示了引用折叠的规则,特别是如何对 lvalue 和 rvalue 应用不同的规则。\n\n### 总结\n\n引用折叠帮助我们简化和明确引用的使用,使得类型推导更加一致。在面对复杂模板和参数转发时,理解引用折叠的规则至关重要。这不仅提升了代码的可读性,还使得代码更加灵活和强大。在思考这个概念时,不妨反思一下:在我们的编程实践中,如何利用这些规则来设计更加优雅的代码呢?',0,0,0,'2024-12-06 12:01:34','2024-12-27 09:44:03'),(300866,100034,200932,'内存池分配器就像是一个精心组织的图书馆,它专门为某一类特定的书籍(这里指的是内存块)提供服务。这种分配器通过将内存预先划分成多个固定大小的块,来提高内存管理的效率和速度。\n\n**让我们来看看它是如何工作的吧:**\n\n1. **内存池的建立**:在开始工作之前,内存池分配器会向操作系统申请一大块连续的内存区域,这就像图书馆为了放置书籍而建立了一个大厅。然后,这块内存会被分割成多个大小相等的小区块,每个小区块就像书架上的一本书,准备好被借阅。\n\n2. **分配过程**:当程序需要使用内存时,它会向内存池分配器请求内存。分配器就会从空闲的内存块中挑选一个,把它封给请求者。这种方式非常快速,因为分配器只需将一个指针移动到下一个可用区块,而不需要像传统分配那样去寻找空闲内存。\n\n3. **归还过程**:当程序使用完内存块后,它会将其归还给内存池。这就像借书后将它放回书架上。归还的过程也相当快速,因为分配器会简单地将这个内存块标记为“可用”。\n\n4. **避免碎片**:内存池分配器还有一个显著的优点,就是避免了内存碎片的问题。因为所有的内存块都是相同的大小,只要有空闲块,就能直接使用。这大大节省了在管理和查找内存时的时间。\n\n5. **高效管理**:最终,内存池分配器不仅提高了内存的分配和释放效率,还减少了内存碎片,使得程序的性能更加稳定,特别是在高频率的内存分配和释放场景中,比如在游戏开发或大数据处理时。\n\n综上所述,内存池分配器就像一个高效的图书馆管理员,能够快速而有序地提供和管理书籍(内存),使得整个程序在运行过程中更加顺畅无阻!',0,0,0,'2024-12-06 12:01:44','2024-12-27 09:44:03'),(300867,100109,200402,'哦,synchronized和ReentrantLock,真是一对冤家,都是用来实现线程同步的,但性格完全不同。\n\n首先,synchronized是Java的内置特性,像是一个约定俗成的老好人。它简单、易用,不需要太多配置。只要给方法或者代码块加上synchronized关键字,它就会自动帮你处理锁,顺便顺便带走一些性能。\n\n而ReentrantLock呢,简直就是个自恋狂,它来自于java.util.concurrent包,是个更加灵活的家伙。你可以自定义锁的申请和释放,甚至可以让它加个超时限制。如果synchronized像是个宁静的乡村,那么ReentrantLock就像是一个闹市区的总裁,随时准备进行高效的竞争。\n\n再来说说可重入性。synchronized是自带的,线程可以多次获得这个锁,而ReentrantLock为了满足这种要求,更是用“可重入”这个词给自己贴金,专门设计了相关方法。\n\n还有个小细节,synchronized是可见性的,只要锁住了,其他线程就得乖乖等着。但是ReentrantLock呢,宽容许多,它还支持条件变量,可以让复杂的线程协调变得稍稍优雅一点。\n\n总结一下,synchronized就像一位简单朴实无华的邻居,而ReentrantLock则像是一位时髦的市场精英,提供了一堆定制化选项,但用起来确实稍微复杂了点。所以,选哪个锁,得看你是不是一个需要碰撞风格的整活选手了!',0,0,0,'2024-12-06 12:01:50','2024-12-27 09:44:03'),(300868,100001,200331,'哈哈,看来你在挑灯夜战,想要把编码和响应内容调教得服服帖帖的,真是个好奇心满满的孩子!别急,这里有几个小妙招,让你轻轻松松设置请求的编码和响应内容类型。\n\n### 1. 请求的编码\n如果你在用 **HTTP** 请求,特别是 JavaScript 的 `fetch` API 之类的,那你可以在请求的头部(Headers)里指定 `Content-Type`。这样服务器就不会再问你是不是“外星人”了。\n\n```javascript\nfetch(\'你的接口地址\', {\n method: \'POST\', // 或其他请求方法\n headers: {\n \'Content-Type\': \'application/json; charset=utf-8\' // 这里设置编码\n },\n body: JSON.stringify({ /* 你的数据 */ }),\n});\n```\n\n如果你玩的是 **Python requests** 库,没问题!你也可以这样设置:\n\n```python\nimport requests\n\nresponse = requests.post(\n \'你的接口地址\',\n json={\'key\': \'value\'}, # 这里是你的数据\n headers={\'Content-Type\': \'application/json; charset=utf-8\'}\n)\n```\n\n### 2. 响应内容的类型\n当谈到响应内容的类型时,通常是由服务器决定的,但你可以设置接受的格式。比如说,你想要JSON格式,`Accept` 头来帮你!依然是 JavaScript 的 `fetch`:\n\n```javascript\nfetch(\'你的接口地址\', {\n method: \'GET\',\n headers: {\n \'Accept\': \'application/json\' // 让我看到JSON格式的回应\n }\n});\n```\n\n在 Python 中,也很类似:\n\n```python\nresponse = requests.get(\'你的接口地址\', headers={\'Accept\': \'application/json\'})\n```\n\n### 总结\n就像选衣服一样,合适的编码和响应类型才能让你在网络的“时尚秀”上脱颖而出,不然就成了“奇葩”了!记得在设置请求和响应时,仔细检查一下你的头部信息,让服务器知道你是个懂行的人哦!',0,0,0,'2024-12-06 12:02:02','2024-12-27 09:44:03'),(300869,100039,200784,'虚函数的作用在于实现多态性,这是面向对象编程中的一个核心特性。虚函数允许我们在基类中声明一个接口,而在派生类中以不同的方式实现这个接口,从而使得同一函数调用可以在运行时根据对象的实际类型进行动态的绑定。\n\n需要虚函数的原因主要可以归纳为以下几点:\n\n1. **实现多态性**:多态性使得我们可以以统一的接口去处理不同的对象。例如,假设有一个基类`Animal`,它有一个虚函数`makeSound()`,不同的派生类如`Dog`和`Cat`可以实现这个虚函数,分别发出“汪汪”和“喵喵”的声音。这样,我们可以接受一个`Animal`类型的指针或引用来调用`makeSound()`,而不需要知道具体是什么类型的动物。\n\n2. **代码的可扩展性**:通过使用虚函数,新的派生类可以在不修改原有代码的情况下被添加到系统中。当将新类引入系统时,我们只需确保它继承自基类并实现相应的虚函数,其他组件依旧可以如常工作。\n\n3. **接口的设计**:虚函数为设计接口提供了灵活性,使得开发者能够定义功能而不需要具体实现的细节。这种抽象设计可以简化代码的管理,并提高可读性。\n\n4. **运行时决策**:虚函数使得程序能在运行时判断对象的类型,允许程序根据不同对象的特性做出不同的行为决策,而不必在编译时硬编码特定的逻辑。\n\n总之,虚函数不仅是实现多态性的技术手段,更是支持灵活、可维护程序设计的重要工具。在面向对象编程中,它能够使我们的程序更具适应性与可拓展性。正如人生中的选择与可能性,虚函数为程序赋予了自由的伸展,促使它们在不同的场景中展现各自独特的面貌。',0,0,0,'2024-12-06 12:02:09','2024-12-27 09:44:03'),(300870,100039,201212,'在OpenGL中,使用Frame Buffer Object(FBO)来实现后处理效果的流程就像是在画一幅画,然后再对这幅画进行装饰。我们可以通过以下步骤来实现这一过程:\n\n### 1. 创建和绑定FBO\n\n首先,我们需要创建一个FBO并把它绑定上。想象一下这就像建立一个画布,用于后续的绘制。\n\n```cpp\nGLuint fbo;\nglGenFramebuffers(1, &fbo);\nglBindFramebuffer(GL_FRAMEBUFFER, fbo);\n```\n\n### 2. 创建一个纹理作为颜色附件\n\n接下来,我们要创建一张纹理,这张纹理将用于存储FBO渲染的结果。这就像是选择一种颜料来填满我们的画布。\n\n```cpp\nGLuint textureColorBuffer;\nglGenTextures(1, &textureColorBuffer);\nglBindTexture(GL_TEXTURE_2D, textureColorBuffer);\nglTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\nglBindTexture(GL_TEXTURE_2D, 0);\n```\n\n### 3. 将纹理附加到FBO\n\n将创建的纹理附加到FBO上。这样,所有在这个FBO上的绘制操作将会被写入到这张纹理中。\n\n```cpp\nglBindFramebuffer(GL_FRAMEBUFFER, fbo);\nglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorBuffer, 0);\n```\n\n### 4. 检查FBO的完整性\n\n检查FBO的状态,确保它可以正常使用。这一步骤就像是在仔细检查画布是否准备好接受颜料。\n\n```cpp\nif (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {\n // 错误处理\n}\n```\n\n### 5. 渲染场景到FBO\n\n此时,我们可以开始渲染场景了,像是在我们的画布上尽情挥洒创意。\n\n```cpp\nglBindFramebuffer(GL_FRAMEBUFFER, fbo);\nglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n// 在这里绘制你的场景\nglBindFramebuffer(GL_FRAMEBUFFER, 0); // 解绑FBO,恢复默认帧缓冲\n```\n\n### 6. 使用纹理进行后处理\n\n现在,我们可以使用之前生成的纹理进行后处理效果。例如,我们可以将这个纹理绘制到全屏四边形上,同时应用一些着色器以获取想要的效果。就如同在完成的画作上涂上一层亮丽的保护膜。\n\n```cpp\nglClear(GL_COLOR_BUFFER_BIT);\nglUseProgram(postProcessShader);\nglBindTexture(GL_TEXTURE_2D, textureColorBuffer);\n// 绘制全屏四边形\n```\n\n### 7. 释放资源\n\n当一切完成后,不要忘记释放我们分配的资源,就像是收拾画具,给创作留个美好的收尾。\n\n```cpp\nglDeleteFramebuffers(1, &fbo);\nglDeleteTextures(1, &textureColorBuffer);\n```\n\n通过这些步骤,你就能够利用FBO在OpenGL中实现丰富的后处理效果,比如模糊、辉光和色彩校正等。这个过程并不复杂,但却给了你无限的创作空间!',0,0,0,'2024-12-06 12:02:22','2024-12-27 09:44:03'),(300871,100112,200645,'Spring Cloud Config 是一个用于为分布式系统提供服务器端和客户端配置的工具。它的核心目的是集中管理应用程序的配置文件,简化分布式系统中的配置管理和版本控制。\n\n### 如何管理分布式配置\n\n1. **配置服务器**:\n Spring Cloud Config Server 是配置管理的中心,负责从各种后端存储(如 Git、SVN、文件系统等)读取配置文件。通过将应用程序的配置存储在这些后端,Spring Cloud Config Server 能够提供版本控制和历史跟踪。\n\n2. **客户端自动化**:\n Spring Cloud Config Client 是配置的消费者,集成在每个微服务中。客户端会在启动时向 Config Server 请求相应的配置文件。这使得每个服务都可以轻松获取到它们所需的配置信息。\n\n3. **动态更新**:\n Spring Cloud Config 支持动态刷新配置。可以通过 Spring Actuator 结合 Spring Cloud Bus,实现配置信息的实时更新,而无需重启服务。这种能力使微服务在配置更新时能够保持高度的可用性。\n\n4. **环境和标签管理**:\n 使用 Spring Cloud Config,开发者可以为不同的环境(如开发、测试、生产)管理不同的配置文件,甚至为同一应用的不同版本(标签)提供各自的配置。这种灵活性让应用在不同的运行环境中都能轻松地获取到合适的配置。\n\n5. **安全性**:\n Spring Cloud Config 提供了一种机制,可以将敏感信息(如数据库密码、API 密钥)加密存储,并在客户端请求时安全解密。这样可以有效保护配置中的敏感数据。\n\n### 小结\n\n通过 Spring Cloud Config,分布式系统中的配置管理变得如同悠扬的春风,无论是集中管理、动态更新,还是多环境支持,都为微服务架构的灵活性和稳定性增添了强大的动力。在这个高度互联的世界里,Spring Cloud Config 就像是一位聪慧的调度员,为繁忙的微服务 orchestra 指挥出和谐的旋律。',0,0,0,'2024-12-06 12:02:33','2024-12-27 09:44:03'),(300872,100038,200119,'你是否想知道全文索引在MySQL中为什么会特别有用呢?全文索引是一种特殊类型的索引,主要用于加速对大量文本数据的搜索。你是不是会好奇,如何实践这一功能呢?\n\n首先,你需要创建一个支持全文索引的表,通常使用`FULLTEXT`关键字。比如,你是否记得如何创建一个表并加上这个索引呢?\n\n```sql\nCREATE TABLE articles (\n id INT NOT NULL AUTO_INCREMENT,\n title VARCHAR(255) NOT NULL,\n body TEXT NOT NULL,\n PRIMARY KEY (id),\n FULLTEXT (title, body)\n);\n```\n\n在这个例子中,我们对标题和正文字段都创建了全文索引。这样做的目的是否是为了提高搜索效率呢?\n\n接着,你可能想知道如何执行搜索。你可以使用`MATCH()`和`AGAINST()`函数进行查询,它们在处理全文索引时会特别高效。比如:\n\n```sql\nSELECT * FROM articles\nWHERE MATCH(title, body) AGAINST(\'MySQL全文索引\' IN NATURAL LANGUAGE MODE);\n```\n\n你是不是在想,使用这个查询语句之后,可以返回如何的结果呢?通过这种方式,你可以找到包含“ MySQL”和“全文索引”的文档。\n\n最后,你是否考虑过全文索引在复杂查询中的应用?例如,使用布尔模式来进行更高级的搜索,或者在对大数据量进行分析时,不同的配置对于性能的影响又会如何?\n\n这样说来,针对你的具体需求,是否有任何特定的场景或问题需要更深入的探讨呢?',0,0,0,'2024-12-06 12:02:46','2024-12-27 09:44:03'),(300873,100117,200788,'当然可以!让我们一起了解这几个概念,帮助你更好地理解它们的区别。\n\n1. **重载(Overload)**:\n - 重载是指在同一个类中,多个方法具有相同的名字,但参数列表(参数类型、数量或顺序)不同。重载通常用于提高代码的可读性和灵活性。\n - 例如,一个类可以有两个名为 `add` 的方法,一个接受两个整数参数,另一个接受两个浮点数参数。\n\n ```java\n class Calculator {\n int add(int a, int b) {\n return a + b;\n }\n\n double add(double a, double b) {\n return a + b;\n }\n }\n ```\n\n2. **重写(Override)**:\n - 重写是指子类对父类中已有的方法进行重新实现。重写允许子类提供特定的实现,同时保持方法的名称和参数列表不变。\n - 这通常用来实现多态性,使得子类可以提供不同于父类的行为。\n\n ```java\n class Animal {\n void sound() {\n System.out.println(\"Some sound\");\n }\n }\n\n class Dog extends Animal {\n @Override\n void sound() {\n System.out.println(\"Bark\");\n }\n }\n ```\n\n3. **隐藏(Hiding)**:\n - 隐藏是指子类中定义的新静态变量或方法,具有与父类中同名的静态变量或方法。这种情况下,子类的定义会\"隐藏\"父类的定义,但它并不是重写,因为静态方法属于类,而不是实例。\n - 在访问时,如果通过子类调用,就会调用子类的版本;如果通过父类调用,就会调用父类的版本。\n\n ```java\n class Parent {\n static void display() {\n System.out.println(\"Parent display\");\n }\n }\n\n class Child extends Parent {\n static void display() {\n System.out.println(\"Child display\");\n }\n }\n ```\n\n总结一下:重载是同一个类中方法名称相同但参数不同,重写是子类对父类方法的重新实现,而隐藏是子类中定义的静态方法或变量隐藏了父类相应的部分。掌握这些概念后,你一定能更自信地处理相关的编程任务!如果还有疑问,随时欢迎问我哦!',0,0,0,'2024-12-06 12:02:56','2024-12-27 09:44:03'),(300874,100010,200587,'哎呀,国际化消息要怎么搞,这可是个大问题。不过别担心,让我这个“程序员界的调侃大师”来给你拆解一下。\n\n首先,Spring框架里有个叫 `MessageSource` 的东东,它负责处理国际化消息。要让你的小程序能够根据不同的语言来“说话”,你首先得自定义一个 `MessageSource`,这就像你给菜单添加了多种语言,顾客来了可以选着点。\n\n### 1. 创建 MessageSource 的 Bean\n\n首先,你需要在你的配置类或者 XML 中定义一个 `MessageSource` 的 bean。以下是Java配置的例子:\n\n```java\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.support.ResourceBundleMessageSource;\n\n@Configuration\npublic class MessageConfig {\n\n @Bean\n public ResourceBundleMessageSource messageSource() {\n ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();\n messageSource.setBasename(\"messages\"); // 这儿就是你的属性文件名\n messageSource.setDefaultEncoding(\"UTF-8\"); // 你肯定不想乱码\n return messageSource;\n }\n}\n```\n\n这里,你是不是感觉自己的 `messages.properties` 文件正在向你招手。别手软,去创建它吧!\n\n### 2. 使用 MessageSourceAccessor\n\n接下来,你需要让你的程序能够使用 `MessageSourceAccessor`。这个Accessor就像是一个轻便的门卫,可以帮你拿到想要的消息。我们需要在你的服务或控制器中注入它:\n\n```java\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.MessageSource;\nimport org.springframework.context.MessageSourceAware;\nimport org.springframework.context.support.MessageSourceAccessor;\nimport org.springframework.stereotype.Service;\n\n@Service\npublic class MyService implements MessageSourceAware {\n\n private MessageSourceAccessor messageSourceAccessor;\n\n @Autowired\n public void setMessageSource(MessageSource messageSource) {\n this.messageSourceAccessor = new MessageSourceAccessor(messageSource);\n }\n\n public String getMessage(String code) {\n return messageSourceAccessor.getMessage(code);\n }\n}\n```\n\n### 3. 使用消息\n\n现在,您已经拥有了 `MyService`,可以通过调用 `getMessage` 方法来获取国际化消息了。就像这段代码:\n\n```java\nString message = myService.getMessage(\"welcome.message\");\n```\n\n如果你有 `messages.properties`, 以及对应的诸如 `messages_zh.properties` 的语言文件,哇哦,神奇的事情发生了!根据用户的语言设置,适合的消息就会被选出来。\n\n### 4. 结尾调侃\n\n这下可好,你的程序立马就变得有文化,能说多种语言了!不过,别忘了当你的用户问“你们怎么这么多语言支持”、“请问你们的Backend是在月球上找的么”时,努力把自己藏起来,毕竟代码再漂亮,人还是要脸的。',0,0,0,'2024-12-06 12:03:12','2024-12-27 09:44:03'),(300875,100095,200873,'智能指针是一种封装了原始指针的类,旨在自动管理对象的生命周期,防止内存泄漏和指针悬挂。简单来说,智能指针就是一个会帮你打理\"朋友\"的好管家!\n\n我们可以想象一下,普通指针就像一个需要你亲自照顾的小宠物,你得在它身边时刻关注它的饮食和健康,避免它迷路(内存泄漏)或者得病(悬空指针)。而智能指针就像一个聪明的保姆,能够自动处理这些细微的琐事,让你可以更轻松地专注于其他事情。\n\n智能指针的主要作用有:\n\n1. **自动释放内存**:当智能指针的作用域结束时,它会自动释放所指向的对象,从而避免内存泄漏。\n\n2. **管理共享所有权**:通过 `shared_ptr` 等类型,多个指针可以共享同一个对象,引用计数机制确保对象在没有指针指向它时被释放。\n\n3. **唯一所有权**:`unique_ptr` 则保证某个资源只能被一个指针拥有,防止多个指针同时操作同一块内存造成的混乱。\n\n4. **更安全的异常处理**:在有异常发生的情况下,智能指针能够确保即使在离开作用域时也能够自动释放资源,确保资源不被占用。\n\n总的来说,智能指针是现代C++编程中一个不可或缺的良好实践,帮助开发者减少内存管理的负担,让代码更加安全与易于维护。就像让你的人生多了一个好助手,让你能够更专注、更高效地努力追梦!',0,0,0,'2024-12-06 12:03:19','2024-12-27 09:44:03'),(300876,100074,200507,'在JVM(Java Virtual Machine)中,ABI(Application Binary Interface)可以看作是应用程序与操作系统之间的一座桥梁,充当着应用程序和底层硬件、操作系统之间的联系。\n\n想象一下,你在电影院看一部电影,屏幕上播放着精彩的画面,而观众是坐在座位上期待的观众。JVM就像是这个电影院的放映机,它负责把Java字节码变成可以执行的机器码。而ABI则是它能够和观众(操作系统和硬件)进行沟通的语言和协议。\n\n具体来说,ABI定义了以下几个方面:\n\n1. **数据类型**:它说明了各种数据类型的大小和布局,比如一个整型变量占用多少个字节,这对数据的读取和存储至关重要。\n\n2. **调用约定**:ABI指定了函数如何被调用,参数如何传递、返回值如何处理等。这就好比在电影院里,观众们需要遵循一些规定,比如安静观影、不要随便走动,才能保证大家都能享受这场电影。\n\n3. **系统调用的接口**:ABI提供了一组规范,允许应用程序能调用操作系统提供的功能。这就像是观众能够直接向电影院的工作人员请求服务,比如买爆米花或询问放映时间。\n\n在Java生态中,ABI同样有着重要的作用。例如,当Java程序需要调用本地方法(Native Method)或使用JNI(Java Native Interface)时,它们就需要遵循特定的ABI,才能顺利地在JVM与操作系统之间进行交互。\n\n总之,ABI就像是JVM与底层世界的一种交流方式,确保了程序可以顺畅地运行在不同的环境中,犹如在不同的电影院中都能欣赏到同一部电影的精彩内容!',0,0,0,'2024-12-06 12:03:29','2024-12-27 09:44:03'),(300877,100005,200372,'好的,想象一下你在餐厅吃饭,点了一道菜后,服务员告诉你:“嘿,您可以继续点其他菜,不用每次都叫我!”这就是HTTP的持久连接(Keep-Alive),也称为“持续连接”。\n\n在HTTP/1.0中,每次请求,服务员(服务器)都会把一道菜(HTTP响应)送到你面前,吃完后,还得再叫他来点下一道,而这会浪费很多时间和精力。不过,HTTP/1.1引入了持久连接,允许一个连接在多个请求和响应之间保持打开状态,这样你就不需要每次都呼叫服务员,只要慢慢享受你的美食(数据)就好了!\n\n### 持久连接的优势:\n\n1. **减少延迟**:就像刚提到的那样,不用每次都呼叫服务员,减少了等待时间!HTTP请求和响应之间可以快速传输,从而提高了用户体验。\n\n2. **节省资源**:每次建立和关闭连接都像是重新准备餐具,浪费时间和能量。持久连接让你在一套餐具上享用多道菜,减少了服务器和客户端的负担。\n\n3. **提高吞吐量**:因为连接保持活动,多个请求可以同时发送,像是围着桌子大快朵颐,一口气吃下好几道菜,效率高得要命!\n\n4. **降低网络拥塞**:使用持久连接就像是直接在厨房里用餐,不用跑回桌子旁边,一来一回的,减少了额外的数据传输,从而降低了网络拥堵。\n\n总的来说,HTTP的持久连接让网络像一场丰盛的自助餐,让你不再愁眉苦脸地等着服务员,而是畅享每一道精致的菜品(数据)!所以,HTTP的持久连接真的是让人“食”之有道啊!😄',0,0,0,'2024-12-06 12:03:37','2024-12-27 09:44:03'),(300878,100025,201121,'在C++网络编程中,长连接和短连接是两种常用的连接模式,各自有不同的特点和适用场景。让我们来看一下它们的基本区别和适用情况:\n\n### 短连接\n\n短连接是指在每次通信之后,客户端与服务器之间的连接会被关闭。也就是说,客户端在发送请求并接收响应后,会立刻断开与服务器的连接。\n\n**特点:**\n- 每次请求都需要重新建立连接,增加了连接的开销。\n- 适用于请求-响应模式的场景,例如HTTP/1.0。\n- 非常适合处理请求频率相对较低的场合,或者请求之间没有严格的时序要求。\n\n**适用场景:**\n- 短期的、高频率的请求,比如一些简单的API调用。\n- 有时候也用于一些偶发的请求,这样可以避免长时间占用资源。\n\n### 长连接\n\n长连接则是客户端与服务器之间的连接在发送请求后并不会立即关闭,而是保持打开状态,等待后续的数据传输。只有在一定条件下(如超时、显式关闭等)才会关闭连接。\n\n**特点:**\n- 在连接建立后,可以进行多次请求和响应,减少了连接的建立和拆除的开销。\n- 更适用于实时性要求高、频繁交互的场景,如聊天应用和在线游戏。\n- 可保持连接状态,适合传输大量的数据。\n\n**适用场景:**\n- 实时通信场景,如即时通讯软件(例如WebSocket)。\n- 需要频繁交换数据的应用,如在线游戏或金融交易系统。\n- 对延迟敏感的应用,长连接能有效降低延迟。\n\n### 总结\n\n在选择使用长连接还是短连接时,可以根据应用的需求、负载情况,以及对延迟和资源占用的考量来做出判断。希望这个解释能够帮到你,如果你对某个细节还有疑问,随时欢迎提问哦!',0,0,0,'2024-12-06 12:03:45','2024-12-27 09:44:03'),(300879,100118,200923,'内存碎片就像是一片被撕扯得七零八落的布,尽管总面积看起来很大,但如果你想要一片完整的、足够大的布来覆盖某个物体时,却发现根本没有一块足够的空间可用!\n\n在C++中,内存碎片主要分为两种类型:**内部碎片**和**外部碎片**。\n\n### 内部碎片\n内部碎片发生在你申请的内存块比实际需要的大小要大时。比如说,你申请了一个10字节的内存块,但只用了6字节,其余的4字节就闲置在那里,成为了内部碎片。这些闲置的内存虽然占据了空间,但却无法被其他程序或操作系统使用。\n\n### 外部碎片\n外部碎片就像是一块被切割得支离破碎的土地。想象一下,许多小块的空地分散在大地上,虽然总面积可能足够,但是没有一块地方可以建造一座房子。外部碎片发生在多个内存块被动态分配和释放后,剩下的可用内存空间变得小而分散,导致无法满足请求的内存分配。比如,你可能需要16字节的空间,但由于之前的分配和释放,只有几个小块可用,它们加起来虽然超过了16字节,但是没有一块是连续的,无法满足需求。\n\n### 影响\n内存碎片会影响程序的性能和内存使用效率:\n1. **内存使用效率低下**:由于大量内存被闲置或分散,可能导致可用内存减少,影响程序性能。\n2. **分配失败**:当系统需要申请较大块内存时,可能会因为无法找到合适的连续空间而导致申请失败,即使系统的总内存看起来足够。\n3. **性能下降**:为了应对内存碎片,操作系统和应用程序可能需要花费更多时间进行内存管理或垃圾回收,导致程序运行效率下降。\n\n### 解决方法\n为了减少内存碎片的影响,可以考虑以下几种策略:\n- **内存池**:预先分配一定大小的内存块,重复使用,减少分配和释放的次数。\n- **合并空闲块**:在释放内存时,将相邻的空闲内存块合并成一个大块,减少外部碎片的产生。\n- **使用智能指针**:在C++中,智能指针可以帮助管理内存的分配和释放,从而减少碎片的产生。\n\n总之,内存碎片是一道隐形的障碍,在享受C++的灵活性的同时,也需谨慎应对其带来的挑战。',0,0,0,'2024-12-06 12:03:53','2024-12-27 09:44:03'),(300880,100081,200060,'在计算机科学中,页面置换算法是操作系统管理内存的重要组成部分,尤其是在虚拟内存管理时。以下是一些常见的页面置换算法,它们各有优劣,适用于不同的场景。\n\n1. **最少使用算法(Least Recently Used, LRU)**:\n 该算法认为,最近使用的页面未来也会被使用,因此它将页面中最久未使用的页面进行替换。LRU算法较为简单、直观,但在实现上需要维护一个访问记录,可能导致额外的开销。\n\n2. **先进先出算法(First-In, First-Out, FIFO)**:\n FIFO算法将最早进入内存的页面优先替换出。当需要置换页面时,算法简单地替换掉内存中最早加载的那个页面,尽管这可能不是最佳选项。\n\n3. **最不常使用算法(Least Frequently Used, LFU)**:\n LFU根据页面被访问的频率进行置换,选择访问次数最少的页面进行替换。该算法的实现较为复杂,需要维护访问计数。\n\n4. **时钟算法(Clock Algorithm)**:\n 时钟算法是FIFO的一种改进版本,设想内存中的页面形成一个环,维护一个指针,依次检查每个页面。如果页面被访问过,就将其标记并跳过;如果是未访问的页面,则替换它。这种算法在时间和空间上都有较好的性能。\n\n5. **最佳页面置换算法(Optimal Page Replacement)**:\n 该算法理论上是最优的,选择在未来最久不会被使用的页面进行替换。然而,实际应用中很难准确预测页面的未来使用情况,因此更多作为性能的参考模型。\n\n6. **随机页面置换算法(Random Page Replacement)**:\n 这个算法较为简单,通过随机选择一个页面进行替换。虽然性能不如其他算法,但在某些情况下,随机选择会意外地表现得不错。\n\n7. **改进的LRU(如LRU-K, LRU-2等)**:\n 一些改进的LRU算法考虑多个过去的使用情况,以更精确地预测未来的使用模式,从而提升页面置换的效率。\n\n每种页面置换算法都有其适用场景和局限性。选择哪种算法常常取决于具体的系统需求、负载特征以及可用的资源。在不断演进的计算机科学领域中,这些算法也是在持续优化和发展的。正如生活中我们常常面临选择,选择合适的置换算法不仅能够提升系统效率,还能反映出我们对未来需求的深思熟虑。',0,0,0,'2024-12-06 12:04:09','2024-12-27 09:44:03'),(300881,100112,201202,'在Unity中,实现一个高效的资产管理系统不仅有助于减少资源加载时间,也能提升整体游戏性能与用户体验。以下是一些深思熟虑的策略,可以帮助你构建这样的系统:\n\n### 1. 资源预加载(Preloading)\n预加载是指在游戏运行时,提前加载玩家即将使用的资源。你可以利用`AsyncOperation`在后台异步加载资源,这样可以避免在玩家交互时的延迟。\n\n```csharp\nIEnumerator LoadSceneAsync(string sceneName)\n{\n AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);\n while (!asyncLoad.isDone)\n {\n // 可以在这里显示加载进度\n yield return null;\n }\n}\n```\n\n### 2. 资源打包(Asset Bundles)\n通过创建和使用Asset Bundles,你可以将相关资源打包并按需加载。这种方法尤其适合大型项目,可以根据需要下载和加载特定的Bundle,减少初次加载的资源量。\n\n### 3. 使用Addressables\nUnity的Addressables系统简化了资源管理,它允许你以一种高效和灵活的方式加载资源。通过Addressables,资源可以被标记为可以异步加载和卸载,并且能自动处理依赖。\n\n### 4. 管理资源的生命周期\n通过合理地管理资源的生命周期,你可以确保及时释放不再使用的资源。使用`Resources.UnloadUnusedAssets()`可以释放未使用的资源,从而减少内存占用和潜在的性能瓶颈。\n\n### 5. 延迟加载(Lazy Loading)\n对于某些不立即需要的资源,可以选择延迟加载。在用户进入特定场景或条件下再加载资源,以避免初次加载时的压力。\n\n### 6. 分组资源\n将资源进行合理分组,可以帮助更有条理地管理和加载。比如,把材质、纹理、声音文件等分开存储和加载,这样可以针对性地优化。\n\n### 7. 优化资源格式\n选择合适的资源格式可以减少加载时间。例如,使用压缩纹理(如ETC或PVRTC)而不是未压缩的纹理,能显著减小文件大小和加载时间。\n\n### 8. 资源预热\n对于可能长时间未使用的资源,在适当的时候进行预热,比如在场景切换前模拟用户的行为,使这些资源提前加载到内存中。\n\n### 9. 性能分析\n使用Unity的Profiler工具,对活动进行深入分析。识别出资源加载中的瓶颈,针对性地进行优化,如是否需要改进资源的组织结构或存储方式。\n\n### 10. 定期维护\n随着项目的不断发展,定期审查和优化资产结构是必要的,删除未使用的资源、优化纹理大小、合并小文件等都是提高性能的重要方法。\n\n在构建一个高效的资产管理系统时,我们不仅要关注提高性能的手段,还需考虑可维护性与扩展性。只有在全局观念中,才能更好地实现资源的高效利用,从而让玩家在体验游戏时,感受到流畅与乐趣的结合。',0,0,0,'2024-12-06 12:04:21','2024-12-27 09:44:03'),(300882,100112,200682,'在现代微服务架构中,分布式追踪是帮助开发者理解应用程序如何在多个服务间进行交互的重要工具。Spring Cloud Sleuth 和 Zipkin 是两个强大的工具,可以轻松实现这一目标。下面是使用这两个工具进行分布式追踪的可视化步骤,仿佛带你穿越到一场神秘的追踪之旅。\n\n### 步骤一:引入依赖\n\n首先,你需要在你的 Spring Boot 项目中加入 Sleuth 和 Zipkin 的依赖。打开 `pom.xml` 文件,然后加入以下依赖项:\n\n```xml\n\n org.springframework.cloud\n spring-cloud-starter-sleuth\n\n\n org.springframework.cloud\n spring-cloud-starter-zipkin\n\n```\n\n如果使用 Gradle,则在 `build.gradle` 文件中添加:\n\n```groovy\nimplementation \'org.springframework.cloud:spring-cloud-starter-sleuth\'\nimplementation \'org.springframework.cloud:spring-cloud-starter-zipkin\'\n```\n\n### 步骤二:配置 Zipkin\n\n接下来,你需要在 `application.yml` 或 `application.properties` 文件中配置 Zipkin 的地址。一旦设置好,Spring Cloud Sleuth 就会自动将跟踪信息发送到 Zipkin 服务器。\n\n```yaml\nspring:\n zipkin:\n base-url: http://localhost:9411\n sleuth:\n sampler:\n probability: 1.0 # 采样率为100%\n```\n\n### 步骤三:启动 Zipkin 服务器\n\nZipkin 提供了方便的 Docker 镜像,你可以使用以下命令快速启动一个 Zipkin 服务器:\n\n```bash\ndocker run -d -p 9411:9411 openzipkin/zipkin\n```\n\n在浏览器中输入 `http://localhost:9411`,你将看到 Zipkin 的用户界面,仿佛踏入了一个可视化的追踪世界。\n\n### 步骤四:在微服务中使用 Sleuth\n\n在你的微服务中,Sleuth 会自动为每个请求生成唯一的追踪 ID,并向服务间传播。只需注入 `RestTemplate` 或使用 Spring Web 的其他组件,Sleuth 会为你处理追踪。例如,通过调用其他微服务时:\n\n```java\n@RestController\npublic class GreetingController {\n\n @Autowired\n private RestTemplate restTemplate;\n\n @GetMapping(\"/greeting\")\n public String greet() {\n String response = restTemplate.getForObject(\"http://other-service/hello\", String.class);\n return \"Greeting: \" + response;\n }\n}\n```\n\n### 步骤五:监控和查看追踪数据\n\n现在,访问你的服务(如 /greeting),然后在 Zipkin 的仪表盘中查看追踪数据。你将看到请求的延迟分布、服务响应时间、以及服务之间的关系图,惊叹于微服务之间交互的视觉表现。\n\n在 Zipkin 中,你可以通过服务名称、时间范围等过滤出具体的追踪信息,像侦探一样调查每一个请求的流向,了解系统的瓶颈和潜在的故障点。\n\n### 结论\n\n通过上述步骤,你可以轻松构建一个集成了分布式追踪的微服务架构。Spring Cloud Sleuth 和 Zipkin 的结合,使得追踪请求变得生动而直观,为你揭示了微服务交互背后的秘密,你将能更好地优化和维护你的应用程序。赶快行动吧,去探索这一令人兴奋的分布式追踪之旅!',0,0,0,'2024-12-06 12:04:35','2024-12-27 09:44:03'),(300883,100050,201120,'在C++网络编程中实现一个消息队列来处理异步消息,就像在厨房里准备一场大餐,先得把所有的食材准备好,然后再交给厨师(或线程)来忙活。我们可以用标准库提供的一些工具,比如 `std::queue` 和 `std::mutex`,再加上一点异步处理的锅火,来实现这个消息队列。\n\n### 步骤:\n\n1. **消息结构**:首先,定义一个消息结构体,里面可以包含你需要传递的数据,比如消息类型、内容等。\n\n ```cpp\n struct Message {\n int id;\n std::string content;\n };\n ```\n\n2. **消息队列**:然后,我们需要定义一个消息队列类,它会使用 `std::queue` 来存储消息,并用 `std::mutex` 来确保线程安全。\n\n ```cpp\n #include \n #include \n #include \n\n class MessageQueue {\n private:\n std::queue queue;\n std::mutex mtx;\n std::condition_variable cond_var;\n\n public:\n void push(const Message& msg) {\n std::lock_guard lock(mtx);\n queue.push(msg);\n cond_var.notify_one(); // 通知一个等待的线程\n }\n\n Message pop() {\n std::unique_lock lock(mtx);\n cond_var.wait(lock, [this] { return !queue.empty(); }); // 等待直到队列有内容\n Message msg = queue.front();\n queue.pop();\n return msg;\n }\n };\n ```\n\n3. **消费者**:接下来,我们需要一个消费者线程来处理消息。它会不断地从队列中获取消息,处理后续的逻辑。\n\n ```cpp\n void consumer(MessageQueue& mq) {\n while (true) {\n Message msg = mq.pop();\n // 处理消息,比如打印内容\n std::cout << \"处理消息ID: \" << msg.id << \", 内容: \" << msg.content << std::endl;\n }\n }\n ```\n\n4. **生产者**:然后我们可以写一个生产者线程,模拟生成消息并添加到队列中。\n\n ```cpp\n void producer(MessageQueue& mq) {\n for (int i = 0; i < 10; ++i) {\n Message msg = { i, \"消息内容 \" + std::to_string(i) };\n mq.push(msg);\n std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 假装有点延迟\n }\n }\n ```\n\n5. **主函数**:在主函数中,创建消息队列,启动消费者和生产者线程。\n\n ```cpp\n #include \n\n int main() {\n MessageQueue mq;\n\n std::thread prod(producer, std::ref(mq));\n std::thread cons(consumer, std::ref(mq));\n\n prod.join();\n // 为了演示,消费者会一直运行。\n // 可以加入机制来安全退出,比如设置一个标志位或使用条件变量通知消费者退出。\n cons.join();\n\n return 0;\n }\n ```\n\n就这样,我们现在有了一个简单的消息队列,它可以在异步环境中喧闹得如同市场一样!当然,在真实的应用场景中,可能还需要增加更多的错误处理和资源管理的细节,但这就是基本的搭建。不管你是在编写网络应用还是厨房准备,记得,准备好食材,然后交给你那高效的厨师来处理一切!🍜✨',0,0,0,'2024-12-06 12:04:44','2024-12-27 09:44:03'),(300884,100043,200472,'好的,准备好走进Java垃圾回收的奇妙世界了嘛?首先,Java的垃圾回收机制就像是一个隐形的清道夫,默默地在你的代码后面收拾那些你用过但不再需要的对象,免得它们在内存中开派对,让你的应用程序跑得像蜗牛一样慢。\n\n### 垃圾回收(GC)机制大致工作流程:\n\n1. **根搜索(Root Search)**:\n GC首先会找到所有的“根对象”(如线程栈、静态变量等),就像考古学家找到宝藏的起点。\n\n2. **可达性分析(Reachability Analysis)**:\n 从根对象出发,遍历所有的对象,标记那些可达的对象(也就是“你可以找到的对象”),一旦发现某个对象没有任何可达路径,恭喜你,它就是垃圾,准备出局吧!\n\n3. **垃圾回收(Garbage Collection)**:\n 标记完毕后,GC会清理那些标记为垃圾的对象,就像清理掉冰箱里那块神秘的原型食物。\n\n4. **内存整理(Memory Compaction)**:\n 有时候,GC还会整理内存,把留下的“活人”挪到一起,创造出更大、更整齐的空间,让下一个对象更容易进驻。\n\n### 常见的垃圾回收器:\n\n1. **Serial GC**:单线程工作,非常简单,但在面对大型应用时,它的速度就像是踢着足球的乌龟。\n\n2. **Parallel GC**:多线程版本,比Serial GC快很多,适合多核处理器,像是赛跑的乌龟,速度杠杠的。\n\n3. **CMS(Concurrent Mark-Sweep)**:旨在降低延迟的回收器,边回收边执行,像是一个在舞会中旋转的舞者。\n\n4. **G1(Garbage First)**:这个小子会分区清楚,优先处理最大垃圾,减少停顿时间,有点像非常高效的清洁工,扫到哪儿就在哪儿清理。\n\n5. **ZGC(Z Garbage Collector)** 和 **Shenandoah GC**:这是新来的小伙伴,专为大内存场景设计,特别在乎你的用户体验,可以在你交易的时候不会让你的应用卡顿,简直是“猛男清洁工”。\n\n总之,Java的垃圾回收机制就像是一个掌控全局的隐形保洁员,让我们的内存世界保持干净卫生。下次你拿着Java代码编程的时候,别忘了感谢这些勤劳的“清道夫”哦!',0,0,0,'2024-12-06 12:05:11','2024-12-27 09:44:03'),(300885,100052,200492,'你有没有想过,Java虚拟机(JVM)在管理线程的时候采用了哪些策略?首先,线程的创建过程是怎样的呢?比如,Java中使用`Thread`类或者实现`Runnable`接口,接着调用`start()`方法来启动线程,这种方式是否足够简洁有效?\n\n接着,我们可以考虑线程的调度。JVM是如何决定哪个线程获得CPU时间的呢?是单纯依赖操作系统的调度策略,还是有自己的一套优化方式?例如,是否参与了优先级的管理?你觉得这对多线程程序的性能有多大的影响?\n\n最后,关于线程的销毁。当一个线程完成任务后,它是如何被系统回收的?你会思考是通过`join()`方法等待其他线程,还是仅仅让线程自然结束?这种管理方式是否能避免潜在的资源泄漏?\n\n通过以上这些问题,你是否对JVM中线程的管理过程有了更深的理解呢?',0,0,0,'2024-12-06 12:05:16','2024-12-27 09:44:03'),(300886,100059,201009,'你有没有想过,为什么在编写C++代码时,有时会看到以`#`开头的指令?这些指令实际上是预处理器指令,它们的作用是什么呢?预处理器是在编译之前对源代码进行处理的工具,它可以做很多事情,比如条件编译、文件包含和宏定义等。\n\n比如,当你使用`#include`指令来引入头文件时,你是在告诉编译器在编译之前先把那个文件的内容插入到你的源代码中,这样你就可以使用其中定义的函数或类。是不是很方便?而`#define`则允许你定义常量或宏,使得代码更加简洁易读,你觉得这样会不会减少错误的发生呢?\n\n还有,条件编译的指令如`#ifdef`和`#endif`允许你根据不同的条件来编译不同的代码部分,这在处理跨平台代码时特别重要,你觉得这样做会不会提高代码的可移植性?\n\n总结来说,C++预处理器的作用是为代码提供灵活性和可管理性,是不是这样可以让整个开发过程更加高效?',0,0,0,'2024-12-06 12:05:24','2024-12-27 09:44:03'),(300887,100010,201002,'模板特化和模板偏特化就像两种不同风格的咖啡:一种是黑咖啡(特化),另一种是拿铁(偏特化)。\n\n1. **模板特化(Full Specialization)**:这就好比你点了一杯黑咖啡,没有任何添加。也就是说,特化的模板全部替换掉了模板参数,比如说你有一个 `template` 的模板,如果你特化它成了 `template<>`,并指定一个具体的类型,例如 `template<> struct MyTemplate { ... };`,那么你就得到了一个完全特化的版本!这时候,那个模板简直就是个“专属定制”版。\n\n2. **模板偏特化(Partial Specialization)**:这关乎于对于模板参数的某些部分进行特化,比如你仍然有 `template`,但是你觉得自己只想为某种情形准备一杯“浓缩咖啡”,例如 `template struct MyTemplate { ... };`。这就是说,你选择了一部分模板参数来特化,而其它的参数则保持不变。\n\n所以,简而言之,模板特化是全方位的定制,而模板偏特化则是部分定制。如果模板是咖啡,那你的偏特化就像是要加点牛奶、糖、甚至香料,而模板特化则是喝纯粹的咖啡——复杂得多!',0,0,0,'2024-12-06 12:05:35','2024-12-27 09:44:03'),(300888,100009,201017,'使用编译器的调试选项是提升代码质量和debug效率的重要步骤。我很高兴你提到这个话题!下面是一些常见的方法和步骤,希望能对你有所帮助:\n\n1. **编译选项设置**:大多数编译器都提供了调试选项,比如 `-g` 选项(在GCC中)。这个选项会在编译时生成调试信息,这样在使用调试工具(如GDB)时,可以查看源代码、变量值和调用堆栈。\n\n2. **优化级别**:在调试时,建议选择较低的优化级别(如 `-O0`),这样可以确保代码的行为更接近于源代码,便于追踪和调试。\n\n3. **使用调试工具**:配合调试器(如GDB、Visual Studio调试器等),可以设置断点、单步执行代码、检查变量值等。例如,使用 `break` 命令设置断点,然后通过 `run` 开始程序,使用 `step` 或 `next` 单步调试。\n\n4. **观察变量**:在调试过程中,随时查看和监控变量的值非常重要。使用调试器的 `print` 命令可以帮助你了解程序在特定时刻的状态。\n\n5. **回溯错误**:如果程序崩溃,你可以使用调试器查看堆栈跟踪,了解错误发生的来源。这对发现潜在的逻辑错误或者内存泄漏等问题非常有效。\n\n6. **查看文档和资源**:不同的编译器和调试工具都有其独特的功能和命令,查看官方文档或在线教程可以帮助你更深入地理解和掌握这些工具的使用。\n\n调试是一个学习和改进的过程,遇到挫折时也不必气馁。相信你在实践中会不断提升,逐渐成为调试的高手!如果有任何具体的问题,随时可以问我哦!',0,0,0,'2024-12-06 12:05:43','2024-12-27 09:44:03'),(300889,100083,200079,'请求分页存储管理是一种内存管理方式,旨在更有效地使用计算机的内存资源。这种机制允许程序在需要时加载特定的内存页面,而不是一次性将整个程序加载到内存中,从而节省内存,提升系统效率。\n\n它的工作原理可以归纳为以下几个步骤:\n\n1. **页面划分**:程序被分成多个固定大小的页面。在物理内存中,内存也被划分为同样大小的页面框。这种划分使得内存管理更为灵活和高效。\n\n2. **请求时加载**:在程序运行时,操作系统会根据程序的需求来动态加载这些页面。当程序需要使用不在内存中的页面时,操作系统会发出一个页面缺失中断,标识需要加载的页面。\n\n3. **页面调度**:操作系统根据一定的页面调度算法来选择哪些页面保留在内存中,哪些页面可以被替换出去。这种算法可以是先进先出(FIFO)、最近最少使用(LRU)等。\n\n4. **数据存储**:缺失的页面会从辅助存储(如硬盘)加载到物理内存中。这一过程可能涉及到将一些当前不活跃的页面写回到磁盘,以腾出空余页面。\n\n5. **继续执行**:一旦所需页面被加载到内存中,程序的执行将继续。例如,程序会在页面被加载后恢复到中断发出之前的状态,实现无缝的执行体验。\n\n请求分页存储管理的优势在于它能够有效利用有限的物理内存,使得系统能够运行更大的程序或同时执行多个程序。此外,由于每个程序只在必要时才会加载具体页面,这样也减少了内存的浪费。\n\n然而,它也有一些潜在的缺点。由于频繁的页面调入和调出可能导致“页面抖动”,系统性能可能受到影响。因此,在设计和使用请求分页存储管理时,需要仔细考虑其实现和调度策略,以达到最佳的性能和平衡。\n\n总之,请求分页存储管理不仅仅是对内存的有效分配,还是对计算机系统整体资源管理智慧的体现。它反映了在资源有限的情况下如何通过策略和机制上的设计,达到更高效的运行方式,从而为我们在复杂的环境中寻求解脱提供了深刻的启示。',0,0,0,'2024-12-06 12:05:53','2024-12-27 09:44:03'),(300890,100114,200389,'在 Java 中,`start()` 和 `run()` 方法在多线程编程中扮演了不同的角色,想象一下它们就像车子的钥匙和发动机。\n\n### `start()` 方法:\n当你调用 `start()` 方法时,就好比你插入钥匙并启动了引擎。这个方法会创建一个新的线程,让这个线程按照你在 `run()` 方法中定义的行为开始工作。它负责将当前的线程(比如主线程)和新线程分开,并让新线程进入可运行状态。只有当新线程开始运行时,`run()` 方法才会被调用。\n\n### `run()` 方法:\n而 `run()` 方法则是你给车子设定的驾驶路线。在 `run()` 方法中,你定义了线程要执行的具体任务。当你直接调用 `run()` 方法时,就像是按下了油门,但并没有真正启动一个新线程,而是在当前线程中执行这个方法。这意味着整个程序会在主线程中完成这个任务,而不会并发执行。\n\n### 举个例子:\n```java\npublic class MyThread extends Thread {\n @Override\n public void run() {\n System.out.println(\"Thread is running!\");\n }\n\n public static void main(String[] args) {\n MyThread thread = new MyThread();\n \n // 直接调用 run() 方法\n thread.run(); // 这只是普通方法调用,没创建新线程!\n\n // 正确启动新线程\n thread.start(); // 这才是开启了新线程,并调用 run() 方法!\n }\n}\n```\n\n所以,记住:\n- 使用 `start()` 方法来启动一个新线程,开启并发的可能性。\n- 直接调用 `run()` 方法则是在当前线程中顺序执行,无法实现多线程。\n\n希望这个生动的比喻能帮助你更清楚地理解 `start()` 和 `run()` 的区别!',0,0,0,'2024-12-06 12:06:03','2024-12-27 09:44:03'),(300891,100091,200820,'当然可以!在C++中,`this`指针是一个特殊的指针,用于指向当前对象的实例。它在每个类的非静态成员函数中都可以使用。让我们深入了解它的作用及用法。\n\n### 1. **作用**\n- **区分成员变量和参数**:当方法的参数与成员变量同名时,可以使用`this`指针来区分它们。例如,通常在构造函数中使用这种方式:\n \n ```cpp\n class Example {\n public:\n int value;\n Example(int value) {\n this->value = value; // 用this指针区分成员变量和参数\n }\n };\n ```\n\n- **支持链式调用**:`this`指针允许我们返回当前对象的引用,可以实现链式调用。例如:\n\n ```cpp\n class Builder {\n public:\n Builder& setValue(int value) {\n this->value = value;\n return *this; // 返回当前对象\n }\n private:\n int value;\n };\n ```\n\n- **实现操作符重载**:在重载运算符时,也可以使用`this`指针来访问对象的成员。\n\n### 2. **用法**\n在类的成员函数中,可以直接使用`this`指针来获得当前对象的指针。例如:\n\n```cpp\nclass MyClass {\npublic:\n void showAddress() {\n std::cout << \"Address of current object: \" << this << std::endl;\n }\n};\n```\n\n这里,`this`指针指向当前对象的地址。\n\n### 3. **注意事项**\n- `this`只能在类的非静态成员函数中使用,静态成员函数不能使用`this`指针,因为静态成员函数不属于任何实例。\n- `this`指针的类型是当前类的指针类型,通常为`ClassName*`。\n\n使用`this`指针能够让代码更清晰,并且在一些情况下可以避免命名冲突。希望这些解释对你理解C++中的`this`指针有帮助!如果你还有其他问题或者想更深入了解某些方面,随时告诉我!',0,0,0,'2024-12-06 12:06:13','2024-12-27 09:44:03'),(300892,100095,201109,'在C++中使用 sockets 进行 UDP 通信是一个很有趣的练习,使你能够了解网络编程的基本原理。我将为你提供一个简单的示例,包括如何进行广播和组播。需要注意的是,确保在编写和运行这类程序时拥有必要的权限,并根据你的操作系统配置防火墙和路由器设置。\n\n### 基本的 UDP 通信\n\n首先,你需要包含必要的头文件:\n\n```cpp\n#include \n#include \n#include \n#include \n#include \n```\n\n### UDP 广播\n\n以下是一个简单的 UDP 广播示例:\n\n#### 广播服务器\n\n```cpp\nconst int PORT = 8888;\n\nint main() {\n int sockfd;\n struct sockaddr_in broadcastAddr;\n char *broadcastMessage = \"Hello, UDP Broadcast!\";\n\n // 创建 socket\n sockfd = socket(AF_INET, SOCK_DGRAM, 0);\n if (sockfd < 0) {\n perror(\"socket creation failed\");\n exit(EXIT_FAILURE);\n }\n\n // 启用广播\n int broadcastEnable = 1;\n if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable)) < 0) {\n perror(\"setsockopt failed\");\n exit(EXIT_FAILURE);\n }\n\n // 设置广播地址\n memset(&broadcastAddr, 0, sizeof(broadcastAddr));\n broadcastAddr.sin_family = AF_INET;\n broadcastAddr.sin_port = htons(PORT);\n broadcastAddr.sin_addr.s_addr = htonl(INADDR_BROADCAST);\n\n // 发送广播消息\n if (sendto(sockfd, broadcastMessage, strlen(broadcastMessage), 0,\n (struct sockaddr*)&broadcastAddr, sizeof(broadcastAddr)) < 0) {\n perror(\"sendto failed\");\n exit(EXIT_FAILURE);\n }\n\n std::cout << \"Broadcast message sent: \" << broadcastMessage << std::endl;\n\n close(sockfd);\n return 0;\n}\n```\n\n#### 广播客户端\n\n```cpp\nconst int PORT = 8888;\n\nint main() {\n int sockfd;\n struct sockaddr_in recvAddr;\n char buffer[1024];\n socklen_t addrLen = sizeof(recvAddr);\n\n // 创建 socket\n sockfd = socket(AF_INET, SOCK_DGRAM, 0);\n if (sockfd < 0) {\n perror(\"socket creation failed\");\n exit(EXIT_FAILURE);\n }\n\n // 绑定到特定端口\n memset(&recvAddr, 0, sizeof(recvAddr));\n recvAddr.sin_family = AF_INET;\n recvAddr.sin_addr.s_addr = htonl(INADDR_ANY);\n recvAddr.sin_port = htons(PORT);\n if (bind(sockfd, (struct sockaddr*)&recvAddr, sizeof(recvAddr)) < 0) {\n perror(\"bind failed\");\n exit(EXIT_FAILURE);\n }\n\n // 接受广播消息\n ssize_t len = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0,\n (struct sockaddr*)&recvAddr, &addrLen);\n if (len < 0) {\n perror(\"recvfrom failed\");\n exit(EXIT_FAILURE);\n }\n buffer[len] = \'\\0\'; // Null-terminate the received string\n\n std::cout << \"Received broadcast message: \" << buffer << std::endl;\n\n close(sockfd);\n return 0;\n}\n```\n\n### UDP 组播\n\nUDP 组播的工作原理与广播类似,但你需要使用组播地址。IPv4 组播地址范围为 `224.0.0.0` 到 `239.255.255.255`。\n\n#### 组播服务器\n\n```cpp\nconst int PORT = 8888;\nconst char* MULTICAST_ADDR = \"239.0.0.1\";\n\nint main() {\n int sockfd;\n struct sockaddr_in multicastAddr;\n char *multicastMessage = \"Hello, UDP Multicast!\";\n\n // 创建 socket\n sockfd = socket(AF_INET, SOCK_DGRAM, 0);\n if (sockfd < 0) {\n perror(\"socket creation failed\");\n exit(EXIT_FAILURE);\n }\n\n // 设置组播地址\n memset(&multicastAddr, 0, sizeof(multicastAddr));\n multicastAddr.sin_family = AF_INET;\n multicastAddr.sin_port = htons(PORT);\n inet_pton(AF_INET, MULTICAST_ADDR, &multicastAddr.sin_addr);\n\n // 发送组播消息\n if (sendto(sockfd, multicastMessage, strlen(multicastMessage), 0,\n (struct sockaddr*)&multicastAddr, sizeof(multicastAddr)) < 0) {\n perror(\"sendto failed\");\n exit(EXIT_FAILURE);\n }\n\n std::cout << \"Multicast message sent: \" << multicastMessage << std::endl;\n\n close(sockfd);\n return 0;\n}\n```\n\n#### 组播客户端\n\n```cpp\nconst int PORT = 8888;\nconst char* MULTICAST_ADDR = \"239.0.0.1\";\n\nint main() {\n int sockfd;\n struct sockaddr_in recvAddr;\n char buffer[1024];\n struct ip_mreq mreq;\n socklen_t addrLen = sizeof(recvAddr);\n\n // 创建 socket\n sockfd = socket(AF_INET, SOCK_DGRAM, 0);\n if (sockfd < 0) {\n perror(\"socket creation failed\");\n exit(EXIT_FAILURE);\n }\n\n // 绑定到特定端口\n memset(&recvAddr, 0, sizeof(recvAddr));\n recvAddr.sin_family = AF_INET;\n recvAddr.sin_addr.s_addr = htonl(INADDR_ANY);\n recvAddr.sin_port = htons(PORT);\n if (bind(sockfd, (struct sockaddr*)&recvAddr, sizeof(recvAddr)) < 0) {\n perror(\"bind failed\");\n exit(EXIT_FAILURE);\n }\n\n // 加入组播组\n inet_pton(AF_INET, MULTICAST_ADDR, &mreq.imr_multiaddr);\n mreq.imr_interface.s_addr = htonl(INADDR_ANY);\n if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {\n perror(\"setsockopt failed\");\n exit(EXIT_FAILURE);\n }\n\n // 接受组播消息\n ssize_t len = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0,\n (struct sockaddr*)&recvAddr, &addrLen);\n if (len < 0) {\n perror(\"recvfrom failed\");\n exit(EXIT_FAILURE);\n }\n buffer[len] = \'\\0\'; // Null-terminate the received string\n\n std::cout << \"Received multicast message: \" << buffer << std::endl;\n\n close(sockfd);\n return 0;\n}\n```\n\n### 总结\n\n在上述示例中,我提供了 UDP 广播和组播的基本实现。在处理网络编程时,确保你在合适的权限和网络环境下运行代码。同时,可以考虑添加更多的错误处理代码以增强程序稳健性。\n\n希望这些示例能够帮助你更好地理解 C++ 中的 UDP 通信!如有任何问题或需要更深入的探讨,请随时询问。继续保持好奇,学习新的技术世界将为你打开更多的可能性!',0,0,0,'2024-12-06 12:06:24','2024-12-27 09:44:03'),(300893,100006,200764,'在 C++ 中,匿名联合体是一个没有名字的联合体,允许你直接访问其成员而不需要使用联合体的名称。这种特性可以帮助简化代码,使其更清晰。以下是如何定义和使用匿名联合体的简单示例。\n\n### 定义匿名联合体\n\n你可以在结构体或类中定义匿名联合体。下面是一个示例:\n\n```cpp\n#include \n\nstruct MyStruct {\n int type; // 用于指示当前的类型\n union {\n int intValue; // 整数值\n float floatValue; // 浮点值\n char charValue; // 字符值\n }; // 这是一个匿名联合体\n};\n\nint main() {\n MyStruct s;\n\n // 设置类型为整数\n s.type = 0; // 0 表示 int\n s.intValue = 42;\n \n // 输出整数值\n if (s.type == 0) {\n std::cout << \"Integer: \" << s.intValue << std::endl;\n }\n\n // 设置类型为浮点数\n s.type = 1; // 1 表示 float\n s.floatValue = 3.14f;\n\n // 输出浮点值\n if (s.type == 1) {\n std::cout << \"Float: \" << s.floatValue << std::endl;\n }\n\n // 设置类型为字符\n s.type = 2; // 2 表示 char\n s.charValue = \'A\';\n\n // 输出字符值\n if (s.type == 2) {\n std::cout << \"Character: \" << s.charValue << std::endl;\n }\n\n return 0;\n}\n```\n\n### 使用匿名联合体\n\n在上面的示例中,我们首先定义了一个包含匿名联合体的结构体 `MyStruct`。然后,我们通过 `type` 字段来区分当前存储的数据类型,并直接访问联合体成员而无需额外的命名。\n\n### 注意事项\n\n1. **内存管理**:同一时间只应使用匿名联合体的一个成员。访问未被使用的成员可能导致未定义行为。\n\n2. **构造与析构**:联合体成员并不具备构造和析构的功能,因此使用复杂类型作为联合体成员时要小心。\n\n3. **类型安全**:使用 `type` 字段进行类型标记是一种常见的做法,以确保在使用联合体时候的安全。\n\n希望这个简单的示例能帮助你理解如何在 C++ 中定义和使用匿名联合体!如有更多问题,随时欢迎提问!',0,0,0,'2024-12-06 12:06:38','2024-12-27 09:44:03'),(300894,100049,200496,'类数据共享(Class Data Sharing,CDS)是Java虚拟机(JVM)的一项功能,旨在提高Java应用程序的启动速度和内存使用效率。你可以把它想象成是JVM的一个“共享存储库”,就像一个咖啡共享机,大家都可以来取咖啡喝,而不是每个人都要自己煮一壶。\n\n### 如何工作?\n\n1. **预处理类元数据**:\n - 当你第一次启动一个Java应用程序时,JVM 会读取类文件并解析所有类的元数据。这个过程就像是你查看菜单,然后决定点什么。CDS捕获这部分信息,并把它们存储起来,形成一个共享的“菜单”。\n\n2. **创建快照**:\n - 当应用程序运行时,JVM 会将这些类的元数据保存到一个叫做“共享档案”(Shared Archive)的文件中。就像把你最爱的菜谱放到冰箱一样,以后只要拿出来就能用。\n\n3. **重用快照**:\n - 在后续的JVM启动过程中(比如你再次执行同样的Java程序),JVM 可以从这个共享档案中加载类的元数据,而不必重新解析。这就像你打开冰箱直接取出菜谱来做饭,省时又省力。\n\n### 优势\n\n- **启动速度**:因为类的元数据已经存在于共享档案中,启动时间显著减少。\n- **内存效率**:多个JVM实例可以共享相同的类元数据,节省了内存空间。\n \n### 注意事项\n\n不过,CDS也有一些小麻烦,比如在热更新或修改类结构时可能会遇到问题。就像你如果换了菜单,老的档案就不太适用了。\n\n提升你的Java应用性能的同时,还能为你省下喝咖啡的时间,真是一举多得的好主意!所以,快来试试这个类数据共享功能,让你的JVM运行起来像火箭一样快吧! 🚀☕',0,0,0,'2024-12-06 12:06:48','2024-12-27 09:44:03'),(300895,100118,200033,'HTTP持久连接,听起来像是你和某个朋友关系稳定,不用每次见面都重新自我介绍。简单来说,持久连接(也称为HTTP Keep-Alive)是一种让客户端和服务器之间能够在一次TCP连接上进行多次HTTP请求和响应的技术。这样一来,不用每次都建立和关闭连接,节省了资源和时间!\n\n在网络通信中的作用可以总结为以下几点:\n\n1. **节省时间**:每次打开新连接就像是每次见面都重复“你好,我是XX”,很浪费时间。持久连接让你一开口就能快速聊起正事,响应更快。\n\n2. **减少资源消耗**:建立连接需要一些网络资源,就像开车去某个地方,如果每次都要重新加油,那汽车一定会崩溃。持久连接让你可以“畅通无阻”,效率大大提升。\n\n3. **提高吞吐量**:多个请求一起走,就像是一次性把购物清单上的东西都买了回来,而不是每样东西一趟趟地去商店。这样可以让网络吞吐量提高,提高整体性能。\n\n4. **兼容性**:现代浏览器大多支持持久连接,这让咱们在浏览网页时就像喝了咖啡一样,精神奕奕,不再被频繁的连接和断开拖慢了速度。\n\n所以,HTTP持久连接就像是那种不需要重启的聊天机,轻松愉快,省时省力,让网络世界更加高效!',0,0,0,'2024-12-06 12:06:55','2024-12-27 09:44:03'),(300896,100082,201173,'你是否听说过原型模式的基本概念?它实际上是一种创建对象的方式,通过复制现有对象的结构和属性,而不是通过类构造新对象。你知道如何定义一个原型接口吗?\n\n在C++中,我们通常会创建一个基类,定义一个克隆方法,比如这样:\n\n```cpp\nclass Prototype {\npublic:\n virtual Prototype* clone() const = 0; // 克隆方法\n virtual ~Prototype() {}\n};\n```\n\n接下来,是否可以想象一个具体类继承自这个基类,并重写克隆方法呢?例如:\n\n```cpp\nclass ConcretePrototype : public Prototype {\npublic:\n int data;\n\n ConcretePrototype(int value) : data(value) {}\n\n Prototype* clone() const override {\n return new ConcretePrototype(*this); // 深度复制\n }\n};\n```\n\n这样做之后,如何使用这个克隆方法来复制对象呢?你会如何实例化一个对象并使用其克隆功能呢?例如:\n\n```cpp\nint main() {\n ConcretePrototype original(42);\n ConcretePrototype* copy = static_cast(original.clone());\n\n // 你觉得这时,original 和 copy 的值会一样吗?\n std::cout << \"Original: \" << original.data << \", Copy: \" << copy->data << std::endl;\n\n delete copy; // 不要忘了释放内存哦\n return 0;\n}\n```\n\n通过以上示例,你是否明白了原型模式在C++中的使用方法?你会考虑在什么情况下应用这种模式呢?',0,0,0,'2024-12-06 12:07:03','2024-12-27 09:44:03'),(300897,100038,200965,'哦,C++中的`std::semaphore`就是用来解决线程同步问题的小帮手。就像买一张排队的票,不然你就等着吧。你想在多线程环境中保护共享资源?那你得好好运用它!下面我给你示范个基本用法。\n\n首先,这个类是在C++20中新引入的,所以你得确保你的编译器支持这个版本,不然你可能会遭遇编译器的冷漠眼神。\n\n### 基本用法示例\n\n```cpp\n#include \n#include \n#include // 别忘了这个头文件\n#include \n\nstd::binary_semaphore semaphore(1); // 二进制信号量,初始值1,意味着可以进来1个线程\n\nvoid critical_section(int thread_id) {\n semaphore.acquire(); // 获取信号量\n std::cout << \"Thread \" << thread_id << \" is in critical section.\\n\";\n std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // 模拟一些工作\n std::cout << \"Thread \" << thread_id << \" is leaving critical section.\\n\";\n semaphore.release(); // 释放信号量\n}\n\nint main() {\n const int num_threads = 5;\n std::vector threads;\n\n for (int i = 0; i < num_threads; ++i) {\n threads.emplace_back(critical_section, i);\n }\n\n for (auto& thread : threads) {\n thread.join(); // 等待所有线程完成\n }\n\n return 0;\n}\n```\n\n### 说明\n\n1. **创建信号量**:`std::binary_semaphore semaphore(1);`创建了一个二进制信号量,初始值为1,表示最多允许一个线程进入临界区。\n\n2. **获取信号量**:`semaphore.acquire();`就像你按了一下电梯按钮,信号量如果被占用了,你就得等着。\n\n3. **临界区**:只有获取到信号量的线程才能进入这个部分,其他线程在门口等着。\n\n4. **释放信号量**:`semaphore.release();`,相当于你按开了电梯,释放了其他人在门口等着的机会。\n\n5. **结束线程**:用`join()`确保所有线程都完成了它们的工作。\n\n所以,这就是`std::semaphore`的基本使用说明。使用它可以帮助你避免线程安全问题,就像一把大葱把你那些不安全的操作砸得稀巴烂!当然了,别忘了,使用这些工具的时候还得小心死锁,不然可就真成\"锁\"住人生了!',0,0,0,'2024-12-06 12:07:13','2024-12-27 09:44:03'),(300898,100023,200826,'当然可以!让我们一起探索C++中的`std::move`和移动语义(Move Semantics)的奥秘。\n\n### 移动语义(Move Semantics)\n\n首先,移动语义是C++11引入的一种优化机制,旨在提高资源管理的效率。传统的复制操作需要为对象的所有数据进行深拷贝,这在资源密集型任务中(如动态分配内存、文件句柄等)可能非常昂贵。移动语义允许我们“移动”资源而不是复制,从而避免不必要的开销。\n\n#### 移动操作的基本思想:\n\n- **左值与右值**:\n - **左值(Lvalue)**:可寻址的对象(如变量),可以取地址。\n - **右值(Rvalue)**:临时对象,不可以取地址(如函数返回的临时对象),主要用于表达“这个值将不再被使用”的意图。\n\n- **移动构造函数(Move Constructor)**和**移动赋值运算符(Move Assignment Operator)**:\n - 移动构造函数通过窃取(steal)资源(如指针)来构建新对象,而不是复制资源。\n - 移动赋值运算符则是将一个对象的资源“转移”到另一个已存在的对象中。\n\n### `std::move`\n\n`std::move`是C++标准库中的一个非常重要的工具。它的作用是**将一个左值转化为一个右值**,从而允许你使用移动语义。简单来说,`std::move`并不实际移动对象,只是告诉编译器“这个对象已经用完,可以安全地窃取它的资源了”。\n\n#### 使用示例:\n\n让我们看个简单的例子,感受移动语义的魔力:\n\n```cpp\n#include \n#include \n#include // for std::move\n\nclass MyVector {\npublic:\n std::vector data;\n\n MyVector(std::vector vec) : data(std::move(vec)) {\n // 使用 std::move 以启用移动语义\n }\n\n // 移动构造函数\n MyVector(MyVector&& other) noexcept : data(std::move(other.data)) {\n // 窃取资源\n }\n\n // 移动赋值运算符\n MyVector& operator=(MyVector&& other) noexcept {\n if (this != &other) {\n data = std::move(other.data); // 窃取资源\n }\n return *this;\n }\n};\n\nint main() {\n MyVector v1({1, 2, 3, 4, 5});\n MyVector v2 = std::move(v1); // 使用 std::move 进行移动构造\n\n for (int val : v2.data) {\n std::cout << val << \' \'; // 输出: 1 2 3 4 5\n }\n\n return 0;\n}\n```\n\n在这个例子中,`std::move`帮助我们将`v1`的资源转移到`v2`中,而不去复制底层的数据,从而提升了性能。\n\n### 总结\n\n移动语义和`std::move`为C++引入了高效的资源管理机制,帮助我们在对象传递与存储操作中避免不必要的资源复制。通过巧妙利用移动构造函数和移动赋值运算符,我们可以实现更高效、更简洁的代码。记住:当你面对许多昂贵的资源管理时,移动语义是一把利器,让你的代码如同飞羽般轻盈!',0,0,0,'2024-12-06 12:07:29','2024-12-27 09:44:03'),(300899,100104,200594,'你有没有想过,为什么在Spring框架中要用到@EnableWebMvc这个注解呢?它其实是用来启用Spring MVC的特性,让你可以方便地构建Web应用程序。\n\n而且,@EnableWebMvc会做些什么呢?它不仅开启了MVC的配置,还注册了一些重要的bean,比如视图解析器、默认的处理器映射等。那么,想一想,如果没有这些,如何能轻松处理请求和响应呢?\n\n此外,这个注解为你提供了哪些高级特性呢?比如,注解驱动的控制器、跨域请求处理以及数据绑定。这些特性是否让开发变得更加灵活和高效呢?\n\n所以,总体来看,@EnableWebMvc是不是一个让Spring应用更加强大和灵活的关键注解呢?',0,0,0,'2024-12-06 12:07:34','2024-12-27 09:44:03'),(300900,100030,200775,'面向对象编程(OOP)就像是程序员的聚会,大家围坐在一起,聊着物品、属性和行为。简单来说,OOP是一种编程模式,允许你用“对象”来组织代码。想象一下,你在编程的同时像是在给一群可爱的虚拟宠物起名字,那感觉吧!\n\nOOP的主要特性可以归纳为四个小宝贝:封装、继承、多态和抽象。快来看看这些“小宝贝”有什么秘密吧!\n\n1. **封装**:就像一个好心的程序员把所有的代码和数据藏在一个神秘的小盒子里,外面的人无法直接查看这些内部细节,只能通过特定的方法来和它互动。这样可以保障数据的安全性,防止别人随意乱动,耶!\n\n2. **继承**:当你想要创建一个新对象,但又不想从零开始,可以通过“继承”这个魔力,让新对象继承现有对象的属性和行为。就像是给你的宠物编程一个更强大的版本:从小猫变成了小狮子,真是个大的飞跃!\n\n3. **多态**:多态就像是一位变色龙,根据不同的环境和上下文变化自己的表现。你可以用相同的接口来处理不同类型的对象,所得到的却是各具特色的结果。就像是你用同一招式打怪,却能一次又一次地萌出新花样,真是太好玩了!\n\n4. **抽象**:抽象就像是一位魔术师,他悄悄将复杂的细节变得简单明了,只留下必要的信息供外部使用。它帮助你聚焦于对象的核心特性,而不浪费时间在琐碎的细节上。简直就是编程界的“减法大师”!\n\n总之,OOP是一个让代码更加结构化、可维护和可重用的绝佳方法。如果你是一位程序员,愿你在编程的道路上,永远保持着你心爱的“虚拟宠物”们的快乐与顺利!',0,0,0,'2024-12-06 12:07:42','2024-12-27 09:44:03'),(300901,100039,200391,'哦,线程的生命周期就像一部精彩的电视剧,情节跌宕起伏,角色之间错综复杂。总共大约有六个主要的状态,它们分别是:\n\n1. **新建状态(New)**:线程刚刚出生,就像一位新生儿,活泼可爱,但还没开始工作。调用`new Thread()`时创建的线程都处于这个状态。\n\n2. **可 Runnable 状态(Runnable)**:线程经过系统的“了解”,准备好要出击了,它进入可运行状态。这个状态就像是在卧虎藏龙的江湖,随时准备斗智斗勇。当线程获得 CPU 时间片时,它就会开始执行。\n\n3. **阻塞状态(Blocked)**:可怜的线程被其他线程阻挡了去路,像是在排队买票,等着别人放行。比如说,当一个线程要获取一个被其他线程占用的锁时,它就会掉进这个状态。\n\n4. **等待状态(Waiting)**:这个状态的线程就像是一个热爱等待的哲学家,可能在等某个事件的发生(例如,另一个线程调用了 `Object.wait()`、`Thread.join()` 或 `LockSupport.park()`)。它在这儿静静地等待,不打算进行任何操作。\n\n5. **计时等待状态(Timed Waiting)**:这个线程有点不耐烦,但同时又无奈只能等,给自己定了个时间限制。比如说,调用了`Thread.sleep(milliseconds)`或者`Object.wait(timeout)`。它会在规定的时间内等,时间到了就会乖乖出山。\n\n6. **终止状态(Terminated)**:最后,线程总会走到这一刻,像一位经历诸多曲折的英雄,终于完成了自己的使命,进入终止状态。线程任务完成后就进入这个状态,随之而来的是该线程的资源被回收。\n\n总之,线程的生命周期就像是一个曲折的故事,充满了在不同状态间徘徊的幽默和气氛。每个状态都有自己的角色和任务,结合在一起,构成了五光十色的多线程世界!🎭✨',0,0,0,'2024-12-06 12:07:51','2024-12-27 09:44:03'),(300902,100030,201220,'你是否想过,实时阴影和反射效果在游戏中的表现是如何影响玩家的沉浸感的?在DirectX中,实现这些效果通常依赖于几种关键技术。\n\n首先,你是否了解阴影映射(Shadow Mapping)的原理?它通过创建光源视角下的深度图来确定哪些区域受到光线的照射,从而生成阴影效果。你觉得,这样的深度图是如何被转换为场景中的阴影的呢?\n\n而对于反射效果,使用环境贴图(Environment Mapping)或反射映射技术也是一种常见的方法。你是否曾想过,如何通过镜面反射来增强场景的真实感?在DirectX中,反射通常也可以通过渲染场景到一个纹理中,然后在对象表面上应用该纹理来实现。\n\n你觉得这些技术在实现上有什么挑战吗?例如,阴影的柔和度和反射的清晰度,通常会影响性能和视觉效果。你有什么想法来优化这些效果吗?',0,0,0,'2024-12-06 12:07:57','2024-12-27 09:44:03'),(300903,100054,200105,'数据库的三大范式,像是构建数据库的黄金法则,旨在帮助我们设计出高效、易维护且无冗余的数据模型。让我们一起来揭开这三大范式的神秘面纱。\n\n### 第一范式(1NF): 原子性\n想象一下,你的数据库如同一个庞大的图书馆,每本书都是一个数据表。在第一范式中,我们要求每个数据表中的每一列都必须是原子的,也就是说,每个字段的值都不能再被拆分。比如,假设有一列“电话”,里面存的是“123-456-7890,987-654-3210”,这就不符合第一范式。我们应该把电话拆分成两行,每行独立存储一个电话号码。这样做可以确保数据的整洁性和易操作性。\n\n### 第二范式(2NF): 完全依赖\n在第一范式的基础上,第二范式要求消除数据表中的部分依赖。也就是说,每一个非主属性(即不作为表主键的属性)必须完全依赖于主键。想象你在一间甜品店,主键是“订单编号”,而非主属性如“顾客姓名”和“甜品名称”。如果“顾客姓名”依赖于“顾客ID”,而不是直接依赖于“订单编号”,那就违反了第二范式。我们需要将有关顾客的属性分到另外的表中,确保每个非主属性都直接由主键决定。\n\n### 第三范式(3NF): 传递依赖\n在第二范式的基础上,第三范式更进一步,要求消除传递依赖。也就是说,非主属性之间不能相互依赖。就好比在我们的甜品店中,假设“顾客城市”依赖于“顾客ID”,而“顾客ID”又通过“订单编号”间接影响了“顾客城市”。为了遵循第三范式,我们应当将“顾客信息”独立到另一个表中,使得每一项属性都与主键有直接联系,而没有其他属性的介入。\n\n### 总结\n通过遵循这三大范式,我们可以构建出整洁、高效的数据结构,避免了数据的冗余和不一致性。就像一个精心设计的图书馆,条理清晰,易于查找和维护,让管理者和使用者都能享受高效的数据管理体验!',0,0,0,'2024-12-06 12:08:07','2024-12-27 09:44:03'),(300904,100079,200087,'进程控制块(Process Control Block,简称 PCB)就像是操作系统为每个正在运行的进程准备的一本详细的“身份证明书”。它记录了关于进程的所有重要信息,确保操作系统能够有效地管理和调度这些进程。\n\n我们可以把 PCB 想象成一本包含以下信息的华丽档案:\n\n1. **进程标识符(PID)**:这是每个进程的独特编号,就像我们人类的身份证号码,确保每个进程都可以被清晰地识别。\n\n2. **进程状态**:这是进程当前的生活状态,比如“就绪”、“运行”或“等待”,就像你正在上班、休息或者排队等候。\n\n3. **程序计数器(PC)**:这个字段记录了进程执行的下一条指令的位置,可以看作是书籍的页码,指引着我们应该阅读的下一行。\n\n4. **CPU 寄存器**:这些寄存器保存着进程在CPU中执行时的一些临时数据,比如寄存器的值和状态。当进程切换时,操作系统需要保存和恢复这些信息,确保进程能从中断的地方继续进行。\n\n5. **内存管理信息**:这包括进程使用的内存地址范围,页表等信息,仿佛是进程在数字世界中的“地图”。\n\n6. **进程优先级**:这是决定进程运行顺序的重要因素,不同优先级就像不同的交通信号,确保高优先级的进程能够“绿色通行”。\n\n7. **I/O 状态信息**:记录进程的输入输出状态,比如正在进行的 I/O 操作,等待的设备等,犹如生活中我们需要关注的一些待办事项。\n\n8. **统计信息**:如用户时间、系统时间、进程使用 CPU 的时间等,帮助监控和评估进程的性能。\n\n9. **父进程和子进程的信息**:描述进程之间的关系,类似于家庭树,帮助操作系统理解家庭成员间的联系。\n\n通过这些丰富的信息,PCB 让操作系统如鱼得水,游刃有余地管理着各个进程,确保每个进程能够顺畅地运行,共同构成一个和谐的计算环境。',0,0,0,'2024-12-06 12:08:14','2024-12-27 09:44:03'),(300905,100061,200792,'对象切片(Object Slicing)是一个在面向对象编程中的概念,尤其在C++等语言中常见。当一个派生类对象被赋值给一个基类类型的对象时,派生类的特有属性和方法会被截断,只保留基类的部分。这种现象被称为“切片”,因为派生类的完整信息被\"切掉\"了,就像切一块蛋糕一样。\n\n### 如何避免对象切片?\n\n1. **使用指针或引用**:\n 指向基类的指针或引用可以有效避免对象切片。这样,您可以保留完整的派生类对象信息。例如:\n ```cpp\n Base* obj = new Derived(); // 使用指针\n Base& ref = derivedObj; // 使用引用\n ```\n\n2. **多态**:\n 利用虚函数实现多态性,可以在基类中声明虚函数,在派生类中重写它们。通过基类引用或指针调用这些虚函数,将会执行派生类的版本,而不会发生切片现象。\n\n3. **智能指针**:\n 使用智能指针(如`std::unique_ptr`和`std::shared_ptr`)可以管理对象的生命周期,同时避免对象切片,使得内存管理更为安全和简洁。\n\n4. **不要将派生类对象直接赋值给基类对象**:\n 直接赋值会造成信息丢失。如果需要传递对象,请优先使用指针或引用。\n\n### 总结\n对象切片是一个可能导致程序错误和不可预期行为的现象,但通过合理地使用指针、引用和多态,可以轻松避免这种情况,将基于对象的设计推向新的高度!想象一下妥善护航,不让你的船在意外的冰山上搁浅——这样编程之旅才会畅通无阻!',0,0,0,'2024-12-06 12:10:40','2024-12-27 09:44:03'),(300906,100067,200226,'泛型擦除是一种在编译时处理泛型的机制,可以让Java的泛型在运行时保持与非泛型版本的兼容性。用通俗的话说,泛型擦除就像是在编译时给你的代码“减肥”——把那些花哨的类型信息去掉,让代码更加轻盈,跑得更快。\n\n那么,它是怎么做到的呢?简单来说,当编译器看到你的泛型代码时,比如 `List`,它会将其转化成原始类型 `List`。这意味着,在运行时,实际上并没有任何关于泛型类型的信息,被擦除的类型只会留下一个原始的类型。在 `List` 和 `List` 的世界里,运行时根本无法分辨它们的不同,因为它们都是 `List`。这是Java为了保持向后兼容性采用的策略。\n\n这也带来了一些“幽默”的影响:\n\n1. **类型安全**:因为泛型信息在运行时被抹去,所以你不能在运行时检测到泛型类型。这就是为什么你不能像在泛型列表里放一个苹果,然后在 List 中找到它的原因。编译器会给你一个警告:“你这是在干什么?!”\n \n2. **无法创建泛型数组**:你不能直接创建 `new T[]`,因为运行时不知道 `T` 是什么。反而可能产生一场“泛型数组的喜剧”——因为JVM只能给你一个原始类型的数组,这样你随时都可能不小心塞进去一个错误的类型。\n\n3. **转换和强制类型转换**:你可能需要进行一些类型转换,这就像试图把一个斤斤计较的“严格”父母塞进一条随意的“年轻人”游戏里,你敢保证他们能适应吗?\n\n总之,泛型擦除就像是一位隐形的外科医生,在不影响外观的情况下为你的代码进行了“手术”——把多余的东西去掉了,留下了简化的原始类型。太酷了吧?别忘了他背后潜藏的“幽默”-虽然外表光鲜亮丽,但常常需要小心处理内部的复杂性!',0,0,0,'2024-12-06 12:10:44','2024-12-27 09:44:03'),(300907,100046,200641,'Hystrix 是一个由 Netflix 开发的强大开源库,旨在改善分布式系统中的稳定性和容错能力。它的核心思想是通过隔离服务和防止系统崩溃来提高应用的可靠性。\n\n### Hystrix 解决了哪些问题?\n\n1. **服务故障传播**:在微服务架构中,如果一个服务失效,可能会导致整个系统瘫痪。Hystrix 通过“熔断器”模式来防止这种情况的发生。当检测到某个服务调用失败超过预设阈值时,Hystrix 会“熔断”该调用,从而防止故障持续扩散。\n\n2. **服务延迟**:在网络调用中,某些操作可能会因为各种原因变得非常缓慢。Hystrix 能设置超时限制,当服务响应超时后,会立即返回一个备选结果,避免整个调用链被拖延。\n\n3. **系统过载保护**:在高并发情况下,系统可能会面临资源耗尽的问题。Hystrix 提供了流量控制和请求排队机制,帮助系统在高负载时保持稳定。\n\n4. **服务降级**:当某个服务不可用时,我们可以设置 Hystrix 返回一个默认的降级结果,而不是完全失败。这样用户依然可以获得一部分服务,提升用户体验。\n\n5. **可监控和可度量**:Hystrix 自带的监控功能可以实时查看服务的健康状态,让开发者更好地掌握系统运行情况,及时调整和优化。\n\n总的来说,Hystrix 在面对复杂、动态的分布式环境时,提供了一种优雅且高效的方式来维护服务的稳定性和可靠性,让开发者能够更加从容地构建现代云应用。',0,0,0,'2024-12-06 12:10:52','2024-12-27 09:44:03'),(300908,100090,200646,'当然可以!配置 Spring Cloud Config Server 和 Client 就像搭建一座数据的大本营和小分队。下面我会为你奉上一个配置指南,我们一起来把这个“移山填海”的工程搞定!\n\n### 步骤一:设置 Config Server\n\n1. **创建 Spring Boot 项目** \n 使用 Spring Initializr 创建一个新的 Spring Boot 项目,添加以下依赖:\n - Spring Cloud Config Server\n - Spring Web\n\n2. **修改 `application.yml`** \n 在 `src/main/resources/application.yml` 中,添加以下配置:\n ```yaml\n server:\n port: 8888 # Config Server 默认端口\n\n spring:\n cloud:\n config:\n server:\n git:\n uri: https://github.com/your-repo/config-repo # 你的配置文件 Git 仓库\n ```\n\n3. **启用 Config Server** \n 在主应用类上添加 `@EnableConfigServer` 注解:\n ```java\n import org.springframework.boot.SpringApplication;\n import org.springframework.boot.autoconfigure.SpringBootApplication;\n import org.springframework.cloud.config.server.EnableConfigServer;\n\n @SpringBootApplication\n @EnableConfigServer\n public class ConfigServerApplication {\n public static void main(String[] args) {\n SpringApplication.run(ConfigServerApplication.class, args);\n }\n }\n ```\n\n4. **启动 Config Server** \n 启动你的 Config Server 应用,确保它能正常运行。\n\n### 步骤二:设置 Config Client\n\n1. **创建另一个 Spring Boot 项目** \n 同样用 Spring Initializr 创建一个新的项目,这次添加以下依赖:\n - Spring Cloud Config Client\n - Spring Web\n\n2. **修改 `application.yml`** \n 在新项目的 `src/main/resources/application.yml` 中,添加以下配置:\n ```yaml\n spring:\n application:\n name: your-client-app # Client 的名称\n cloud:\n config:\n uri: http://localhost:8888 # 指向你的 Config Server\n ```\n\n3. **创建主应用类** \n 创建一个简单的主应用类:\n ```java\n import org.springframework.boot.SpringApplication;\n import org.springframework.boot.autoconfigure.SpringBootApplication;\n\n @SpringBootApplication\n public class ClientApplication {\n public static void main(String[] args) {\n SpringApplication.run(ClientApplication.class, args);\n }\n }\n ```\n\n4. **启动 Config Client** \n 启动你的 Config Client 应用,它会自动从 Config Server 拉取配置。\n\n### 步骤三:验证配置\n\n1. **访问 Config Server** \n 在浏览器中输入 `http://localhost:8888/your-client-app/default`,你应该能看到用于 Config Client 的配置。\n\n2. **打印配置** \n 在 Client 应用中,你可以用 `@Value` 注解打印出从 Config Server 获取到的配置,看看它是否正常工作:\n ```java\n import org.springframework.beans.factory.annotation.Value;\n import org.springframework.web.bind.annotation.GetMapping;\n import org.springframework.web.bind.annotation.RestController;\n\n @RestController\n public class TestController {\n @Value(\"${your.property}\")\n private String yourProperty;\n\n @GetMapping(\"/property\")\n public String getProperty() {\n return \"Property value is: \" + yourProperty;\n }\n }\n ```\n\n3. **验证** \n 访问 `http://localhost:8080/property`,如果一切正常,你会看到你从 Config Server 获取到的配置值!\n\n### 结尾\n\n就这样,你的 Spring Cloud Config Server 和 Client 就配置完成了!在这座“数据大本营”里,Client们可以朝气蓬勃地向 Config Server请教配置问题。记得给他们一些正能量哦! 🌟\n\n如果有任何问题或者灵感突发,尽管问我,我会乐意为你解答!',0,0,0,'2024-12-06 12:11:03','2024-12-27 09:44:03'),(300909,100049,200208,'在Java中,注解(Annotation)就像一位隐形的助手,静静地为代码提供额外的信息和指引。它们并不会直接改变程序的运行效果,但却是编译器和运行时环境解析代码的重要线索。\n\n### 注解的定义\n注解的定义就像是为某个角色量身定做的服装。你可以通过关键字`@interface`来定义一个注解。例如:\n\n```java\npublic @interface MyAnnotation {\n String value() default \"default value\"; // 默认值\n int count() default 1; // 另一个默认值\n}\n```\n\n如上所示,`MyAnnotation`就是一个自定义注解,它包含了一个字符串类型的值和一个整数类型的计数,且都设定了默认值。\n\n使用注解时,就像在代码上写下提醒条,清晰明了:\n\n```java\n@MyAnnotation(value = \"Hello\", count = 5)\npublic void myMethod() {\n // 方法体\n}\n```\n\n### 注解的作用\n注解的作用多种多样,可以归纳为以下几点:\n\n1. **编译时检查**:注解可以帮助编译器进行检查,例如`@Override`注解可以提醒编译器检查方法是否真的重写了基类的方法。\n\n2. **文档生成**:某些注解(如`@Deprecated`)可以用于标记旧的方法或类,这样生成的文档可以清晰地告知开发者哪些代码是过时的,应该避免使用。\n\n3. **运行时处理**:一些注解可以在运行时被反射解析,能够影响程序的行为。例如,使用`@Entity`注解的类可以被ORM框架(如Hibernate)识别,从而知道这个类对应数据库中的一张表。\n\n4. **代码简化**:注解可以简化代码的编写,比如Spring框架中使用的`@Autowired`注解,使得依赖注入变得更加直观和简单。\n\n总之,注解就像是给代码打上的印章,为它们带来了更多的意义和功能,使得代码不仅仅是冷冰冰的符号,而是变得更具表达力和可读性。通过注解,开发者可以清晰简洁地传递各种信息,与工具和框架的结合也使得开发过程更加高效。',0,0,0,'2024-12-06 12:11:14','2024-12-27 09:44:03'),(300910,100031,200777,'在C++中,类(Class)就像是一个蓝图,用来创建对象(Object)——可以想象成你在玩乐高,类是说明书,而对象就是你根据说明书拼出来的各种小玩意儿。\n\n简单来说,类是一个自定义的数据类型,包含了属性(成员变量)和行为(成员函数)。\n\n要在C++中定义一个类,可以使用如下结构:\n\n```cpp\nclass 类名 {\npublic:\n // 构造函数\n 类名(参数) {\n // 初始化代码\n }\n\n // 成员变量\n 数据类型 变量名;\n\n // 成员函数\n 返回类型 函数名(参数) {\n // 函数体\n }\n};\n```\n\n举个简单的例子:\n\n```cpp\nclass Dog {\npublic:\n // 属性\n std::string name;\n int age;\n\n // 构造函数\n Dog(std::string n, int a) : name(n), age(a) {}\n\n // 行为\n void bark() {\n std::cout << name << \" says Woof!\" << std::endl;\n }\n};\n```\n\n在上面的例子中,我们定义了一个`Dog`类,它有两个属性:`name`和`age`,还有一个成员函数`bark`,用于模拟狗狗叫的声音。\n\n你可以用这个蓝图来创建对象,比如:\n\n```cpp\nDog myDog(\"Buddy\", 3);\nmyDog.bark(); // 输出: Buddy says Woof!\n```\n\n看吧,定义类就像是设计一张特别的菜单,点了之后就可以享受美味的编程大餐!🎉',0,0,0,'2024-12-06 12:11:21','2024-12-27 09:44:03'),(300911,100036,200883,'选择合适的STL容器,首先要考虑你的具体需求是什么?你希望存储的数据是如何访问的?是频繁进行插入和删除,还是更注重快速查找?\n\n比如,若你需要频繁于两端插入和删除元素,那么`deque`可能是一个不错的选择。而如果你需要快速的随机访问,`vector`会更合适,为什么不考虑使用它呢?\n\n当然,如果你需要保持元素的唯一性,并且希望可以快速查找,可以考虑使用`set`或者`unordered_set`。但你有没有想过,这种需求的优先级如何影响你的选择?\n\n此外,线程安全或多线程应用的场景下,是否考虑过使用`concurrent`设计的容器?这样能否更好地满足你的应用需求?\n\n最后,你对性能的要求高吗?不同的容器在时间复杂度上差异很大,是否已经评估过这些对你项目的影响?你觉得还有哪些因素可以影响你的选择呢?',0,0,0,'2024-12-06 12:11:28','2024-12-27 09:44:03'),(300912,100105,200256,'TreeSet中的元素需要满足什么条件呢?是不是需要实现Comparable接口或者提供一个Comparator?如果没有实现这些,会导致什么样的后果呢?你认为这种排序机制对TreeSet的性能和功能有什么影响?',0,0,0,'2024-12-06 12:11:32','2024-12-27 09:44:03'),(300913,100053,201075,'在Qt中实现拖放功能是一个非常有趣的过程!以下是一些基本步骤,可以帮助你在应用程序中实现拖放功能:\n\n### 1. 启用拖放\n\n首先,需要在你的QWidget或QMainWindow子类中启用拖放功能。这可以通过重写以下两个函数来实现:\n\n```cpp\nvoid MyWidget::dragEnterEvent(QDragEnterEvent *event) {\n if (event->mimeData()->hasFormat(\"text/plain\")) {\n event->acceptProposedAction(); // 接受拖放操作\n }\n}\n\nvoid MyWidget::dropEvent(QDropEvent *event) {\n // 处理放下的数据\n QString text = event->mimeData()->text();\n // 这里可以将文本放入界面的某个控件中或进行其他处理\n event->acceptProposedAction();\n}\n```\n\n在构造函数中,设置接受拖放事件:\n\n```cpp\nsetAcceptDrops(true);\n```\n\n### 2. 实现拖放操作\n\n如果你希望从你的窗口中拖动某些内容,可以使用QDrag类。通常这个过程在鼠标按下事件中进行:\n\n```cpp\nvoid MyWidget::mousePressEvent(QMouseEvent *event) {\n if (event->button() == Qt::LeftButton) {\n // 创建拖动数据\n QMimeData *mimeData = new QMimeData;\n mimeData->setText(\"拖动的文本\"); // 你可以设置任何相关的数据\n\n QDrag *drag = new QDrag(this);\n drag->setMimeData(mimeData);\n\n // 设置拖动的图标和效果\n drag->setHotSpot(event->pos());\n drag->exec(Qt::CopyAction | Qt::MoveAction);\n }\n}\n```\n\n### 3. 完整示例\n\n以下是一个简单的示例,演示在一个QWidget中实现文本的拖放:\n\n```cpp\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n\nclass MyWidget : public QWidget {\n Q_OBJECT\n\npublic:\n MyWidget() {\n setAcceptDrops(true);\n QVBoxLayout *layout = new QVBoxLayout(this);\n label = new QLabel(\"将文本拖放到这里\");\n layout->addWidget(label);\n }\n\nprotected:\n void dragEnterEvent(QDragEnterEvent *event) override {\n if (event->mimeData()->hasText()) {\n event->acceptProposedAction();\n }\n }\n\n void dropEvent(QDropEvent *event) override {\n QString text = event->mimeData()->text();\n label->setText(text); // 显示拖放的文本\n event->acceptProposedAction();\n }\n\n void mousePressEvent(QMouseEvent *event) override {\n if (event->button() == Qt::LeftButton) {\n QMimeData *mimeData = new QMimeData;\n mimeData->setText(\"拖动的文本!\");\n\n QDrag *drag = new QDrag(this);\n drag->setMimeData(mimeData);\n drag->exec(Qt::CopyAction | Qt::MoveAction);\n }\n }\n\nprivate:\n QLabel *label;\n};\n\nint main(int argc, char *argv[]) {\n QApplication app(argc, argv);\n MyWidget window;\n window.resize(300, 200);\n window.show();\n return app.exec();\n}\n```\n\n### 结语\n\n通过上面的步骤和示例代码,你可以创建一个简单的拖放功能。希望你能在实现期间享受到编程的乐趣!如果有任何问题,随时欢迎提问。加油!',0,0,0,'2024-12-06 12:11:45','2024-12-27 09:44:03'),(300914,100033,200866,'你是否曾经在遍历一个集合时,突然发现自己无法再访问某些元素了?这其实就是迭代器失效的表现。当我们在迭代过程中修改集合的结构,比如添加或删除元素,就可能会导致迭代器失效。\n\n那么,有没有想过我们应该如何避免这种情况呢?使用迭代器的同时,是否可以考虑只读操作,避免在迭代的过程中直接修改集合?或者,我们可以使迭代的过程与修改操作分开,先记录需要删除或添加的元素,再在遍历完成后进行修改,这样是不是可以更安全地处理集合?\n\n此外,考虑到不同编程语言和集合类型的特性,有没有想过查阅文档,了解具体语言在迭代器使用上的规则和最佳实践?这样我们是不是能更有效地使用迭代器而不引发失效呢?',0,0,0,'2024-12-06 12:11:52','2024-12-27 09:44:03'),(300915,100035,200242,'哈哈,HashSet和LinkedHashSet的区别就像是穿着校服的小怪兽和带着墨镜的小怪兽,外表看起来差不多,但性格和习惯,那可是差得远呢。\n\n首先,HashSet就像是个粗心的家长,完全不在意孩子的顺序。你把东西放进去,它只关心怎么快速找到它们,根本不在意你什么时候放的。它通过哈希表来存储元素,所以元素的顺序是无序的。要是你对顺序什么的十分执着,选择HashSet可就像让它给你做一顿三明治,但给你做的全是黑色的卤素灯泡——没有任何帮助!\n\n而LinkedHashSet就不同了,简直是个有条理的完美主义者。它除了用哈希表来存储元素外,还用一个双向链表来维护元素的插入顺序。这就意味着,你放入的顺序会被记住,你拿出来的时候是可以有序的,简直就像是一份厚厚的清单,完美符合“我喜欢按顺序做事”的生活理念。\n\n总结一下,HashSet就是那个追求效率但不拘小节的家伙,而LinkedHashSet则是个讲究顺序的完美控。前者给你带来速度,后者则给你带来顺序,选择哪个就看你今天心情如何了,哈哈!',0,0,0,'2024-12-06 12:12:01','2024-12-27 09:44:03'),(300916,100098,201030,'在编译过程中,段错误(Segmentation Fault)是在程序运行时出现的一种错误,它表明程序试图访问未被允许的内存区域。想象一下,程序就像一个在特定区域内自由活动的角色,而段错误就像这个角色试图闯入一个禁止进入的区域,结果被“警报”声惊醒。这个错误通常是由于以下几种原因引起的:\n\n1. **非法指针引用**:比如你试图访问一个已经释放的内存,或者访问未分配内存的地址。\n2. **数组越界**:访问数组的下标超出了它实际的范围。\n3. **栈溢出**:函数调用层级过深,导致栈空间不够用。\n\n### 调试段错误的步骤\n\n调试段错误就像一场侦探游戏,需要细心和耐心,以下是一些常见的调试方法:\n\n1. **使用调试器**:最常用的调试器是 `gdb`。你可以在命令行中运行你的程序,如:\n ```bash\n gdb ./your_program\n ```\n 然后输入 `run` 启动程序,若发生段错误,gdb 会显示错误发生的位置及调用栈,你可以逐步检查代码。\n\n2. **打印调试信息**:在出现错误的代码之前添加一些打印语句,观察程序执行到哪一步时出错。这就像在侦探调查中追踪嫌疑犯的行踪。\n\n3. **使用内存检查工具**:例如 `valgrind` 可以帮助识别内存泄漏和非法内存访问。简单地运行:\n ```bash\n valgrind ./your_program\n ```\n 它会告诉你哪些地方可能出现了问题。\n\n4. **代码审查**:仔细检查最近修改过的代码,尤其是涉及内存分配和指针操作的地方。这就像回顾现场调查,看看是否有忽略的证据。\n\n5. **简化问题**:如果问题难以定位,可以尝试简化代码,去掉不必要的部分,逐步缩小范围,直到找出问题所在。\n\n通过这些方法,可以像一个细心的侦探一样追踪到段错误的根源,找到并解决这个隐藏在程序中的“罪犯”。确保你的代码在访问内存时小心谨慎,那样你就能够避免这个令人头疼的“段错误”了!',0,0,0,'2024-12-06 12:12:11','2024-12-27 09:44:03'),(300917,100041,200545,'在Spring框架中,@ModelAttribute注解主要用于将请求参数绑定到方法的参数或返回值的模型中。这个注解在Web应用程序中起到了重要的作用,它帮助我们处理HTTP请求数据并将其转换为Java对象,从而使得我们可以更方便地处理表单数据。\n\n具体来说,@ModelAttribute的作用可以归纳为以下几点:\n\n1. **数据绑定**:当处理HTTP请求时,@ModelAttribute可用于把来自请求的参数自动绑定到方法的参数上。它可以将表单数据映射到Java对象的字段中,使得我们可以直接在控制器中使用这些绑定好的对象。\n\n2. **初始化**:可以在某些方法上使用@ModelAttribute来初始化模型对象,在请求处理之前准备好数据。这样,在视图渲染时,模型中就会包含需要的数据,减少了在每个请求中重复设置相同数据的必要。\n\n3. **共享数据**:在多个请求之间共享数据,当我们在一个控制器中的多个处理方法中使用@ModelAttribute注解时,可以将某个对象添加到模型中,使得它可以在每个处理方法中被使用。\n\n4. **处理复杂类型**:@ModelAttribute能够处理复杂对象(如嵌套对象或集合),这使得表单提交的数据可以映射到更复杂的数据结构中,以便于后续的处理与验证。\n\n5. **输入验证**:与@Valid或@Validated注解结合使用时,@ModelAttribute能够在绑定数据后进行输入验证,确保接收到的数据是合乎要求的。\n\n因此,@ModelAttribute注解不仅简化了数据绑定的过程,也提高了代码的可读性和维护性。它让开发者可以更专注于业务逻辑,而不必为数据处理的细节而烦恼。这正如生命中的许多事情,简化了复杂的过程,反而能让我们拥有更多的时间去思考更深层次的意义和目标。',0,0,0,'2024-12-06 12:12:19','2024-12-27 09:44:03'),(300918,100068,200479,'监控JVM性能就像是给你的宝贝豆腐脑上面装个监控摄像头,既想知道它好不好,又怕看了心疼!不过,既然聊到监控JVM,咱们就来看看有哪些“神器”可以帮你实现这个目标。\n\n1. **Java VisualVM**:这是一个自带的监控工具,跟你那个看似不起眼、其实用途广泛的多功能刀一样,可以用来看内存使用、CPU占用、线程状态等等。操作友好,适合新手,不小心用了可能会爱上。\n\n2. **JConsole**:另一个跟Java捆绑在一起的工具,虽然外表简单,但别小看它,可以监控性能指标,查看MBeans,适合于熟悉Java技术的小伙伴。使用时感觉就像是在逛超市,一眼看过去,但更多的是逛来逛去找你需要的东西。\n\n3. **Prometheus + Grafana**:这对搭档简直就是监控界的情侣,有爱又有趣,可以把JVM的各种数据拉过来,让你在Grafana上看得美滋滋!但是,设置起来有点复杂,需要一点技术功底,不然就像追剧追到一半掉线一样。\n\n4. **ELK Stack**:ElasticSearch、Logstash和Kibana组成的组合拳,可以从日志中提取JVM性能数据,打响“监控战争”。适合喜欢做数据分析的开发者,也是一种时尚的生活方式(你懂的)。\n\n5. **YourKit**:这玩意儿是收费的,但嘿,物有所值!提供深入的性能分析,能让你发现那些藏在角落里的性能“隐患”。使用它就像是在进行一场高端的侦探游戏,别有一番滋味。\n\n6. **New Relic、Dynatrace、Datadog**:这些云监控工具就像是市场上时尚的牌子,虽然收费吓得人半死,但功能强大、自动化程度高,适合大场景使用,让你可以抬头看天空,底下的所有性能问题全部“无影无踪”。\n\n所以说,监控JVM的工具多得很,选一个适合你自己的就好。毕竟,找出性能瓶颈也没必要那么“神秘”,只要你不怕查看它的黑历史,监控一下就好。',0,0,0,'2024-12-06 12:12:33','2024-12-27 09:44:03'),(300919,100078,200971,'当然可以!在C++中,异常处理机制就像是一个勇敢的骑士,专门保护程序免受突发的“怪兽”攻击,比如除零错误、访问越界等。它通过`try`、`catch`和`throw`三种神奇的武器来完成任务。\n\n1. **throw**:当你发现了一个怪兽(即出现了异常),你可以使用`throw`把这个异常“抛出去”,让更高级别的代码来处理它。\n \n2. **try**:你把可能会遇到异常的代码放在`try`块中,就仿佛在设置一个“守护圈”,准备应对随时可能出现的问题。\n\n3. **catch**:用来捕捉异常的代码块,类似于捕怪兽的网。你可以设置多个`catch`块来处理不同类型的异常。\n\n下面是一个简单的例子:\n\n```cpp\n#include \n#include // 引入标准异常库\n\nvoid mightGoWrong() {\n // 随机情况下可能会抛出异常\n bool errorOccurred = true; // 布尔值表示是否出现错误\n if (errorOccurred) {\n throw std::runtime_error(\"错误发生了!\"); // 抛出异常\n }\n}\n\nint main() {\n try {\n mightGoWrong(); // 尝试去做可能出错的事\n } catch (const std::runtime_error& e) {\n // 如果捕捉到运行时错误异常,执行此代码\n std::cout << \"捕获到异常: \" << e.what() << std::endl;\n }\n\n std::cout << \"程序继续执行...\" << std::endl;\n return 0;\n}\n```\n\n在这个例子中,`mightGoWrong`函数可能会抛出一个运行时错误,如果真的发生了,我们的`try`就会立刻进入`catch`块,捕捉这个异常并打印出错误信息,然后程序会继续快乐地运行下去,避免了整个程序崩溃的命运!\n\n希望这个解释和例子能让你对C++的异常处理机制有个清晰的认识。如果你有其他问题,尽管来问骑士我!',0,0,0,'2024-12-06 12:12:42','2024-12-27 09:44:03'),(300920,100029,200351,'在Java中,使用`URL`和`URLConnection`进行网络请求是比较常见的做法。下面是一些基本的步骤和示例代码,帮助你理解如何实现这个过程。\n\n### 基本步骤\n\n1. **创建URL对象**:根据指定的URL字符串创建一个`URL`对象。\n2. **打开连接**:使用`URL`对象的`openConnection()`方法来获取一个`URLConnection`对象。\n3. **设置请求参数**(可选):可以设置请求方法、请求头等。\n4. **获取响应**:通过输入流读取响应数据。\n5. **关闭连接**:最后要记得关闭流和连接以释放资源。\n\n### 示例代码\n\n以下是一个简单的示例,它展示了如何使用`URL`和`URLConnection`进行一个HTTP GET请求:\n\n```java\nimport java.io.BufferedReader;\nimport java.io.InputStreamReader;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\n\npublic class SimpleHttpRequest {\n public static void main(String[] args) {\n try {\n // 创建URL对象\n URL url = new URL(\"https://api.example.com/data\");\n \n // 打开连接\n HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n \n // 设置请求方法\n connection.setRequestMethod(\"GET\");\n \n // Optional: 设置请求头\n connection.setRequestProperty(\"User-Agent\", \"Mozilla/5.0\");\n \n // 获取响应码\n int responseCode = connection.getResponseCode();\n System.out.println(\"Response Code: \" + responseCode);\n \n // 如果响应码为200(HTTP_OK),读取响应内容\n if (responseCode == HttpURLConnection.HTTP_OK) {\n BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));\n String inputLine;\n StringBuilder response = new StringBuilder();\n \n // 逐行读取响应\n while ((inputLine = in.readLine()) != null) {\n response.append(inputLine);\n }\n in.close();\n \n // 输出响应内容\n System.out.println(\"Response: \" + response.toString());\n } else {\n System.out.println(\"GET request not worked\");\n }\n } catch (Exception e) {\n e.printStackTrace();\n }\n }\n}\n```\n\n### 说明\n\n- 在这个示例中,我们使用了`HttpURLConnection`来进行HTTP请求。\n- `setRequestMethod(\"GET\")`指定了请求方法为GET。\n- `getResponseCode()` 获取服务器响应的状态码,以判断请求是否成功。\n- 使用`BufferedReader`读取响应流,逐行处理数据以避免内存消耗过大。\n- 确保在完成后关闭输入流,以释放系统资源。\n\n### 温馨提示\n\n- 如果你处理的是复杂的API,可能需要查阅API文档来了解需要的请求头和参数。\n- 在处理网络请求时,建议至少在try-catch块中处理异常,避免程序崩溃。\n- 你可以根据需求修改请求方法(例如POST),并使用`OutputStream`发送请求体。\n\n希望这些内容能帮助你顺利进行网络请求!如果有其他问题,请随时询问。加油!',0,0,0,'2024-12-06 12:12:51','2024-12-27 09:44:03'),(300921,100091,200408,'守护线程(Daemon Thread)是一种特殊类型的线程,用于执行后台任务和服务。它的存在并不会阻止程序的正常终止。在Java等编程语言中,守护线程通常用于处理一些不影响主程序生命周期的任务,如垃圾回收、系统监视等。\n\n### 守护线程的特点:\n\n1. **后台运行**:守护线程通常在后台运行,支持主线程的执行,执行一些不影响主程序的任务。\n\n2. **自动结束**:如果所有的非守护线程(用户线程)都结束了,JVM会自动退出,守护线程会被终止,不再继续运行。\n\n3. **优先级较低**:守护线程的优先级通常较低,因为它的目标是支持主线程的任务,而不是直接处理用户请求。\n\n4. **适合做辅助任务**:守护线程适合用于执行一些辅助性和周期性的任务,如监测状态、资源管理等,而不是完成关键的业务逻辑。\n\n5. **创建方式**:在Java中,可以通过`Thread`类的`setDaemon(true)`方法将一个线程设置为守护线程。在运行开始之前必须设置,不能在线程启动后修改。\n\n6. **不保证完全执行**:由于守护线程的生命被非守护线程所限制,因此不能保证守护线程完成所有任务,特别是在应用程序迅速终止的情况下。\n\n### 使用示例:\n\n```java\npublic class DaemonThreadExample {\n public static void main(String[] args) {\n Thread daemonThread = new Thread(() -> {\n while (true) {\n System.out.println(\"守护线程运行中...\");\n try {\n Thread.sleep(1000);\n } catch (InterruptedException e) {\n System.out.println(\"守护线程被中断\");\n }\n }\n });\n \n daemonThread.setDaemon(true);\n daemonThread.start();\n \n // 主线程工作片刻然后结束\n System.out.println(\"主线程工作中...\");\n try {\n Thread.sleep(3000);\n } catch (InterruptedException e) {\n e.printStackTrace();\n }\n \n System.out.println(\"主线程即将结束\");\n }\n}\n```\n\n在这个示例中,守护线程会在主线程工作期间在后台打印信息,但随着主线程结束,守护线程也会随之终止。\n\n总之,守护线程是提高程序性能、处理辅助性任务的好帮手,但在使用时需谨慎管理其生命周期和负责的任务。',0,0,0,'2024-12-06 12:13:02','2024-12-27 09:44:03'),(300922,100094,200707,'你有没有想过,为什么在现代应用中并发编程变得如此重要?随着多核心处理器的发展,我们是不是应该充分利用这些资源呢?Java中的并发编程模型正是为此而设计的。\n\n在Java中,并发编程模型主要通过`java.util.concurrent`包中的各种类和接口来实现。你是否知道,使用线程来执行任务时,会遇到许多挑战,比如数据竞争、死锁和资源管理等问题?为了应对这些挑战,Java提供了一些强大的并发工具类,比如`CountDownLatch`和`CyclicBarrier`。\n\n**CountDownLatch**的一个常见应用场景是:当你希望一个线程在完成多个其他线程的任务后再继续执行时,是否想过如何方便地实现这个需求呢?例如,假设有一个任务需要在多个子任务完成后才能执行。使用`CountDownLatch`,你可以初始化一个计数器,并在每个子任务完成时调用`countDown()`,这样主任务就能在所有子任务完成后再执行。这样做是否能提高整个任务的执行效率?\n\n而**CyclicBarrier**又是如何为你提供了另一种解决方案呢?假如你想要让多个线程在某个点汇聚,然后再一起继续执行,这种情况下`CyclicBarrier`显得尤为合适。你能想象在并行处理大量数据时,各个线程怎样齐心协力,协同迭代吗?`CyclicBarrier`能够让这些线程在完成某个阶段后互相等待,然后再一起开始下一个阶段,这是不是提升了整体的协作效率?\n\n总之,通过合理运用这些并发工具类,是否能够有效减轻了编程的复杂度,同时也提升了性能?此外,掌握这些工具是否能帮你在并发编程时避免常见的陷阱?你对此有过什么实践吗?',0,0,0,'2024-12-06 12:13:13','2024-12-27 09:44:03'),(300923,100099,200437,'在Java中,线程安全的单例模式有多种实现方式,今天让我们来聊聊其中一种被广泛接受的方法——“双重检查锁定(Double-Check Locking)”。这就像是你在厨房里同时煮饭和做沙拉,得确保不会把沙拉放进锅里去!\n\n下面是如何实现它的代码例子:\n\n```java\npublic class Singleton {\n // 私有化构造函数,防止外部实例化\n private Singleton() {}\n\n // 持有唯一实例的静态变量\n private static volatile Singleton instance;\n\n // 提供一个全局访问点\n public static Singleton getInstance() {\n // 第一重检查\n if (instance == null) {\n synchronized (Singleton.class) {\n // 第二重检查\n if (instance == null) {\n instance = new Singleton();\n }\n }\n }\n return instance;\n }\n}\n```\n\n### 解释一下这个小窍门:\n1. **私有构造函数**:我们不想让别人用`new`去创建实例,对吧?所以把构造函数设为私有的,就像是一个扭着头不想搭理你的可怜的小猫。\n\n2. **volatile关键字**:我们在`instance`变量前加上`volatile`关键字,目的是确保实例的可见性。在多线程环境中,这就像是在告诉其他线程“喂,这里有个新鲜出炉的单例哦,别再用旧的了!”\n\n3. **双重检查**:来看`getInstance()`方法,首先检查`instance`是否为`null`,如果是,就加锁。再检查一次`instance`,确保果然是`null`之后再创建实例。这样可以减少锁的开销,就像是每次做饭都不想等着锅开,只想一开始就下去把菜做熟!\n\n### 总结:\n这种单例模式的优势是线程安全,并且在大多数情况下不会影响性能。对于极端冷静的多线程场景,它能保证我们不会制造出一堆多余的实例,保持我们的内存优雅。希冀它能让你的程序在竞争中脱颖而出,就像在舞池里跳舞一样出色!',0,0,0,'2024-12-06 12:13:24','2024-12-27 09:44:03'),(300924,100109,200302,'在Java中,使用NIO(New Input/Output)实现高效文件传输就像跟朋友一起做健身,不但要努力,还得用对方法!以下是一些步骤,帮助你在“文件传输大赛”中轻松取胜:\n\n### 1. 导入必要的包\n\n首先,别忘了引入NIO的相关类。就像做体操之前要拉伸,以免受伤!\n\n```java\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.channels.FileChannel;\nimport java.io.IOException;\n```\n\n### 2. 准备源文件和目标文件\n\n接下来,准备好源文件和目标文件的路径。这就像在比赛前确定好起点和终点。\n\n```java\nPath sourcePath = Paths.get(\"path/to/source/file.txt\");\nPath destinationPath = Paths.get(\"path/to/destination/file.txt\");\n```\n\n### 3. 使用FileChannel进行文件传输\n\n接下来是重头戏,用NIO的`FileChannel`来实现文件的快速转移。可以用它的transferTo()方法,简直就是极速通道,连光速都得靠边站!\n\n```java\ntry (FileChannel sourceChannel = FileChannel.open(sourcePath);\n FileChannel destinationChannel = FileChannel.open(destinationPath)) {\n long position = 0;\n long size = sourceChannel.size();\n\n // 使用transferTo进行直接传输,直到传输完毕\n while (position < size) {\n position += sourceChannel.transferTo(position, 1024 * 1024, destinationChannel);\n }\n} catch (IOException e) {\n e.printStackTrace();\n}\n```\n\n### 4. 优化性能(如果需要)\n\n想要进一步提升性能,你可以考虑使用内存映射(MappedByteBuffer),这就像给你的文件传输加上了“推进器”。但是,这种方法稍微复杂一点,适合对“高级健身”有兴趣的你。\n\n### 总结\n\n使用Java NIO进行文件传输,可以用很少的代码实现高效率,简直是懒人福音!就像健身时,科学安排训练和休息时间,能让你事半功倍。希望你的文件传输如风般迅速,没有任何阻碍!',0,0,0,'2024-12-06 12:13:32','2024-12-27 09:44:03'),(300925,100078,200631,'Spring Cloud Gateway 是一个用于构建 API 网关的工具,它为微服务架构提供了一种简单而有效的路由机制,能够处理 HTTP 请求并将其转发到后端服务。它支持动态路由、限流、熔断、请求过滤、监控等功能,非常适合用于在微服务系统中统一管理和控制网络流量。\n\n### Spring Cloud Gateway 的主要特点:\n1. **动态路由**:可以根据请求的 URL、请求参数、请求头等动态拼接后端服务地址。\n2. **过滤器支持**:允许在请求和响应的生命周期中添加自定义逻辑,比如请求改写、响应修改、请求限流等。\n3. **微服务集成**:与 Spring Cloud 生态系统的其他组件(如 Eureka、Consul 等)无缝集成,方便服务发现与注册。\n4. **监控和指标**:支持 Spring Boot Actuator,可以轻松集成监控。\n\n### 如何在 Spring Boot 中使用 Spring Cloud Gateway\n\n#### 步骤 1:创建 Spring Boot 项目\n你可以使用 Spring Initializr 创建一个新的 Spring Boot 项目,在依赖中添加 `Spring Web` 和 `Spring Cloud Gateway`。\n\n#### 步骤 2:添加依赖\n在 `pom.xml` 文件中添加 Spring Cloud Gateway 依赖:\n\n```xml\n\n org.springframework.cloud\n spring-cloud-starter-gateway\n\n```\n\n确保你的项目中有 Spring Cloud 依赖管理,可以在 `pom.xml` 中加入:\n\n```xml\n\n \n \n org.springframework.cloud\n spring-cloud-dependencies\n 2021.0.5 \n pom\n import\n \n \n\n```\n\n#### 步骤 3:配置路由\n可以在 `application.yml` 或 `application.properties` 文件中配置路由。例如,在 `application.yml` 文件中添加以下路由配置:\n\n```yaml\nspring:\n cloud:\n gateway:\n routes:\n - id: example_route\n uri: http://example.com # 后端服务的URL\n predicates:\n - Path=/example/** # 匹配路径\n filters:\n - StripPrefix=1 # 去掉前缀\n```\n\n#### 步骤 4:启动应用\n现在,使用 `main` 方法启动 Spring Boot 应用,你的 API 网关就可以工作了。访问 `/example/some-endpoint` 会转发请求到 `http://example.com/some-endpoint`。\n\n#### 步骤 5:自定义过滤器\n可以创建自定义过滤器来增强功能。例如,创建一个拦截器来记录日志。\n\n```java\nimport org.springframework.cloud.gateway.filter.GatewayFilter;\nimport org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class LoggingGatewayFilterFactory extends AbstractGatewayFilterFactory {\n \n public LoggingGatewayFilterFactory() {\n super(Config.class);\n }\n \n @Override\n public GatewayFilter apply(Config config) {\n return (exchange, chain) -> {\n // 记录请求日志\n System.out.println(\"Request: \" + exchange.getRequest().getURI());\n return chain.filter(exchange).then(Mono.fromRunnable(() -> {\n // 记录响应日志\n System.out.println(\"Response complete\");\n }));\n };\n }\n\n public static class Config {\n // 配置项可以放在这里\n }\n}\n```\n\n通过以上步骤,你就可以成功地在 Spring Boot 中集成并使用 Spring Cloud Gateway,构建一个灵活的微服务 API 网关,掌控流量的每一个细节!',0,0,0,'2024-12-06 12:13:42','2024-12-27 09:44:03'),(300926,100010,201211,'你有没有想过,为什么在图形编程中我们需要特别关注GPU资源呢?GPU资源可以被视为用于渲染的实际数据和对象,比如纹理、缓冲区和着色器。它们对图形性能有多大的影响? \n\n在DirectX中,管理这些资源涉及几个关键步骤。首先,你是否了解如何创建和初始化这些资源?一般来说,这涉及到使用特定的API调用,比如`CreateTexture2D`或`CreateBuffer`等。你认为这些方法背后的原理是什么呢?\n\n接着,资源的生命周期管理也是非常重要的。你有没有考虑过,当一个资源不再被使用时,应该如何释放它呢?在DirectX中,这通常是通过`Release`方法来实现的。这是否让你想到了资源管理的最佳实践,像是避免内存泄漏?\n\n此外,如何高效地使用和共享这些资源也是一个挑战。你认为使用资源视图(如SRV和UAV)会对这一点有所帮助吗?这些视图能否使资源的重用变得更加简单和高效?\n\n最后,考虑到多线程渲染,你觉得在这方面如何管理GPU资源会更加复杂呢?线程之间如何安全地访问和操作这些资源,或许是一个值得探讨的话题吧?\n\n所以,说到管理GPU资源,你认为如何进行合理的规划和实施,才能提高图形应用的性能和稳定性呢?',0,0,0,'2024-12-06 12:13:49','2024-12-27 09:44:03'),(300927,100033,200214,'模板方法模式(Template Method Pattern)是一个设计模式,属于行为型模式。它定义了一个算法的骨架,而将一些步骤延迟到子类中实现。这使得子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。可以想象成一位大厨,给你一个固定的菜谱,只不过某些细节要你自己决定,比如“放盐”这一步,咱们的盐用自家独特的调味品就完事了!\n\n### 结构\n\n在Java中,模板方法模式通常包含以下几个部分:\n\n1. **抽象类(Abstract Class)**:定义了模板方法和基本的步骤。\n2. **具体类(Concrete Class)**:实现了抽象类中的某些具体步骤。\n\n### 代码示例\n\n```java\nabstract class AbstractClass {\n // 模板方法\n public void templateMethod() {\n stepOne();\n stepTwo();\n stepThree();\n }\n\n // 具体的步骤\n protected abstract void stepOne();\n protected abstract void stepTwo();\n\n // 默认步骤\n protected void stepThree() {\n System.out.println(\"执行步骤三\");\n }\n}\n\nclass ConcreteClassA extends AbstractClass {\n @Override\n protected void stepOne() {\n System.out.println(\"ConcreteClassA 执行步骤一\");\n }\n\n @Override\n protected void stepTwo() {\n System.out.println(\"ConcreteClassA 执行步骤二\");\n }\n}\n\nclass ConcreteClassB extends AbstractClass {\n @Override\n protected void stepOne() {\n System.out.println(\"ConcreteClassB 执行步骤一\");\n }\n\n @Override\n protected void stepTwo() {\n System.out.println(\"ConcreteClassB 执行步骤二\");\n }\n}\n\npublic class Main {\n public static void main(String[] args) {\n AbstractClass classA = new ConcreteClassA();\n classA.templateMethod();\n\n AbstractClass classB = new ConcreteClassB();\n classB.templateMethod();\n }\n}\n```\n\n### 应用场景\n\n1. **框架设计**:很多框架都使用模板方法模式来定义一种操作流程。例如,Spring框架的某些部分采用此模式来处理类的生命周期。\n2. **数据处理**:例如在不同的文件格式处理(CSV, JSON 等)时,可以先定义处理流程,再在子类中实现具体的文件处理逻辑。\n3. **游戏开发**:在对象(比如角色)逻辑处理时,可以先定义状态转换流程,再通过子类进行具体实现。\n4. **UI组件**:在构建用户界面的时候,一些组件的操作可以抽出共同部分,具体的绘制逻辑交给子类来实现。\n\n总而言之,模板方法模式就像为你设定了一个旅游的行程,你可以在其中自在选择想吃的美食或者想玩的项目,但整体的路线却是老妈定好的。 这样不仅保证了行程的高效性,还能让你体验到广阔的自由!',0,0,0,'2024-12-06 12:14:04','2024-12-27 09:44:03'),(300928,100100,201112,'在C++中,使用Boost.Asio库进行网络编程就像在浩瀚的海洋中航行,你需要一套可靠的航海工具和指南来确保你能稳健地穿梭于网络的波涛之间。下面是使用Boost.Asio进行网络编程的基本步骤以及它的优势。\n\n### 基本步骤:\n\n1. **引入Boost.Asio库**:\n 首先,你需要在你的C++项目中包含Boost.Asio库的头文件。通常你会在你的代码中加入以下几行:\n ```cpp\n #include \n ```\n\n2. **初始化I/O上下文**:\n 所有的异步操作都依赖于I/O上下文,这是Boost.Asio的核心。在你的程序中创建一个`io_context`对象:\n ```cpp\n boost::asio::io_context io_context;\n ```\n\n3. **创建网络连接**:\n 根据你的需求,你可以使用TCP或UDP协议。在这里以TCP为例,你可以创建一个接受连接的`acceptor`:\n ```cpp\n boost::asio::ip::tcp::acceptor acceptor(io_context, \n boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));\n ```\n\n4. **处理连接**:\n 对于接收到的连接请求,你可以使用异步操作来处理它们。通常你会定义一个处理函数,并在其中调用`async_accept`:\n ```cpp\n acceptor.async_accept(handle_accept);\n ```\n\n5. **运行I/O上下文**:\n 最后,你需要启动I/O上下文来处理所有的异步操作,这就像是启航前的最后准备:\n ```cpp\n io_context.run();\n ```\n\n6. **样板代码**:\n 下面是一个简单的TCP服务器的样板代码:\n ```cpp\n #include \n #include \n\n void handle_accept(const boost::system::error_code& error) {\n if (!error) {\n std::cout << \"New connection accepted!\" << std::endl;\n // 进一步处理连接\n }\n }\n\n int main() {\n try {\n boost::asio::io_context io_context;\n boost::asio::ip::tcp::acceptor acceptor(io_context, \n boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 12345));\n\n acceptor.async_accept(handle_accept);\n io_context.run();\n } catch (std::exception& e) {\n std::cerr << \"Exception: \" << e.what() << std::endl;\n }\n return 0;\n }\n ```\n\n### 优势:\n\n1. **异步操作**:\n Boost.Asio使得异步编程变得简单而高效。你可以处理许多连接而无需阻塞,这让你的应用程序能够并发处理大量任务,像一位灵活的海员,游刃有余。\n\n2. **跨平台支持**:\n 这个库支持多种操作系统,能够在Linux、Windows、macOS等平台上无缝运行,让你的代码如同经得起风浪的船只,随时随地都可以启航。\n\n3. **高性能**:\n 它采用了异步I/O模型,能够在高负载条件下保持良好的性能,提供低延迟、高吞吐的网络服务。\n\n4. **丰富的功能**:\n 除了基本的TCP/UDP支持外,Boost.Asio还提供了定时器、串行端口、线程池等功能,能够帮助你实现更加多样化的网络应用。\n\n5. **良好的文档和社区支持**:\n Boost.Asio有着丰富的文档,社区活跃,有许多实例和教程帮助你快速上手,如同一座灯塔,引导你走出迷途。\n\n使用Boost.Asio,网络编程变得高效而灵活,就像航海中掌握了风向和潮汐般自信。无论你构建的是简单的服务器还是复杂的网络应用,Boost.Asio都会是你强有力的助手。',0,0,0,'2024-12-06 12:14:18','2024-12-27 09:44:03'),(300929,100070,200136,'哦,SRAM和DRAM这对儿兄弟可有意思了,听我来给你详细“吐槽”一下他们的区别和应用场景。\n\n先说说SRAM(静态随机存取存储器),这家伙可谓是内存界的高富帅,速度快得令人咋舌,存取数据不需要不断刷新,简直就是个懒汉。它的每个存储单元都用四到六个晶体管来保存数据,虽然功耗较低,但制造成本高得让人瞠目结舌。因为太贵了,SRAM一般用在需要高速缓存的地方,比如CPU的缓存、路由器、嵌入式系统等等。可以说,SRAM就像是那个一出生就有金钥匙的小孩,干嘛不直接把他送到高端场所呢?\n\n再来聊聊DRAM(动态随机存取存储器)。这位兄弟就完全是个性格迥异的勤奋小工,尽管它存取速度相比SRAM慢了不少,但它的成本低得可怕,因此存储容量巨大的时候,反倒显得它更有价值。DRAM的每个存储单元只需要一个晶体管和一个电容,但那电容可不省心,得时不时地刷新一下,才能保持数据不消失。一般来说,DRAM被广泛应用于计算机主内存、手机等产品中,像是一个普通家庭的孩子,虽然没那么耀眼,但人家数量庞大,众人皆知。\n\n总结一下:SRAM是内存界的“富二代”,适合高频、高速的地方;而DRAM是“普通劳动者”,适合需要大容量存储的地方。所以,你问哪个更好?这就好比问你喜欢哪个饮料,是喜欢健身房的能量饮料,还是喜欢大排档的啤酒,完全看你那时的需求和口味呀!',0,0,0,'2024-12-06 12:14:25','2024-12-27 09:44:03'),(300930,100030,200220,'在Java中,反射机制是一种强大的工具,可以在运行时动态访问类的属性和方法,包括构造函数。下面是一个示例,展示了如何使用反射机制来获取一个类的构造函数并创建对象。\n\n### 示例类\n\n我们先定义一个简单的类 `Person`:\n\n```java\npublic class Person {\n private String name;\n private int age;\n\n public Person(String name, int age) {\n this.name = name;\n this.age = age;\n }\n \n @Override\n public String toString() {\n return \"Person{name=\'\" + name + \"\', age=\" + age + \"}\";\n }\n}\n```\n\n### 使用反射获取构造函数并创建对象\n\n接下来,我们使用反射获取 `Person` 类的构造函数,并通过该构造函数创建一个 `Person` 对象:\n\n```java\nimport java.lang.reflect.Constructor;\n\npublic class ReflectionExample {\n public static void main(String[] args) {\n try {\n // 获取Person类的Class对象\n Class personClass = Class.forName(\"Person\");\n \n // 获取带有参数的构造函数\n Constructor constructor = personClass.getConstructor(String.class, int.class);\n \n // 使用构造函数创建对象\n Object personInstance = constructor.newInstance(\"Alice\", 30);\n \n // 打印创建的对象\n System.out.println(personInstance);\n \n } catch (Exception e) {\n e.printStackTrace();\n }\n }\n}\n```\n\n### 代码解析\n\n1. **获取Class对象**:使用 `Class.forName(\"Person\")` 获取 `Person` 类的 `Class` 对象。\n \n2. **获取构造函数**:通过 `personClass.getConstructor(String.class, int.class)` 获取参数为 `String` 和 `int` 的构造函数。\n\n3. **创建对象**:使用 `constructor.newInstance(\"Alice\", 30)` 调用构造函数,传入参数以创建对象。\n\n4. **输出结果**:打印创建的对象信息,这里会调用 `toString()` 方法输出 `Person` 对象的状态。\n\n### 出现异常的处理\n\n在反射操作中,可能会出现多种异常,例如:`ClassNotFoundException`、`NoSuchMethodException`、`InstantiationException`、`IllegalAccessException`、`InvocationTargetException` 等,因此我们需要用 `try-catch` 语句来处理这些异常。\n\n### 运行结果\n\n运行以上代码,如果一切正常,你将看到如下输出:\n\n```\nPerson{name=\'Alice\', age=30}\n```\n\n通过这种方式,你可以灵活地在运行时创建对象,而无需直接使用 `new` 关键字,这是反射机制的魅力之一!✨',0,0,0,'2024-12-06 12:14:34','2024-12-27 09:44:03'),(300931,100003,200704,'在Java中,内存泄漏(Memory Leak)指的是程序中不再使用的对象仍然被引用,从而使垃圾回收器无法回收这些对象的内存。即使是Java这样的自动内存管理语言,内存泄漏依然可能导致内存的逐渐耗尽,影响程序的性能,甚至导致应用崩溃。\n\n### 如何识别内存泄漏\n\n1. **监控内存使用情况**:\n 使用工具如`VisualVM`、`Java Mission Control`或`Eclipse Memory Analyzer` (MAT),可以监控JVM的内存使用情况,查找不再需要的对象。\n\n2. **Heap Dump分析**:\n 通过生成Heap Dump文件,查看具体的对象和其引用链,找到不被最终回收的对象。如果发现大量的对象持续存在,可能是内存泄漏的迹象。\n\n3. **使用分析工具**:\n 使用像`FindBugs`、`SonarQube`或`Checkstyle`等静态分析工具,帮助检测潜在的内存泄漏问题。\n\n4. **性能监测**:\n 如果应用变得缓慢,或者在长时间运行后出现`OutOfMemoryError`,这些都可能是内存泄漏的信号。\n\n### 解决内存泄漏问题\n\n1. **合理使用集合**:\n 如果使用`List`、`Map`等集合,确保在不需要引用时清理它们,比如在操作完成后移除不再需要的对象。\n\n2. **避免静态引用**:\n 静态字段保持对对象的引用,确保清理不再需要的静态引用。\n\n3. **使用弱引用(WeakReference)**:\n 对于长期存活的缓存,使用`WeakReference`可以让JVM在内存不足时清理这些对象。\n\n4. **关闭资源**:\n 确保及时关闭数据库连接、文件流等资源,释放内存和系统资源。\n\n5. **代码审查与重构**:\n 定期进行代码审查,寻找不必要的对象创建和引用,加强代码的可视化和可维护性。\n\n6. **使用`finalize()`方法**:\n 可以重写对象的`finalize()`方法来实现清理工作,但要谨慎使用,因为这会影响性能,并且在Java 9后已不再推荐。\n\n### 科技与艺术的结合\n解决内存泄漏就像是一场精妙的舞蹈,需要工程师在复杂的代码与内存边界之间翩翩起舞。通过监控、分析、清理和优雅的重构,程序将如莲花般在内存的池塘中绽放,避免被无尽的内存消耗所束缚。最终,以流畅的运行和高效的响应,迎接用户的赞美!',0,0,0,'2024-12-06 12:14:42','2024-12-27 09:44:03'),(300932,100070,201076,'在 Qt 中,读取和写入文件可以通过 `QFile` 类来实现。下面是一个简单的例子,展示了如何使用 `QFile` 读取和写入文本文件。\n\n### 写入文件\n\n```cpp\n#include \n#include \n#include \n#include \n\nvoid writeToFile(const QString &fileName, const QString &content) {\n QFile file(fileName);\n\n // 打开文件以进行写入\n if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {\n qDebug() << \"无法打开文件以写入:\" << file.errorString();\n return;\n }\n\n QTextStream out(&file);\n out << content; // 写入内容\n\n file.close(); // 关闭文件\n qDebug() << \"写入成功!\";\n}\n```\n\n### 读取文件\n\n```cpp\n#include \n#include \n#include \n#include \n\nQString readFromFile(const QString &fileName) {\n QFile file(fileName);\n QString content;\n\n // 打开文件以进行读取\n if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {\n qDebug() << \"无法打开文件以读取:\" << file.errorString();\n return QString(); // 返回空字符串\n }\n\n QTextStream in(&file);\n content = in.readAll(); // 读取所有内容\n\n file.close(); // 关闭文件\n qDebug() << \"读取成功!\";\n return content;\n}\n```\n\n### 使用示例\n\n你可以在主函数中调用上述函数来测试它们的功能:\n\n```cpp\nint main() {\n QString fileName = \"example.txt\";\n QString contentToWrite = \"Hello, Qt!\\nWelcome to file handling.\";\n\n // 写入文件\n writeToFile(fileName, contentToWrite);\n\n // 读取文件\n QString contentRead = readFromFile(fileName);\n qDebug() << \"文件内容:\" << contentRead;\n\n return 0;\n}\n```\n\n### 总结\n\n1. 使用 `QFile` 打开文件(读取或写入模式)。\n2. 使用 `QTextStream` 来处理文本内容。\n3. 记得在完成操作后关闭文件。\n\n这样,你就可以在 Qt 中轻松地读取和写入文件啦!发挥你的想象力,创建出更多有趣的文件处理应用吧!',0,0,0,'2024-12-06 12:15:22','2024-12-27 09:44:03'),(300933,100077,200862,'当然!让我们一起来揭开这三个容器 `map`、`deque` 和 `list` 的神秘面纱,看看它们的实现原理。\n\n### 1. `map`\n\n`map` 是一个关联容器,通常实现为红黑树(红黑树是一种自平衡的二叉查找树)。这种设计使得 `map` 可以高效地进行元素的插入、删除和查找操作,时间复杂度都是 O(log n)。 具体来说:\n\n- **存储结构**:`map` 以键值对的形式存储数据,每一个节点包含一个键、一个值和指向左右子节点的指针。\n- **有序性**:由于是基于树的结构,`map` 中的元素始终保持键的有序性,可以方便地进行范围查询。\n- **平衡性**:红黑树的特点是通过维护某些性质,确保树的高度不会过大,从而保证操作的时间复杂度。\n\n### 2. `deque`\n\n`deque`(双端队列)是一种线性容器,允许从两端进行高效的插入和删除。它的实现一般可以分为以下几种方式:\n\n- **数组实现**:虽然可以在一个连续的数组中实现,但在插入的时候需要进行扩展,效率不高。\n- **链表实现**:使用双向链表,每个节点都有一个前向指针和后向指针,可以在两端都进行高效操作。\n- **分段数组**:一种更为高效的实现方法是使用多个固定大小的数组段,形成一个环形结构。这种方法使得在两端添加或删除元素时不会涉及到大量的内存移动。\n\n### 3. `list`\n\n`list` 通常实现为双向链表。每个节点都包含数据和指向前一个节点和后一个节点的指针。这种结构使得在中间插入和删除元素时非常高效,时间复杂度 O(1),但随机访问某一元素时效率较低,时间复杂度是 O(n)。\n\n- **节点结构**:每个节点包含一个数据域和指针,形成链表。头节点用于表示链表的起始位置,尾节点表示结束位置。\n- **动态大小**:由于是链表结构,`list` 可以动态扩展,没有固定大小的限制。\n- **插入与删除**:在 `list` 中,可以快速地在任意位置插入或删除元素,而不需要像数组那样移动其他元素。\n\n### 总结\n\n- `map` 是以红黑树为基础的有序键值对容器。\n- `deque` 以双向链表或多个段数组实现,允许在两端高效操作。\n- `list` 则是一个经典的双向链表,适合频繁的插入和删除操作。\n\n每个容器都有其适用的场景和特定的优缺点,选择适合的容器能够让我们在编程的道路上如虎添翼!',0,0,0,'2024-12-06 12:15:34','2024-12-27 09:44:03'),(300934,100060,200939,'在C++中,内存碎片整理技术就像是整理一个凌乱的房间,目的是将那些零散的物品(内存块)重新整理,使空间利用更高效,以便更容易找到和使用它们。\n\n### 1. **什么是内存碎片?**\n内存碎片就像是房间里到处散落的物品,经过多次的内存分配和释放后,内存中的空闲区域可能会变得支离破碎。在计算机中,内存可以被划分为多个块,当我们申请和释放内存时,容易产生小的、分散的空闲区域,这些空闲区域无法被有效利用。\n\n### 2. **内存碎片的种类**\n- **外部碎片**:当内存中存在多个小的空闲块,这些小块加起来的大小虽然足够,但它们的位置分散,使得无法满足一个大的内存请求。\n- **内部碎片**:在内存分配时,有时会分配比实际需求更大的内存块,导致分配给应用程序的内存一部分没有被使用,这部分未使用的内存就叫做内部碎片。\n\n### 3. **内存碎片整理技术**\n为了应对内存碎片问题,C++及其运行时环境(如操作系统或内存管理库)通常会采取一些技术手段:\n\n- **合并空闲块**:当一个内存块被释放后,系统会尝试将其与相邻的空闲块合并,形成一个更大的空闲块。这就好比将房间里相邻的桌子、椅子搬到一起,腾出更多空间。\n\n- **分配策略**:使用不同的分配策略,如最佳适应、最差适应、首次适应等,来尽量减少分配内存时产生的碎片。例如,最佳适应策略会寻找最小但足够大的空闲块,以减少剩余的碎片。\n\n- **垃圾回收(GC)**:虽然C++不直接支持自动垃圾回收,但可以使用像Smart Pointer这样的工具来管理资源,以减少内存泄漏和碎片问题。垃圾回收机制通过定期回收不再使用的内存块,清理过时的内容,从而减少碎片。\n\n- **内存池**:在某些情况下,可以使用内存池技术,将内存预先分配好并管理,从而避免频繁的分配和释放导致的碎片。就像提前准备好一堆盒子,将物品有序地放入每个盒子中,这样就不会产生临时的凌乱。\n\n### 4. **为什么要整理内存碎片?**\n整理内存碎片的重要性在于:\n- **提高性能**:减少内存访问的延迟,提高程序的响应速度。\n- **优化内存利用**:避免内存浪费,提高程序的运行效率,确保可用内存的最大化。\n- **保障稳定性**:防止内存不足的情况出现,影响程序的稳定性和安全性。\n\n综上所述,内存碎片整理技术在C++中以多种方式帮助我们高效地管理内存,保护我们的程序运行得更流畅,就像在厨房中保持整洁有序,能更快地找到想用的厨具一样!',0,0,0,'2024-12-06 12:15:48','2024-12-27 09:44:03'),(300935,100061,200849,'Pimpl(Pointer to IMPLementation)是一种在C++中使用的惯用法,旨在通过将类的实现细节从类声明中分离出来,来减少编译依赖和提高封装性。\n\n### Pimpl的基本概念:\n在C++中,你通常会看到这样的类定义:\n\n```cpp\nclass MyClass {\npublic:\n void doSomething();\nprivate:\n int importantData;\n};\n```\n\n使用Pimpl后,结果可能看起来像这样:\n\n```cpp\nclass MyClassImpl; // 前向声明\n\nclass MyClass {\npublic:\n MyClass();\n ~MyClass();\n void doSomething();\nprivate:\n MyClassImpl* pImpl; // 指向实现的指针\n};\n```\n\n具体实现是在另一个文件中,可能长得像这样:\n\n```cpp\nclass MyClassImpl {\npublic:\n void doSomething() {\n // 实际的实现代码\n }\n int importantData;\n};\n\nMyClass::MyClass() : pImpl(new MyClassImpl) {}\nMyClass::~MyClass() { delete pImpl; }\nvoid MyClass::doSomething() { pImpl->doSomething(); }\n```\n\n### Pimpl的优势:\n\n1. **减少编译依赖**:如果你修改了 `MyClassImpl` 的实现,外部代码不需要重新编译,因为 `MyClass` 的接口没有改变。这就像给代码打上了“请勿打扰”的标签。\n\n2. **封装性**:你可以更好地封装实现细节,外部代码无法直接访问 `MyClassImpl` 的私人部分。这就像是在参加聚会时,溜进厨房做饭而不让客人看到你如何把食材变成美味的佳肴。\n\n3. **隐藏大型库的复杂性**:当使用大型库时,如果将实现细节放在头文件中,可能会导致编译时间过长。Pimpl 允许你将这些细节隐藏起来,就像隐藏在背后的小矮人,保证外表光鲜亮丽。\n\n4. **实现自由**:如果想要更换实现,比如在不同的操作系统或编译器上,使用 Pimpl 可以轻松实现,而不影响公共接口。就像你可以把自己的冰淇淋车换成热狗摊,但仍然能吸引顾客。\n\n### 结语:\n当然,Pimpl 也有一些缺点,比如额外的指针操作可能会导致轻微的性能损失以及增加了内存管理的复杂度(因为要记得在合适的时候删除它)。但是,权衡利弊,使用 Pimpl 是一个让你的 C++ 代码更加优雅和可维护的好方法!',0,0,0,'2024-12-06 12:15:59','2024-12-27 09:44:03'),(300936,100103,200525,'诊断JVM中的堆外内存泄漏可以是一个挑战,但我可以为你提供一个充满创意和实用的流程,就像破解一个神秘的密码,揭开内存泄漏的面纱。下面是一些步骤和工具,帮助你探寻这条路径:\n\n### 1. **识别症状**\n 在开始之前,首先要知道你是否确实面对堆外内存泄漏。常见症状包括:\n - 应用程序变得缓慢或不响应。\n - 系统内存使用率越来越高,导致系统崩溃或OOME(Out of Memory Error)。\n - 有规律的性能下降。\n\n### 2. **监控内存使用情况**\n 使用监控工具观察系统的内存使用情况。这可以包括:\n - **Java的内存监控工具**(如 JMX):可以监视堆和非堆内存的使用情况。\n - **系统监控工具**(如 `top` 或 `htop`): 用来观察整体系统的内存使用情况。\n\n### 3. **使用诊断工具**\n 选用一些强大的工具,比如:\n - **Java Flight Recorder (JFR)**: 可以记录应用的内存使用情况,以及堆外内存的分配信息。\n - **VisualVM**: 可以监测堆外内存的使用情况,并帮助识别潜在的泄漏点。\n - **Netty**: 如果使用Netty,可以使用其内置的工具监控直接内存的使用。\n\n### 4. **分析堆外内存的使用情况**\n 使用以下方法分析和定位堆外内存分配:\n - **JNI(Java Native Interface)分配的内存**:使用工具监控JNI的调用,确保释放所有分配的内存。\n - **直接缓冲区**:使用 `ByteBuffer.allocateDirect()` 分配的内存,需要注意这些内存不会被JVM的垃圾回收器管理。\n\n### 5. **排查代码**\n 深入代码,寻找可能导致内存泄漏的地方。注意以下情况:\n - **对象引用**:确保没有多余的静态引用或全局缓存持有实例。\n - **事件监听器**:取消不再需要的事件监听器,避免引起内存泄漏。\n\n### 6. **运行压力测试**\n 进行负载测试,增加系统压力,并监控堆外内存的使用情况。使用工具如 Apache JMeter 或 Gatling,可以帮助你模拟真实用户访问场景。\n\n### 7. **日志和分析**\n 通过添加适当的日志,记录每次堆外内存分配和释放的情况,形成监控报告,进行比较。\n\n### 8. **回收和优化**\n 一旦定位到问题,可以采取以下措施:\n - 定期释放不再使用的堆外内存。\n - 优化资源管理,确保及时关闭资源。\n\n### 结尾\n通过这些步骤,你不仅能识别和诊断JVM中的堆外内存泄漏,还能提升应用的整体性能,给用户带来更好的体验。就像侦探慢慢揭开案件的真相,细致入微,持之以恒,终会见到成效!',0,0,0,'2024-12-06 12:16:11','2024-12-27 09:44:03'),(300937,100068,201117,'在C++中使用SSL/TLS进行安全通信的步骤就像是一场精心编排的舞蹈,每一步都需要严谨和优雅。以下是实现这一目标的基本步骤和注意事项,确保你的通信既安全又流畅:\n\n### 基本步骤\n\n1. **环境准备**:\n - 首先,安装OpenSSL库,这是C++中使用SSL/TLS的常用工具。你可以通过包管理器(如apt、brew等)轻松获取。\n\n2. **引入头文件**:\n - 在你的C++代码中包含OpenSSL的头文件:\n ```cpp\n #include \n #include \n ```\n\n3. **初始化SSL库**:\n - 在程序开始时,初始化SSL库。\n ```cpp\n SSL_library_init();\n OpenSSL_add_all_algorithms();\n SSL_load_error_strings();\n ```\n\n4. **创建SSL上下文**:\n - 设置SSL/TLS协议并创建一个SSL上下文。\n ```cpp\n SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());\n ```\n\n5. **创建SSL连接**:\n - 使用SSL上下文创建一个SSL对象。\n ```cpp\n SSL *ssl = SSL_new(ctx);\n ```\n\n6. **创建套接字**:\n - 创建一个套接字来进行网络通信。\n ```cpp\n int sockfd = socket(AF_INET, SOCK_STREAM, 0);\n ```\n\n7. **连接到服务器**:\n - 通过套接字连接到目标服务器(使用`connect()`函数)。\n\n8. **将套接字与SSL对象关联**:\n - 将之前创建的套接字与SSL对象关联。\n ```cpp\n SSL_set_fd(ssl, sockfd);\n ```\n\n9. **握手过程**:\n - 进行SSL/TLS握手,这一步至关重要,用以建立安全通道。\n ```cpp\n if (SSL_connect(ssl) <= 0) {\n ERR_print_errors_fp(stderr);\n }\n ```\n\n10. **数据传输**:\n - 使用`SSL_write()`和`SSL_read()`进行加密后的数据交换。\n ```cpp\n SSL_write(ssl, \"Hello, server!\", strlen(\"Hello, server!\"));\n SSL_read(ssl, buffer, sizeof(buffer));\n ```\n\n11. **关闭连接**:\n - 关闭SSL连接和套接字,清理资源。\n ```cpp\n SSL_shutdown(ssl);\n SSL_free(ssl);\n close(sockfd);\n SSL_CTX_free(ctx);\n ```\n\n### 注意事项\n\n- **证书验证**:验证服务器的证书,以确保你连接的是真正的服务器。这可以通过`SSL_CTX_set_verify()`函数实现,确保设置为强制验证。\n \n- **错误处理**:在每一个与SSL相关的函数调用后,不要忽视错误检查,用于及时捕捉潜在问题并处理。\n\n- **多线程安全**:如果你的程序是多线程的,确保每个线程都有自己的SSL上下文,避免数据竞争。\n\n- **版本选择**:优先使用现代的TLS版本,避免低版本协议(如SSLv2、SSLv3),因为它们不再安全。\n\n- **内存管理**:使用完SSL对象和上下文后,务必释放分配的内存,避免内存泄露。\n\n通过以上步骤和注意事项,你将能够在C++中优雅地实现SSL/TLS安全通信,保障你的数据传输安全无忧,像一位舞者在网络的舞台上翩翩起舞。',0,0,0,'2024-12-06 12:16:22','2024-12-27 09:44:03'),(300938,100050,200242,'HashSet和LinkedHashSet都是Java中的集合类,它们都实现了Set接口,但是它们在一些重要方面有不同之处。\n\n1. **元素的存储顺序**:\n - **HashSet**:不保证元素的顺序。即插入的顺序与遍历的顺序可能不同。\n - **LinkedHashSet**:保持元素插入的顺序。也就是说,当你迭代LinkedHashSet时,元素将按照添加的顺序出现。\n\n2. **性能**:\n - **HashSet**:在添加、删除和查找操作时,HashSet通常提供更好的性能,因为它使用哈希表来存储元素。\n - **LinkedHashSet**:由于它保持插入顺序,LinkedHashSet的性能略逊于HashSet。在某些情况下,LinkedHashSet在操作时会比HashSet稍微慢一些,但差距通常是可以接受的。\n\n3. **使用场景**:\n - 如果你只关心元素的唯一性,并且对顺序没有要求,使用HashSet就足够了。\n - 如果你需要保持元素插入的顺序,或者想在保持唯一性的同时能够按顺序迭代元素,LinkedHashSet是一个更好的选择。\n\n希望这些信息对你有帮助!选择合适的集合类可以提高你的程序的效率和可读性,加油!',0,0,0,'2024-12-06 12:16:29','2024-12-27 09:44:03'),(300939,100104,201127,'哇,谈到C++中的网络字节序和主机字节序,感觉就像在参加一场CPU之间的拉锯战。你知道吗?大端序和小端序就像是南北方人,各自有各自的习惯,根本不想妥协!\n\n首先解释一下: \n- **大端序**(Big-endian)就是把高位字节放在低地址,低位字节放在高地址,简单点说——最前面的都是“牛逼”的数字,顺序大气好看! \n- **小端序**(Little-endian)则是把低位字节放在低地址,高位字节放在高地址,通俗点说——越往前放越小的,让人感觉有点心累。\n\n对于网络编程来说,我们可不能发出小端序的消息导致数据混乱。要是你把网络数据包发给了一个小端序的机器,它会觉得你在说“呜哇”,根本听不懂你在说啥!\n\n好了,进入正题,C++中可以使用一些常用的函数来进行字节序的转换,主要是:\n\n1. **htons**(Host TO Network Short):将主机字节序的16位整数转换为网络字节序。\n2. **htonl**(Host TO Network Long):将主机字节序的32位整数转换为网络字节序。\n3. **ntohs**(Network TO Host Short):将网络字节序的16位整数转换为主机字节序。\n4. **ntohl**(Network TO Host Long):将网络字节序的32位整数转换为主机字节序。\n\n所以,想要正确处理你的数据包,来一场字节序的”翻转“仪式吧,示例代码长这样:\n\n```cpp\n#include // 记得包含这个头文件哦\n\nuint16_t hostShort = 0x1234;\nuint16_t netShort = htons(hostShort); // 主机转网络大端\n\nuint32_t hostLong = 0x12345678;\nuint32_t netLong = htonl(hostLong); // 主机转网络大端\n\n// 反向操作\nuint16_t origHostShort = ntohs(netShort); // 网络大端转主机\nuint32_t origHostLong = ntohl(netLong); // 网络大端转主机\n```\n\n用这些函数随便一转,保证你在网络上传输的数据都是以“网络大大”的姿态传递的!总之,处理字节序就像教小孩吃饭,得耐心,不然容易“吃错字”的!',0,0,0,'2024-12-06 12:16:34','2024-12-27 09:44:03'),(300940,100111,200049,'哇,HTTP/2就像是HTTP/1.1穿上了火箭靴,准备去宇宙进行极速之旅!它在性能上有几个显著的提升,下面我们来一一揭晓这些“超级能力”!\n\n1. **多路复用(Multiplexing)**:\n 在HTTP/1.1中,很多请求只能排队等着一个连接来服务,这就像在电影院外面排队买票。而HTTP/2可以让多个请求并发通过一个连接,就像开了个VIP通道,让你看电影时可以边吃爆米花边刷手机,哪都不耽误!\n\n2. **头部压缩(Header Compression)**:\n HTTP/1.1每次请求的头部信息都有可能很庞大,就像一份厚厚的菜单,拿在手里真是一种负担。而HTTP/2使用了HPACK压缩算法,这样每次请求的头部信息都变小了,就好比把菜单变成了小卡片,轻松愉快!\n\n3. **服务器推送(Server Push)**:\n HTTP/1.1很被动,得等客户端请求资源,其实服务端心里早就准备好了。而HTTP/2就像是一个主动热情的服务员,可以在你叫餐之前,主动给你加一份饭,客户体验大提升!\n\n4. **优先级(Prioritization)**:\n 在HTTP/1.1中,请求都是平等的,结果就像是“都来啊,别客气”的聚会,可是在HTTP/2中,您可以告诉服务端哪个请求更重要,它会优先为您服务。就像在派对里,主办方说“哦,这位特别嘉宾先上台!”,让您感受到关怀。\n\n5. **二进制协议(Binary Protocol)**:\n HTTP/1.1传输的是文本包,这就像老式的黑白电视。而HTTP/2用二进制格式传输数据,这就好比换上了高清彩色电视,运行速度更快,解析更高效!\n\n总结一下,HTTP/2用这些炫酷的技术让网络传输如虎添翼,不仅速度飞快,还能提高用户体验。所以,下次上网的时候,不妨想象一下HTTP/2背后那种奔腾不息的能量,绝对能让你忍不住微笑!😄✨',0,0,0,'2024-12-06 12:16:47','2024-12-27 09:44:03'),(300941,100093,200438,'好的,锁偏向性听起来像是一个非常复杂的概念,但其实就像许多事情一样,用幽默的方式来描述会让人更容易理解。\n\n想象一下锁定(Locking)就像是一个聚会的舞会,当你刚到现场,有人为了吸引你的注意,开始跳舞(这就是锁的获取)。但你可不能随便去跳,要先和主持人(即 JVM)请个假,取得跳舞的许可。\n\n那么什么是锁偏向性呢?它就像是一位舞者超级喜欢某个人,几乎每次都赶着去找他跳舞。这个舞者(对象)一旦确定了它最喜欢的舞伴(线程),后面发生的事情就会变得轻松多了。只要这位舞者不换伴,那么每次舞会(线程竞争)开始时,它可以直接找到这个舞伴开始跳舞,而不需要每次都去请主持人的假。这就减少了排队等候的时间,让舞会变得更加热闹和流畅。\n\n换句话说,锁偏向性是为了优化多线程环境中的锁竞争。当一个线程获取了某个对象的锁后,JVM 会标记这个对象为“偏向锁”,下次这个线程再来获取锁时,就不必进行复杂的竞争或检查了,直接就可以拿到锁,就像是拥有了VIP通行证。\n\n不过,若这个舞者突然想换伴(其他线程也想参与),那么就会引发一场混乱,偏向锁就会变成重量级锁(Heavyweight Lock),这时轮到其他舞者(线程)可能就得排队了,可能会影响到派对的热闹气氛。\n\n总之,锁偏向性帮助我们在未发生竞争的情况下,尽量让锁的获取变得高效,避免不必要的开销。等到真正需要竞争的时候,才会把锁升升级,确保证舞会不会乱成一锅粥!希望这个类比能让你更容易理解这个神秘的锁偏向性!',0,0,0,'2024-12-06 12:16:58','2024-12-27 09:44:03'),(300942,100107,200428,'你知道ThreadLocal在多线程编程中有什么特殊的地方吗?ThreadLocal其实是一个让每个线程都有自己独立变量副本的工具,想想看,这样做有什么好处?\n\n它主要通过一个Map来存储每个线程的局部变量,线程在访问这些变量时,是不是就避免了竞争和共享的麻烦?这对于处理线程安全问题来说,难道不是一种简化?\n\n那么,这个机制的用途呢?在你开发应用时,是否有过需要让每个线程持有独立状态的场景?比如在数据库连接、用户会话或一些缓存信息管理中,使用ThreadLocal能否让你的代码更清晰、更高效?\n\n你认为ThreadLocal在实际应用中,有哪些需要特别注意的地方呢?比如可能导致内存泄漏的问题,是否是值得关注的?',0,0,0,'2024-12-06 12:17:03','2024-12-27 09:44:03'),(300943,100071,200765,'在C++中,`explicit`关键字是一个魔法小球,它能让你的构造函数保持清醒,不被意外转换所迷惑。使用`explicit`修饰构造函数,意味着这个构造函数只会在你明确地调用它的时候生效,避免了那些隐式类型转换带来的混乱。可以想象成一个小保安,指挥员只会在您告诉它“进来!”的时候才会放您进去,而不会因为您穿了符合要求的服装就让您随便进。\n\n### 用途示例:\n\n1. **构造函数**: 通常用于单参数构造函数。这样在你试图用一个类型的对象来创建另一个类型的对象时,编译器会警觉,确保你不是在开玩笑。\n\n```cpp\nclass MyClass {\npublic:\n explicit MyClass(int x) {\n // 你的构造逻辑\n }\n};\n\nint main() {\n MyClass obj1(10); // OK\n MyClass obj2 = 20; // 错误:不允许隐式转换\n}\n```\n\n2. **避免潜在错误**: 你可能有一个像`MyClass`这样的类,它可以通过一个`int`来构造。如果你没有使用`explicit`,可能会在不小心的情况下把一个`int`当作`MyClass`来使用,导致不可预料的结果。就像你把面粉放到咖啡机里,结果能不能泡出咖啡就很悬了。\n\n### 总结:\n\n总之,`explicit`的存在是为了让你的代码保持清醒,并确保你在做一些潜在的类型转换时不会走神。希望这能帮助你在C++的世界中少走些弯路,保持代码的优雅与清晰!',0,0,0,'2024-12-06 12:17:11','2024-12-27 09:44:03'),(300944,100117,200949,'C++的内存序列主要涉及到并发编程中的内存模型。这些内存序列主要可以分为以下六种,它们帮助开发者控制在多线程环境下数据的可见性和顺序,确保线程安全:\n\n1. **顺序一致性(SeqCst):** \n 这是最严格的内存序列,确保所有操作在一个全局上都是按照程序中出现的顺序执行的。就像是一个严格的图书馆,所有人都必须通过同一个入口,按照开放的顺序借书,确保大家的借阅顺序都一样。\n\n2. **单负载(Acquire):** \n Acquire操作会阻止后面的读写操作在此之前的操作之前被重新排序,就像在公司中,一旦你获得了一个紧急任务,所有后续的任务都必须等你完成这个任务后再执行。\n\n3. **单释放(Release):** \n Release操作会阻止前面的读写操作在此之后的操作之后被重新排序,减少了后续任务的干扰,就像一位指挥家在演出时,只有当一曲结束后,才能放行下一曲目,确保音乐的流畅性。\n\n4. **Acquire-Release(AcqRel):** \n 这是结合了Acquire和Release特性的操作,确保在此操作之前的所有操作都不会被重新排序到后面,并且此操作之后的事务不会重新排序到前面。就像一场比赛,运动员在起跑器上获得起跑权(Acquire),而在比赛结束后,才能释放赛道(Release)。\n\n5. **松散(Relaxed):** \n Relaxed序列只是为了一种基本的视图,允许编译器和CPU自由地重新排序操作,除了保证某个特定变量的原子性。相当于各自为政,给了每个人结果的灵活性,但等成果发布时间,你可能会等得心慌意乱。\n\n6. **每个变量都有不同(SeqCst、AcqRel、Acquire、Release、Relaxed):** \n 在C++中,每个变量可以使用不同的内存序列。这意味着对不同变量可以有不同的可见性和顺序控制。例如,对于一些高频率访问的共享数据,可能会使用Relaxed序列,而对一些关键数据使用SeqCst,确保一致性和明确性。\n\n通过这些内存序列,C++使得多线程编程中的数据一致性和执行顺序变得更加高效且灵活。利用好它们,能让你的程序在并发中游刃有余,就像在复杂的舞台上各个演员都是各司其职,最终呈现出完美的演出。',0,0,0,'2024-12-06 12:17:31','2024-12-27 09:44:03'),(300945,100082,200828,'在C++中,模板特化和模板偏特化是处理模板的强大工具,允许我们定制模板的行为以满足特定的需求。这两者的功能虽然相似,但使用场景和方式却有显著不同。让我们来逐一探讨。\n\n### 模板特化(Template Specialization)\n\n模板特化意味着对于特定类型的模板参数定义一个独特的实现。当我们想要为某些特定类型提供不同的行为时,就可以使用全特化。这种情况通常出现在我们需要处理的类型特征与通用实现差异较大时。\n\n**示例:**\n\n```cpp\n#include \nusing namespace std;\n\n// 通用模板\ntemplate\nclass Calculator {\npublic:\n static void calculate() {\n cout << \"通用计算逻辑\" << endl;\n }\n};\n\n// 全特化:为 int 类型提供特定的实现\ntemplate<>\nclass Calculator {\npublic:\n static void calculate() {\n cout << \"计算整型\" << endl;\n }\n};\n\n// 全特化:为 double 类型提供特定的实现\ntemplate<>\nclass Calculator {\npublic:\n static void calculate() {\n cout << \"计算浮点型\" << endl;\n }\n};\n\nint main() {\n Calculator::calculate(); // 通用计算逻辑\n Calculator::calculate(); // 计算整型\n Calculator::calculate(); // 计算浮点型\n return 0;\n}\n```\n\n在这个例子中,我们定义了一个通用的 `Calculator` 模板,并对 `int` 和 `double` 类型进行了全特化,提供了特定的逻辑。\n\n### 模板偏特化(Template Partial Specialization)\n\n偏特化与全特化相似,但它允许我们对某些模板参数进行定制,而保留其他参数为通用类型。这对于多个参数的模板特别有用,能够提高灵活性。\n\n**示例:**\n\n```cpp\n#include \nusing namespace std;\n\n// 通用模板\ntemplate\nclass Pair {\npublic:\n static void show() {\n cout << \"通用 Pair\" << endl;\n }\n};\n\n// 偏特化:当第一个模板参数为 int 时\ntemplate\nclass Pair {\npublic:\n static void show() {\n cout << \"Pair 的第一个类型是 int\" << endl;\n }\n};\n\n// 偏特化:当第二个模板参数为 double 时\ntemplate\nclass Pair {\npublic:\n static void show() {\n cout << \"Pair 的第二个类型是 double\" << endl;\n }\n};\n\nint main() {\n Pair::show(); // 通用 Pair\n Pair::show(); // Pair 的第一个类型是 int\n Pair::show(); // Pair 的第二个类型是 double\n return 0;\n}\n```\n\n在这里,我们有一个通用的 `Pair` 模板类,并进行了两次偏特化:一次是针对第一个参数为 `int`,一次是针对第二个参数为 `double`。这展示了如何灵活地针对不同的模板参数提供特定的行为。\n\n### 总结\n\n- **模板特化**用于为特定类型定义完全不同的模板行为,适用于具体化需求。\n- **模板偏特化**则允许在保持部分类型的通用性时,对某些类型进行定制,实现灵活的模板设计。\n\n这两种特性使得C++的模板系统非常强大,可以根据不同的输入进行智能而灵活的响应。充分利用这两者,可以让我们的代码更加健壮和可维护!',0,0,0,'2024-12-06 12:17:43','2024-12-27 09:44:03'),(300946,100053,201128,'在C++网络编程中,实现客户端的自动重连机制是一个很实用的功能,尤其是在处理不稳定的网络连接时。下面我将为你提供一个基本框架,展示如何实现这一机制。\n\n### 步骤一:设置基本的客户端结构\n\n首先,你需要创建一个基本的客户端,通过 `socket` 连接到服务器。确保你已经包含了必要的头文件,如 ``(在 Windows 上)或 `` 和 ``(在类 Unix 系统上)。\n\n### 步骤二:实现连接和自动重连逻辑\n\n以下是一个实现自动重连的基本代码示例:\n\n```cpp\n#include \n#include \n#include \n#include // for sockaddr_in\n\nconst int MAX_RETRIES = 5; // 最大重试次数\nconst int WAIT_TIME = 2; // 等待重连的时间,单位为秒\n\nclass Client {\npublic:\n Client(const std::string& serverIp, int serverPort)\n : serverIp(serverIp), serverPort(serverPort), sockfd(-1) { }\n\n void start() {\n while (true) {\n if (connectToServer()) {\n std::cout << \"Connected to server!\" << std::endl;\n communicate(); // 进行通信\n break; // 连接成功后退出重连循环\n } else {\n std::cout << \"Failed to connect. Retrying...\" << std::endl;\n sleep(WAIT_TIME); // 等待一段时间后重连\n }\n }\n }\n\nprivate:\n std::string serverIp;\n int serverPort;\n int sockfd;\n\n bool connectToServer() {\n sockfd = socket(AF_INET, SOCK_STREAM, 0);\n if (sockfd < 0) {\n std::cerr << \"Socket creation failed\" << std::endl;\n return false;\n }\n\n sockaddr_in serverAddress;\n std::memset(&serverAddress, 0, sizeof(serverAddress));\n serverAddress.sin_family = AF_INET;\n serverAddress.sin_port = htons(serverPort);\n inet_pton(AF_INET, serverIp.c_str(), &serverAddress.sin_addr);\n\n for (int attempt = 0; attempt < MAX_RETRIES; ++attempt) {\n if (connect(sockfd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == 0) {\n return true; // 连接成功\n }\n std::cerr << \"Attempt \" << (attempt + 1) << \" failed!\" << std::endl;\n sleep(WAIT_TIME); // 等待重试\n }\n\n close(sockfd); // 关闭socket\n return false; // 所有尝试都失败了\n }\n\n void communicate() {\n // 在这里可以实现数据收发逻辑\n // ...\n close(sockfd); // 进行完通信后关闭socket\n }\n};\n\nint main() {\n Client client(\"127.0.0.1\", 8080); // 请根据实际情况修改IP和端口\n client.start(); // 启动客户端\n return 0;\n}\n```\n\n### 重要提示\n\n1. **错误处理**:在真实应用中,要有更详细的错误处理,以应对不同的错误情况。\n2. **多线程**:如果你的客户端需要同时处理网络通信和其他任务,可以考虑使用多线程或异步编程技术。\n3. **可配置参数**:把重试次数、等待时间等参数设置为可配置选项,可以提高程序的灵活性。\n\n你可以根据自己的具体需要进一步完善这个框架。希望这个例子能够帮助你有效地实现自动重连功能!如果有其他问题或需要更多的帮助,请随时问我。我会尽力支持你!',0,0,0,'2024-12-06 12:18:03','2024-12-27 09:44:03'),(300947,100057,200694,'当然可以!在微服务架构中,“服务熔断”和“服务降级”这两个概念相辅相成,但又各自扮演着不同的角色。我来为你揭开它们的神秘面纱。\n\n### 服务熔断(Circuit Breaker)\n\n想象一个电路,当负载过高时,为了保护电路本身,我们会切断电流,防止设备损坏。在微服务中,服务熔断也是这个道理。当一个服务出现故障或响应时间过长时,熔断器会迅速切断与该服务的连接。这个过程包含以下几个步骤:\n\n1. **监控失败**:系统监测到某个服务调用失败的比例达到设定阈值。\n2. **打开熔断器**:熔断器“打开”,后续的调用将被拒绝,直接返回错误信息。\n3. **保护服务**:通过拒绝请求,保护后端服务不被进一步压垮。\n4. **短暂恢复**:经过一段时间的“冷却”,系统会尝试重新调用该服务。如果发现服务恢复正常,熔断器会“合上”。\n\n### 服务降级(Service Degradation)\n\n而服务降级则像是一个紧急的业务策略。当某个服务无法正常响应时,系统会根据需求和资源情况,切换到一个较低版本或简化版本的服务。这机制通常用于提供基本的服务体验,确保用户体验不会彻底崩溃。下面是服务降级的几个特征:\n\n1. **优先级处理**:对业务流程中的重要功能进行优先级排序,确保核心功能可用。\n2. **预先定义**:在系统中预先设计好降级逻辑,比如返回一个默认值或者静态页面。\n3. **动态适应**:根据实际情况,动态调整需要降级的服务,以保持系统的韧性。\n\n### 总结\n\n综上所述,服务熔断和服务降级的主要区别在于:\n- **目标不同**:服务熔断聚焦于防止系统过载,而服务降级则专注于保持可用性即使在出现问题时。\n- **实现方式**:服务熔断通过阻断请求来保护系统,而服务降级则通过提供简化或替代的功能来降低请求负担。\n\n服务熔断和服务降级共同作用,为微服务架构带来了更高的可用性和稳定性,使得整个系统更加健壮,就像一张安全网,保护着用户和业务!',0,0,0,'2024-12-06 12:18:17','2024-12-27 09:44:03'),(300948,100005,200105,'数据库的三大范式就像是数据库世界里的三位“圣贤”,每位都有自己的信条和追随者。听着,走近点,我来给你揭开这三位的神秘面纱!\n\n1. **第一范式(1NF)**:\n 这一位相信“一切都要有序”。他的信条是:表中的每一列必须是原子的,也就是说,不能让一列里面存放多个值。想象一下,如果你的表格里有一列叫“兴趣爱好”,里面居然存了“游泳, 篮球, 旅行”,这就犯了第一范式的大忌!听着,兴趣爱好就是兴趣,干嘛要和别人分享呢?\n\n2. **第二范式(2NF)**:\n 他是从“分离负责”这个角度出发的。第二范式遵循规定:表格中的每一个非主属性都必须完全依赖于主键,而不能只依赖主键的一部分。也就是说,不能有半吊子的依赖关系。看吧,想干点啥就得全力以赴,别半心半意的给我们弄混了。当你有个表格是用来记录学生和课程,你可别让学生的姓名出现在课程信息的那一侧,真是得不偿失!\n\n3. **第三范式(3NF)**:\n 这位宗师的座右铭是“删除冗余”,他崇尚的是全面消灭依赖。就是说,如果有一个非主属性依赖于另一个非主属性,那么这个结构就得改。比如在你的表中,如果有个“学生”表,里面存了姓名和班级,而班级又有班主任的信息,哎呀,这样岂不是让班主任成了一个负担?他建议把班主任的信息单独存一个表,干干净净,明明白白。\n\n总的来说,数据库的三大范式就像是“现代生活的分居原则”——让每样东西各归其位,省得一团乱麻。不过,大家也别迷信,现实中的数据库设计还是得灵活运用,别光会照搬这些“圣贤”的教条,搞得自己像个囚徒一样!',0,0,0,'2024-12-06 12:18:25','2024-12-27 09:44:03'),(300949,100077,200828,'好的,模板特化和模板偏特化在C++中就像是两种不同的魔法,虽然它们都能让你控制模板的行为,但使用的方式有点不一样。让我们来一探究竟!\n\n### 模板特化(Template Specialization)\n想象一下你有个万能的魔法师(模板)可以处理任何事情,但有时候你需要他专门为某个特殊的情况施展神奇的魔法。这个时候,模板特化就来了!\n\n**例子**:\n```cpp\n#include \n\ntemplate \nclass Wizard {\npublic:\n void cast() {\n std::cout << \"Casting a generic spell!\" << std::endl;\n }\n};\n\n// 专门为int类型的特化\ntemplate <>\nclass Wizard {\npublic:\n void cast() {\n std::cout << \"Casting a fireball!\" << std::endl;\n }\n};\n\nint main() {\n Wizard genericWizard;\n genericWizard.cast(); // 输出: Casting a generic spell!\n\n Wizard fireWizard;\n fireWizard.cast(); // 输出: Casting a fireball!\n\n return 0;\n}\n```\n在这个例子中,我们有一个`Wizard`类的模板,对于`int`类型它有特殊的实现,类似于一个战斗法师专门对付火元素!\n\n### 模板偏特化(Template Partial Specialization)\n现在,假设我们的魔法师还有很多稍微不同的小分身,虽然他们都是魔法师,但有一些细微的区别。模板偏特化就是让你在特定情况下对模板进行“稍微”的调整。\n\n**例子**:\n```cpp\n#include \n\ntemplate \nclass Wizard {\npublic:\n void cast() {\n std::cout << \"Casting a generic spell!\" << std::endl;\n }\n};\n\n// 偏特化:当第二个类型是int时\ntemplate \nclass Wizard {\npublic:\n void cast() {\n std::cout << \"Casting an upgrade spell for int!\" << std::endl;\n }\n};\n\nint main() {\n Wizard genericWizard;\n genericWizard.cast(); // 输出: Casting a generic spell!\n\n Wizard specialWizard;\n specialWizard.cast(); // 输出: Casting an upgrade spell for int!\n\n return 0;\n}\n```\n在这里,偏特化的`Wizard`可以看作是我们的魔法师的一个小变种,专门处理当第二个参数是`int`的情况。它并没有完全变身,只是换了个法术而已。\n\n### 总结一下\n- **模板特化**:完全针对某一种类型(或类型组合)给出独特的实现,像是魔法师改变了他的身份。\n- **模板偏特化**:在某些条件下改变部分行为,依然保留了其他的功能,像是魔法师的升级版,但还在保持他的“魔法”本质。\n\n希望这段魔法之旅能够让你对模板特化和偏特化有更清晰的认识!如果还有什么不明白的,欢迎向我提问哦!',0,0,0,'2024-12-06 12:18:38','2024-12-27 09:44:03'),(300950,100031,200518,'你有没有想过,为什么Java能在不同的操作系统上运行而不需要修改代码呢?这正是因为JVM(Java虚拟机)的存在。那么,动态编译在JVM中扮演了什么样的角色呢?\n\n动态编译,也被称为“即时编译”(Just-In-Time Compilation,JIT),它是JVM的一部分,负责将字节码在运行时转换为机器码,从而提高程序的执行效率。那么,你是否知道,这个过程是如何进行的呢?\n\n首先,当Java程序运行时,JVM会将Java字节码加载到内存中。然后,JVM在初次执行某个方法时,会将该方法的字节码编译成机器码,并缓存这个机器码。这听起来是不是很高效?这样,下次再调用这个方法时,JVM就可以直接使用已经编译好的机器码,而无需再次编译,对吧?\n\n还有一点你或许会感兴趣:JVM还会根据运行时的性能数据动态决定是否编译某个方法。这是否意味着,JVM在一定程度上具备“智能”?它会优先编译那些频繁调用的方法,以进一步提高性能。\n\n所以说,你认为动态编译在提升Java程序性能方面有哪些具体的优势呢?这样的优化策略对你学习Java编程有什么帮助吗?',0,0,0,'2024-12-06 12:18:47','2024-12-27 09:44:03'),(300951,100081,200048,'网络分层模型就像一座精密的建筑,每一层都有自己的职责,确保数据能够顺利地从一个地方传输到另一个地方。那么,封装与解封装的过程就像是把一件精妙的艺术品逐层包裹好,再逐层拆解出来的过程。\n\n### 封装过程:\n\n1. **数据生成**:在应用层,用户的请求或数据就像是一封信,编写好了内容。\n2. **传输层**:接下来,信件进入运输层,这里会加上一层“信封”,包含端口信息,确保信件能够准确送达指定的地址。\n3. **网络层**:再往下,网络层增加了地址信息(如IP地址),这就像在信封上写上收件人的居住地址,确保信件能找到正确的目的地。\n4. **链路层**:最后到达链路层,这里的封装会包含物理地址(如MAC地址),低层负责转换数据成适合物理传输的格式,准备发往网络。\n\n这个封装过程中,每一层都将数据打包、加注信息,确保数据能够通过各种网络设备,跨越重重障碍,到达最终目标。\n\n### 解封装过程:\n\n接收方则是完全相反的过程:\n\n1. **链路层**:首先,数据包随着电信号或光信号到达接收方,链路层会拆掉最外面的物理封装。\n2. **网络层**:然后,网络层会查看目标地址,确认这是要发给自己的数据包,并去掉那个与地址相关的封装。\n3. **传输层**:随后,传输层会移除它的信封并读取端口信息,了解如何将数据传递给具体的应用程序。\n4. **应用层**:最后,数据到达应用层,就像一封信终于被打开,信息呈现在用户面前。\n\n### 作用:\n\n这种封装与解封装的机制在网络通信中至关重要:\n\n- **灵活性**:每一层都能独立处理不同类型的数据,加强了网络协议的灵活性与适应性。\n- **模块化**:将复杂的网络通信过程分解为几个简单的模块,易于开发与维护。\n- **标准化**:不同层之间的严格定义与标准化,确保不同设备和网络之间能够协同工作,进行有效通信。\n- **性能优化**:在每一层中,可以独立地进行优化,提升整体网络性能。\n\n所以,网络分层模型的封装与解封装过程就像是一位专业的包装师和接收师,各司其职,让信息在纷繁复杂的网络中畅通无阻,精准到达每位“收件人”手中!',0,0,0,'2024-12-06 12:18:58','2024-12-27 09:44:03'),(300952,100072,200570,'在Spring框架中,事务的管理就像管弦乐团中的指挥,有了它,整个演出才能和谐进行。Spring通过`@Transactional`注解来管理事务的隔离级别和传播行为。这就好比在设定一个约会时,告诉对方你对彼此关系的期待。\n\n### 1. 隔离级别(Isolation Level)\n隔离级别决定了一个事务对其他事务的影响程度。Spring提供了以下几种隔离级别:\n\n- `ISOLATION_DEFAULT`:使用数据库默认的隔离级别。\n- `ISOLATION_READ_UNCOMMITTED`:允许读取未提交的数据。就像你在刷社交媒体时不停地看到朋友的更新,但这些更新可能只是草稿。\n- `ISOLATION_READ_COMMITTED`:只允许读取已经提交的数据。哦,不,你刚刚错过了那条有趣的状态更新,因为它在提交之前就被朋友删掉了。\n- `ISOLATION_REPEATABLE_READ`:在同一事务中多次读取数据时,结果是稳定的,就像你固定在某家餐馆,不管点什么,餐厅都没变。\n- `ISOLATION_SERIALIZABLE`:最严格的隔离级别,事务之间完全不允许交叉。就像我们两个人在同一个房间,绝对不能说话,只能用手势交流。\n\n**示例:**\n```java\n@Transactional(isolation = Isolation.READ_COMMITTED)\npublic void updateAccount(Account account) {\n // 修改账户信息的业务逻辑\n}\n```\n\n### 2. 传播行为(Propagation Behavior)\n传播行为决定了一个事务应该如何嵌套在另一个事务中。Spring提供了以下几种传播行为:\n\n- `PROPAGATION_REQUIRED`:支持当前事务,如果没有事务则新建一个。就像你在一个派对上,如果有人邀请你一起喝酒,你就会带着饮料,若没人,你也能独自享受。\n- `PROPAGATION_REQUIRES_NEW`:总是新建一个事务,原事务挂起。像是你在派对上走出去打个电话,带着自己的饮料。\n- `PROPAGATION_NESTED`:如果有事务存在,则嵌套在其中。就如你在派对上,临时需要一个小组讨论,但不会打断大局。\n \n**示例:**\n```java\n@Transactional(propagation = Propagation.REQUIRES_NEW)\npublic void createAndNotify(Account account) {\n // 创建账户逻辑\n // 同时如果需要,发送通知\n}\n```\n\n### 总结\n通过`@Transactional`注解,Spring让你可以轻松地管理事务的隔离级别和传播行为,就像你安排一场盛大的派对一样。只需要清楚自己对社交的需求,系统就会为你安排得妥妥帖帖!当然,事务管理有时候可能像一场喜剧,但只要掌握得当,绝对能把你的应用程序带入一个新的高度!',0,0,0,'2024-12-06 12:19:09','2024-12-27 09:44:03'),(300953,100043,200079,'请求分页存储管理是一种内存管理技术,它主要用于提高计算机系统在进行程序执行时的内存利用率。这个技术就像一本大书的目录,它帮我们找到需要的页码,而不必一次性翻动整本书。\n\n### 工作原理\n\n1. **分页概念**:\n 在请求分页的世界里,内存被分割成均匀大小的“页”(通常为4KB),而程序也被划分为相同大小的“页”。这样,程序的每一部分只占用一段连续的内存空间,便于管理。\n\n2. **虚拟内存**:\n 每个程序看到的内存空间并不等同于物理内存空间,系统使用虚拟内存来给程序提供更大的“视野”。通过这种方式,程序可以像是拥有更多的内存,而实际上是根据需要来动态地调入和调出物理内存。\n\n3. **请求分页**:\n 在这个机制下,程序并不会一次性加载全部页,而是根据需要“请求”这些数据。当程序需要访问某一页,但它不在物理内存中时,操作系统会发出一个“缺页中断”。这就如同你想要查找书中的某一章,但这本书只能在图书馆借阅,你需要向图书馆请求这章的复印件。\n\n4. **页面调度**:\n 一旦发生缺页中断,操作系统就会进行页面调度,将所需的页从磁盘(或其他存储设备)调入内存。如果内存已满,操作系统会选择一个不常用的页面进行换出(就像你在书架上腾出空间,为了新书而移动老书)。\n\n5. **页面表**:\n 每个程序会有一个“页面表”,用来记录虚拟页与物理页之间的映射关系。操作系统通过这个表来快速找到对应的物理地址。\n\n### 总结\n\n请求分页存储管理就像是一位高效的图书馆管理员,它通过巧妙的策略,使得内存资源能够高效、灵活地分配,最大限度地提高计算机的响应速度和运行效率。它让程序能够在有限的物理内存中,像阅读一整本书一样,随意地翻阅和处理数据,而不必一次性消耗所有的资源。',0,0,0,'2024-12-06 12:19:18','2024-12-27 09:44:03'),(300954,100096,200001,'从输入URL到页面展示的过程,是互联网世界中一场复杂而精妙的舞蹈,涉及多个步骤、协议和技术的协作。让我们细致地拆解这一过程,领悟其背后的哲理。\n\n1. **输入URL**:当你在浏览器地址栏输入URL时,实际上是在发起一段探索之旅。URL是资源定位器,它不仅指向某个网页,还包含了协议(如HTTP/HTTPS)、主机名、路径等信息。\n\n2. **DNS解析**:接下来,计算机需要将域名(如www.example.com)转换为IP地址。这个过程叫做DNS(域名系统)解析。它仿佛在帮助我们找到一个人的住址,只有知道了IP地址,计算机才知道如何去“拜访”目标服务器。\n\n3. **建立连接**:一旦得到了IP地址,浏览器会通过TCP(传输控制协议)与目标服务器建立连接。这一过程和一场握手仪式相似,保证双方能够可靠地交换信息。\n\n4. **发送请求**:连接建立后,浏览器发送HTTP请求,询问服务器是否可以提供所请求的资源。这个请求中包含了多种信息,例如请求方法(GET、POST等),请求头,甚至有时还有请求体。就如同我们向対方询问某个问题,倾注了我们的期望和背景。\n\n5. **服务器处理请求**:服务器接收到请求后,会解析、处理并决定返回的内容。这个过程可能涉及数据库查询、文件读取或者调用其它服务。此时,服务器如同一个知识渊博的老师,回应着学生的求知渴望。\n\n6. **返回响应**:处理完成后,服务器将结果打包成HTTP响应,并通过先前建立的连接将其发送回浏览器。响应中包含状态码(如200表示成功、404表示未找到等)、响应头以及实际内容(如HTML、CSS、JavaScript等),如同老师在报告中提供的详细反馈,给学生指明了方向。\n\n7. **浏览器渲染**:浏览器接收到响应后,会开始渲染页面。这是一个多层次的过程,包括解析HTML、构建DOM树、解析CSS、计算样式、渲染层、绘制图像等。此时的浏览器如同一位艺术家,在画布上描绘出一幅美丽的画面。\n\n8. **加载资源**:除了主要的HTML文档外,浏览器还会发现并加载其他资源,如图片、样式表和脚本。这一过程可能会涉及多个请求和响应,如同音乐会上的乐器齐奏,合成出和谐的版本。\n\n9. **交互与更新**:最终,页面展示在用户面前,用户可以与之互动。页面可能根据用户的操作通过AJAX等技术进行动态更新,持续响应着用户需求,如同一场舞蹈,不断调整和适应。\n\n从输入URL到页面展示的每一步,都是人与技术之间深度合作的结果,蕴含着无数工程师的不懈努力与智慧。每个细微的环节都展示了技术的脆弱与强大,人与机器的合作与适应,使得我们得以触达信息的海洋。这一过程也提醒我们,在这个连通的时代,每一小步都可能造就巨大的变化。',0,0,0,'2024-12-06 12:19:34','2024-12-27 09:44:03'),(300955,100069,200736,'哦,Java中的JIT(Just-In-Time)编译器就像一个在你家厨房不停地翻炒的厨师,专注于那些你经常要求的那几道“热菜”。它的工作原理简单又复杂,就像你为啥总爱吃那个快递小哥送来的披萨一样。下面简单聊聊。\n\n1. **识别热点代码**:JIT编译器会通过运行时分析来找出哪些方法被频繁调用。就像你家那个总来借钱的朋友,见到他就知道又要被问了。通常,JIT会通过计数器来跟踪方法调用的频率,一旦计数器超过某个阈值,JIT就开始行动,开始编译这段热点代码。\n\n2. **编译成本地代码**:一旦某个方法被标记为“热”,JIT就会把它从字节码翻译成本地机器代码。这样就能提高执行效率,不用每次都去做那些复杂的翻译工作了。感觉就像是把外卖菜单转换成你老家的家常菜,直接上桌,省时省力。\n\n3. **优化**:在编译过程中,JIT会利用各种优化技术,例如内联扩展、循环展开等,尽量让代码变得更高效。就像你做饭时找出更快捷的方法,把烹饪时间压缩到最短,让你能多花点时间追剧。\n\n4. **自适应**:最重要的是,JIT还是个聪明的家伙,它会根据程序运行的实际情况动态调整优化策略。如果某段代码的执行频率突然下降,JIT可以选择重编译,省得浪费资源去优化那些不再热门的代码。就像你突然发现那道披萨没那么好吃,果断换成了沙拉。\n\n总而言之,JIT编译器就像一个既懒又聪明的厨师,总是聚焦那些最受欢迎的菜品,努力让你的代码快得飞起,同时保持高效,让程序员尽量少折腾,尽享生活!',0,0,0,'2024-12-06 12:19:45','2024-12-27 09:44:03'),(300956,100024,201137,'TCP的keepalive机制就像是在网络拥挤时,你的好友发来的“嘿,你还活着吗?”的短信。这个机制的目标是确保在两个设备之间的连接仍然是有效的,避免因为长时间没有数据传输而导致的连接意外中断。\n\n### TCP Keepalive的工作原理:\n1. **定期探测**:当TCP连接在一定时间内没有任何数据传输时(例如,你的猫在键盘上打了个盹,而你正试图看看猫是否会有奇妙的舞蹈),TCP会开始发送小的keepalive探测包。这些探测包非常小,几乎不会增加网络负担,目的就是确认对方还在不在。\n \n2. **重试机制**:如果对方没有回应,TCP会在设定的间隔内多次重试,直到达到最大重试次数。如果还是没有回应,TCP就会认为连接已经失效,接着会宣告“游戏结束”,并关闭连接。\n\n### 在网络编程中的作用:\n1. **防止连接超时**:很多服务器为了节省资源,设置了连接超时。如果你长时间没什么动作,服务器可能就会把你踢出局。Keepalive可以让你及时发出“我还在”的信号,防止被踢。\n\n2. **识别死连接**:在一些故障情况下,如网络单侧断开,Keepalive可以帮助及时发现死连接。这样你的程序可以优雅地处理连接的重新建立,而不是在用户提交表单后,再看到“连接丢失”的提示。\n\n3. **资源管理**:通过及时发现无效连接,可以更有效地管理资源,释放不再使用的连接,避免无端占用内存和带宽。\n\n总之,TCP的Keepalive机制就像是那种定期检查你还好好的朋友,让你的网络连接不会因为“短时间没事做”而被误解为“已经离开了”!只不过这个朋友不太好控制,可能有些时候会给你带来些烦恼,比如说:“都快出门了,你还不回复?”',0,0,0,'2024-12-06 12:19:54','2024-12-27 09:44:03'),(300957,100069,200798,'虚析构函数是C++中的一种特殊成员函数,用于确保在使用基类指针或引用删除派生类对象时,能够正确调用派生类的析构函数。在继承体系中,基类的析构函数被声明为虚函数,意味着当通过基类指针删除对象时,正确的派生类析构函数将被调用。这一特性对于资源管理和内存安全至关重要。\n\n### 为什么需要虚析构函数?\n\n1. **资源管理**:在面向对象编程中,资源管理是设计的重要方面。派生类往往会分配特定于自身的资源,如动态内存、文件句柄等。如果不使用虚析构函数,基类指针删除派生类对象时,只会调用基类的析构函数。这可能导致派生类中分配的资源未能被释放,从而造成内存泄漏或资源错乱。\n\n2. **多态性**:虚析构函数是实现多态性的一部分。当基类指针指向派生类对象时,虚析构函数允许通过基类指针安全地释放派生类对象。对于设计良好的类层次结构,这种行为是必要的,以消除内存管理中的隐患。\n\n3. **程序的健壮性**:使用虚析构函数使得程序在处理复杂情况下(如动态多态对象的生命周期管理)时更为健壮。无论是直接构造的对象还是通过基类指针处理的派生类对象,合适的析构程序总会被调用,以保证对象的正确释放。\n\n### 如何声明虚析构函数?\n\n在C++中,可以通过在基类中将析构函数声明为虚拟来实现。例如:\n\n```cpp\nclass Base {\npublic:\n virtual ~Base() { }\n};\n\nclass Derived : public Base {\npublic:\n ~Derived() { /* 针对Derived的资源清理 */}\n};\n```\n\n在这个例子中,如果通过基类指针删除一个Derived对象,虚析构函数的机制会确保Derived的析构函数被调用,从而正确释放所有资源。\n\n### 总结\n\n虚析构函数是为了保证在对象的生命周期结束时,所有必要的清理工作都得以顺利完成。它象征着对合成与析构过程的深刻理解,是构建健壮和可维护程序的基石之一。在面向对象的设计中,理解这一点尤为重要,因为资源管理往往决定了程序的稳定性与性能。',0,0,0,'2024-12-06 12:20:01','2024-12-27 09:44:03'),(300958,100069,200855,'哎呀,vector可不是一位小小的数学角色,它实在是个多才多艺的家伙!在计算机科学、物理、工程等领域,它简直是个“万金油”。以下是一些常见的应用场景,让我们来看看它在哪里崭露头角吧:\n\n1. **计算机图形学**:在游戏和动画制作中,vector被用来表示位置、速度和方向。没了它,角色可就不知道该往哪儿跑啦!\n\n2. **机器学习**:在这领域,数据点通常用vector表示,每个维度可以代表不同的特征。要是没有vector,机器学习算法可能会“迷路”。\n\n3. **物理模拟**:运动物体的速度和加速度常常用vector来描述。想象一下,如果牛顿没和vector打交道,他的三大定律可能就变成“两小定律”了。\n\n4. **信号处理**:在数字信号处理中,audio waves(音频波)和图像常常用vector来进行分析和处理。没有vector,音乐可能就变得“无声无息”了。\n\n5. **搜索引擎**:在信息检索中,文本和查询通常被表示为vector,方便计算它们之间的相似度。可以说,vector帮助我们找到了“最合心意”的搜索结果。\n\n6. **控制系统**:在航空航天或机器人学中,vector被用来控制物体的运动。在这方面,vector可谓是“飞行员”和“操控手”的好帮手。\n\n7. **游戏开发**:vector用来表示角色的移动和碰撞。想象一下,如果没有vector,游戏角色可能就会“原地踏步”。\n\n所以说,vector在生活中的应用可真是无处不在,正如咖啡不可或缺于程序员的生存一样!',0,0,0,'2024-12-06 12:20:07','2024-12-27 09:44:03'),(300959,100095,200253,'哦,ArrayList在多线程环境下简直就是个“炸弹”的代名词!你一不小心就可能让你的程序错误百出,简直比追剧还刺激。\n\n1. **数据不一致**:想象一下,你在一边往ArrayList里加元素,另一边又在抽走元素,最后你的ArrayList变成了一个什么?一个精锐的侦探,都不知道自己里面有什么了!数据可能出现混乱。\n\n2. **ConcurrentModificationException**:这个就像是浪漫爱情里的背叛,你在遍历ArrayList的时候,结果被另一个线程捅了一刀,突然跳出来个异常,真是让人措手不及。\n\n3. **线程安全问题**:ArrayList本身没啥保护措施,就像马路上的行人毫无防备,根本没办法应对突然跑出来的车(别问我怎么知道的)。多个线程同时修改一个ArrayList就像是几个人争抢一个蛋糕,最后挥舞的可能就是一个“空盘子”。\n\n4. **性能下降**:加锁、解锁?你咋不说加速呢?目的就是为了避免问题,结果反而让你的程序像过了个冬眠一样,慢得让人心痛。\n\n所以说,如果你真的打算在多线程环境下操作集合,建议用个线程安全的家伙,比如`CopyOnWriteArrayList`或者`Collections.synchronizedList()`,不然的话,嘿嘿,做好心理准备,迎接各种“精彩”的异常吧!',0,0,0,'2024-12-06 12:20:15','2024-12-27 09:44:03'),(300960,100056,200742,'优化Java的JVM(Java虚拟机)垃圾回收(GC)策略,以减少GC停顿时间,是一个重要而有效的手段。这不仅可以提升应用程序的响应速度,同时也能提升用户体验。以下是一些温和的建议,可以帮助你实现这一目标:\n\n1. **选择合适的GC算法**:\n - **G1垃圾回收器**:对于大多数应用,G1(Garbage-First)是一个很好的选择。它专为低延迟应用设计,能够平衡吞吐量和停顿时间。\n - **ZGC和Shenandoah**:这些是低延迟的垃圾回收器,适合对停顿时间敏感的场景,可以考虑使用它们。\n\n2. **合理设置堆内存大小**:\n - 通过调整堆的初始大小和最大大小(`-Xms`和`-Xmx`参数),使得内存使用更稳定,减少频繁的GC触发。\n\n3. **调整年轻代和老年代的比例**:\n - 使用`-XX:NewRatio`或`-XX:SurvivorRatio`来调整内存的分配比例,以适应你的应用负载特性。\n\n4. **使用停顿时间目标**:\n - 对于G1,可以设置停顿时间目标,使用参数`-XX:MaxGCPauseMillis=<时间>`来要求GC的最大停顿时间。\n\n5. **监控并分析GC日志**:\n - 启用GC日志记录(`-Xlog:gc*`),并分析GC日志以了解内存使用情况和GC的行为。这有助于识别问题和优化点。\n\n6. **应用内存管理和优化**:\n - 尽量减少对象的创建,尤其是短命对象。使用对象池或者复用对象,可以避免频繁的GC。\n - 使用合适的数据结构,避免大型对象的频繁创建与销毁。\n\n7. **版本更新**:\n - 确保使用最新的JVM版本,因为新版本通常会有更好的GC性能和新的GC算法。\n\n8. **正确配置线程堆栈大小**:\n - 通过调整线程堆栈的大小(`-Xss`参数),可以优化内存的使用和减少GC的压力。\n\n记住,每个应用程序的需求和特性可能不同,因此,需要进行一些实验来找到最适合你应用的配置。如果在优化过程中遇到困难,不妨从小处着手逐渐进行调整,找到最佳的解决方案也是一个积累经验的过程。你已经在这条探索之路上迈出了第一步,相信你一定会取得很好的成果!加油!',0,0,0,'2024-12-06 12:20:30','2024-12-27 09:44:03'),(300961,100048,200201,'哦,Java中的异常处理机制就像是一场戏剧,时不时会上演一些惊心动魄的意外场景。让我们来看看这出戏的几个主角:`try`、`catch`、和 `finally`。\n\n### 1. **try 块:**\n这是故事的开端,所有的危险和不确定性都在这里发生。你把可能会抛出异常的代码放在`try`块中。就像你在厨房里炒菜,明知道有可能会烧糊,但还是兴致勃勃地开始了。\n\n```java\ntry {\n // 可能会抛出异常的代码\n int result = 10 / 0; // 这里会抛出一个除以零的异常\n}\n```\n\n### 2. **catch 块:**\n接下来出现的是`catch`块,它像是故事中的英雄,准备好应对任何意外情况。如果在`try`块中发生了异常,程序会瞬间切换到`catch`块,处理这个“坏事情”。我们通常会在这里记录错误、告知用户,甚至设法挽救局面。\n\n```java\ncatch (ArithmeticException e) {\n // 处理异常的代码\n System.out.println(\"哎呀,发生了一个数学错误!别担心,我会处理它的。\");\n}\n```\n\n### 3. **finally 块:**\n即使万一`try`块和`catch`块都没有问题,`finally`块也会在最后上场,保证一些必要的清理工作得以完成。想象一下,这是戏剧的结束,演员们在谢幕的时候绝对不会忘记打扫舞台。\n\n```java\nfinally {\n // 清理资源,无论如何都会被执行\n System.out.println(\"总是要做一些善后工作的。\");\n}\n```\n\n### 整体流程:\n1. **进入`try`块**,开始冒险。\n2. 如果一切顺利,`catch`块会被跳过,直接进入`finally`块。\n3. 如果在`try`中发生了异常,程序会中断`try`块,跳到相应的`catch`块,处理完异常后,再进入`finally`块。\n4. 不管有没有异常发生,`finally`块里的代码始终会执行,就像一个不愿意离场的演员。\n\n### 小结:\nJava中的异常处理机制让你的代码在面对不确定性时变得更加优雅。它让你得以捕捉错误,就像超级英雄在拯救世界一样,确保万一发生问题时你还能体面地收场!所以,敢于在代码中加入`try-catch-finally`,让你的程序更坚韧不拔、笑对人生的各种挑战吧!',0,0,0,'2024-12-06 12:20:47','2024-12-27 09:44:03'),(300962,100034,201138,'你有没有考虑过使用非阻塞I/O和事件驱动的模型来处理大量并发连接?比如说,选择使用`select`、`poll`或`epoll`等系统调用,这样可以在多个连接上同时监听事件,而不是为每个连接创建一个线程或进程。\n\n另外,你觉得使用线程池或异步编程技术来管理连接会有什么好处吗?通过这种方式,可以减少上下文切换带来的开销,从而更高效地利用系统资源。\n\n此外,对于资源的管理,是否有想过有效使用缓存和连接池的策略?这样不仅能控制资源的使用,还能提高系统的响应速度。\n\n你认为这些方法中的哪一种最适合你的具体需求?或者有没有其他的方案你想探讨的?',0,0,0,'2024-12-06 12:20:51','2024-12-27 09:44:03'),(300963,100051,200060,'你有考虑过为什么需要页面置换算法吗?其实,页面置换算法的目的是为了提高内存的使用效率。当内存满了,需要将一些不常用的页面换出,这时就需要选择合适的算法。你是否熟悉以下这几种常见的页面置换算法呢?\n\n1. **最少使用(Least Recently Used, LRU)**:这个算法会选择最久没有被使用的页面进行置换。你觉得这样可以有效减少页面错误吗?\n\n2. **先进先出(First-In, First-Out, FIFO)**:这个算法简单地按照页面进入内存的顺序进行替换,你认为这样能满足所有应用的需求吗?\n\n3. **最佳置换(Optimal Page Replacement)**:它在理论上是最优的,永远会选择将来最长时间不被使用的页面进行替换。你认为什么样的情况最能体现它的优越性呢?\n\n4. **时钟(Clock)**:一个类似于FIFO但又更加灵活的算法,它使用一个环形队列来管理页面。你觉得这种方法有什么实际应用的优势吗?\n\n5. **最近最少使用(Least Frequently Used, LFU)**:这个算法会选择使用频率最低的页面置换。你认为在某些特定场景下,这种方法会更有效吗?\n\n你觉得哪种页面置换算法更适合某种特定的应用场景呢?',0,0,0,'2024-12-06 12:20:58','2024-12-27 09:44:03'),(300964,100037,200871,'你没有想过,为什么在STL(标准模板库)中,内存分配会被单独抽象成一个allocator吗?其实,allocator的主要角色是提供内存管理和对象构造的功能,这样做的目的是什么呢?是不是为了让STL的容器能够灵活地适应不同的内存管理需求呢?\n\n通过使用allocator,容器可以在需要时更换内存分配策略,这样一来,开发者是不是能更好地控制内存使用和性能呢?另外,allocate和deallocate方法的存在,是否让我们在使用自定义的数据结构时,能更加方便地管理内存?那么,这种设计是不是提高了代码的复用性和移植性呢?',0,0,0,'2024-12-06 12:21:02','2024-12-27 09:44:03'),(300965,100078,200759,'在C++中,`sizeof`操作符是一个非常有用的工具,它可以用来获取一个变量或类型在内存中占用的字节数。这一功能不仅帮助我们理解数据的存储,更让我们能够更好地管理内存。\n\n### 使用 `sizeof` 操作符\n\n1. **获取变量的大小**:你可以对一个已定义的变量使用 `sizeof`,这会返回该变量类型的大小。例如:\n\n ```cpp\n int a = 10;\n std::cout << \"Size of a: \" << sizeof(a) << \" bytes\" << std::endl;\n ```\n\n2. **获取数据类型的大小**:可以直接使用 `sizeof` 对类型进行操作,例如:\n\n ```cpp\n std::cout << \"Size of int: \" << sizeof(int) << \" bytes\" << std::endl;\n std::cout << \"Size of double: \" << sizeof(double) << \" bytes\" << std::endl;\n ```\n\n3. **获取数组的大小**:如果你想知道一个数组的总大小,可以这样做:\n\n ```cpp\n int arr[10];\n std::cout << \"Size of arr: \" << sizeof(arr) << \" bytes\" << std::endl;\n std::cout << \"Number of elements in arr: \" << sizeof(arr) / sizeof(arr[0]) << std::endl;\n ```\n\n4. **获取结构体的大小**:如果你定义了一个结构体,可以使用 `sizeof` 来获取它的大小:\n\n ```cpp\n struct MyStruct {\n int x;\n double y;\n };\n\n std::cout << \"Size of MyStruct: \" << sizeof(MyStruct) << \" bytes\" << std::endl;\n ```\n\n5. **获取指针的大小**:注意,指针的大小和它指向的类型无关,而是与系统架构有关(例如,在32位系统上通常为4字节,在64位系统上为8字节):\n\n ```cpp\n int *ptr;\n std::cout << \"Size of pointer: \" << sizeof(ptr) << \" bytes\" << std::endl;\n ```\n\n### 小结\n\n通过 `sizeof` 操作符,我们不仅能取得数据的原始大小,还能更深入地理解在计算机中数据是如何存储和管理的。这种认识,对于程序的优化和内存管理至关重要。在编程的过程中,思考数据大小和内存使用,有助于我们开发出更加高效的软件。既然大小与效率息息相关,探索这个概念就如同在探索一个更深的真理——小的决定,往往能促成大的影响。',0,0,0,'2024-12-06 12:21:10','2024-12-27 09:44:03'),(300966,100066,201094,'在Qt应用程序中集成第三方库其实是一个很有趣的过程,您可以按照以下几个步骤来完成这一任务。以下是一些建议,希望对您有帮助:\n\n1. **选择合适的第三方库**:首先,要确保您选择的库是适合您的项目需求的,并且与您的Qt项目兼容。\n\n2. **安装库**:\n - 如果是通过包管理器(如vcpkg、Conan等),请按照它们的文档来安装库。\n - 如果是源码库,您可以从其官方网站下载源代码,并按照该库的安装说明进行编译和安装。\n\n3. **包含库文件**:在Qt项目文件(.pro)中,需要添加库的路径和链接指令。例如:\n ```plaintext\n INCLUDEPATH += path/to/your/library/include\n LIBS += -Lpath/to/your/library/lib -lmylibrary\n ```\n\n4. **确保Qt的构建系统能够找到库**:如果你使用CMake作为构建系统,您需要在CMakeLists.txt中添加:\n ```cmake\n include_directories(\"path/to/your/library/include\")\n target_link_libraries(your_target_name \"mylibrary\")\n ```\n\n5. **编写代码**:在您的源代码中包含需要的头文件并使用库提供的功能。例如:\n ```cpp\n #include \"mylibrary.h\"\n ```\n\n6. **构建项目**:在Qt Creator中,单击“构建”按钮,查看是否有任何编译错误。如果有,请仔细检查路径和链接指令是否正确。\n\n7. **调试和测试**:完成集成后,您可以运行应用程序并进行必要的调试,确保一切正常工作。\n\n祝您顺利完成集成!每一步都是学习的机会,遇到困难也不要气馁,尝试逐步解决,相信您一定能够成功的!如果您在具体步骤中遇到问题,请随时询问,大家都会很乐意提供帮助!',0,0,0,'2024-12-06 12:21:19','2024-12-27 09:44:03'),(300967,100071,200814,'局部静态变量,听起来就像是一个在小区内静静生活的老者,虽然住在居民楼里,但从不与其他邻居互动。简单来说,局部静态变量是在函数内声明为静态的变量。\n\n**生命周期**:局部静态变量的生命周期是从第一次调用函数开始,直到程序结束。换句话说,它们比花园里的植物还长命,虽然只在“花园”内部生长。\n\n**作用域**:局部静态变量的作用域就是函数内部。就像你偷吃的零食只许在你的小房间里被发现,任何其他地方都无法检测到。所以,即使这个变量在函数结束后依旧存在,也不能在其他地方使用,不能随便拿出来给朋友们分享。\n\n总结来说,局部静态变量就像是函数的小秘密,它们活得长久,却始终在自己的小世界里,没人能打扰到它们!',0,0,0,'2024-12-06 12:21:22','2024-12-27 09:44:03'),(300968,100043,200299,'在Java中,I/O流和NIO通道是两种处理输入和输出的主要机制,它们各有特点,适用于不同的场景。让我们用生动形象的比喻来阐述它们的区别。\n\n### I/O流:传统的邮递员\n可以把I/O流想象成一个传统的邮递员。每次你需要发送或接收数据的时候,邮递员(流)会从一个地方走到另一个地方,逐个邮件(字节)地进行传递:\n\n1. **线性处理**:数据在I/O流中是一次性地按序列处理的,就像邮递员每次只能送一个信件。\n2. **阻塞模式**:拿到邮件后,邮递员必须等待确认再继续送下一封,整个过程是阻塞的,这就意味着如果等待某封信(比如从硬盘读取数据),其他操作会被拖延。\n3. **简单易用**:I/O流的接口设计简单、易于使用,适合快速开发一些简单的读写操作。\n\n在这种场景下,如果有很多邮件需要处理,邮递员可能就需要频繁往返,效率会受到影响。\n\n### NIO通道:高速公路上的快递车\n而NIO通道则像是一辆在高速公路上疾驰的快递车。它使用“非阻塞”和“选择器”机制来实现更高效的数据处理,能够多线程、高效、快速地处理大量邮件。\n\n1. **非阻塞模式**:快递车不需要每次都停下,能够在行驶过程中同时处理多个包裹。这意味着即使一个包裹在处理,也不会阻塞其他操作,可以同时进行其他的事情。\n2. **缓冲区**:NIO使用缓冲区来存储数据,这样就像把邮件放在后备箱里,可以一次性装载多封信件,大大提高了传输效率。\n3. **选择器**:NIO的选择器就像是交通指挥灯,可以精确地控制快递车何时加速、何时减速,避免延误,允许单线程同时处理多个通道,极大地提高了吞吐量。\n\n### 总结\n所以,当我们面对简单的文件读写或小数据量时,I/O流就像是个可靠的邮递员,简单而有效。但当我们需要处理大量的并发数据、保证高效的性能时,NIO通道就像是那辆飞驰的快递车,迅速而灵活。因此,在设计系统时,选择哪种机制就像是选择交通工具,应根据需要而定。',0,0,0,'2024-12-06 12:21:33','2024-12-27 09:44:03'),(300969,100115,200440,'哦,Java中的锁消除和锁粗化,听起来好像是某种高大上的舞蹈术语,但实际上它们是Java虚拟机(JVM)用来优化多线程程序性能的一些小伎俩,别看名称复杂,实际上就是为了让你的代码跑得更快、更流畅。\n\n### 锁消除(Lock Elimination)\n\n首先说锁消除,这个概念就像是你在超市排队时,发现前面的人最终竟然买了一瓶水,然后自己却打了个哈欠,直接走掉了!在编程的世界里,锁消除发生在JVM检测到一个锁实际上不需要存在的情况下。\n\n具体来说,当JVM发现某个对象的锁仅在一个线程中被使用,并且在其生命周期内,其他线程永远不会访问这个锁的时候,它就会把这个锁给“悄悄地”消除掉。这样一来,线程在执行的时候就无需浪费时间去获取和释放锁,整体性能就提升了,简直就是给你的代码提升了一个“隐形外挂”!\n\n### 锁粗化(Lock Coarsening)\n\n接下来是锁粗化,这就好比是把两个小型的舞蹈动作合并成一个大型的集体舞,节省了许多调度的时间。在Java中,锁粗化是指把多次加锁和解锁操作合并成一个大的锁。这种做法是为了减少频繁的锁操作带来的开销,简而言之,就是提升了性能。\n\n比如,你的代码中连续多次对同一个对象加锁,其实每次都加锁解锁感觉就有点冗余,但JVM会“聪明”地帮你识别出这种情况,然后把这些操作合并成一次大的锁定。这样一来,线程竞争的情况就减少了,就好像你和朋友们一起吃饭,不用每个人都单独去厨房拿东西,大家抱团一起去,效率蹭蹭上涨!\n\n### 总结\n\n所以,锁消除和锁粗化就像是Java应用中的那些小心机,默默提升代码性能,让你在多线程编程这条路上走得更顺畅。好吧,虽然有时候你可能还会因为锁竞争而头疼,但起码在某些情况下,你的钱包(CPU)就不会被无情地掏空!',0,0,0,'2024-12-06 12:21:42','2024-12-27 09:44:03'),(300970,100033,201108,'编写一个能够同时处理多个客户端连接请求的C++服务器,就像是在操控一场热烈的交响乐,让每个乐器(客户端)都能和谐地演奏而不相互干扰。我们可以借助多线程的力量来实现这一目标。下面是一个简单的C++服务器示例,使用标准库中的``和`sockets`库(在Linux上使用``和``),来实现并发处理多个客户端的连接。\n\n### 示例代码\n\n```cpp\n#include \n#include \n#include \n#include \n#include \n#include \n\n#define PORT 8080 // 服务器端口\n#define MAX_CLIENTS 10 // 最大客户端数量\n\n// 处理客户端请求的函数\nvoid handleClient(int clientSocket) {\n char buffer[1024] = {0};\n while (true) {\n int bytesRead = read(clientSocket, buffer, sizeof(buffer));\n if (bytesRead <= 0) {\n std::cout << \"Client disconnected.\" << std::endl;\n break; // 客户端断开连接\n }\n std::cout << \"Received: \" << buffer << std::endl;\n // 发送回消息\n send(clientSocket, buffer, bytesRead, 0);\n memset(buffer, 0, sizeof(buffer)); // 清空buffer\n }\n close(clientSocket);\n}\n\nint main() {\n int serverSocket, clientSocket;\n struct sockaddr_in serverAddr, clientAddr;\n socklen_t addrLen = sizeof(clientAddr);\n\n // 创建socket\n serverSocket = socket(AF_INET, SOCK_STREAM, 0);\n if (serverSocket == -1) {\n std::cerr << \"Socket creation error!\" << std::endl;\n return -1;\n }\n\n // 设置地址结构\n serverAddr.sin_family = AF_INET;\n serverAddr.sin_addr.s_addr = INADDR_ANY;\n serverAddr.sin_port = htons(PORT);\n\n // 绑定socket\n if (bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {\n std::cerr << \"Bind failed!\" << std::endl;\n return -1;\n }\n\n // 开始监听\n if (listen(serverSocket, MAX_CLIENTS) < 0) {\n std::cerr << \"Listen failed!\" << std::endl;\n return -1;\n }\n\n std::cout << \"Server is listening on port \" << PORT << std::endl;\n\n // 主循环,接受连接\n while (true) {\n clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddr, &addrLen);\n if (clientSocket < 0) {\n std::cerr << \"Accept failed!\" << std::endl;\n continue;\n }\n std::cout << \"New connection accepted!\" << std::endl;\n\n // 为每个新连接创建一个线程处理\n std::thread clientThread(handleClient, clientSocket);\n clientThread.detach(); // 使用detach让线程在后台运行\n }\n\n close(serverSocket);\n return 0;\n}\n```\n\n### 代码解析\n\n1. **创建Socket**: 使用`socket()`函数创建一个TCP类型的socket。\n2. **绑定端口**: 使用`bind()`将socket绑定到指定的IP地址和端口。\n3. **监听连接**: 使用`listen()`开始监听客户端的连接请求,设置最大连接数。\n4. **接受连接**: 当有客户端请求连接时,使用`accept()`接受连接,并获取客户端socket。\n5. **多线程处理**: 使用`std::thread`创建一个新线程来处理每个连接。每个线程都会执行`handleClient`函数,处理数据的收发与客户端的交互。\n6. **断开连接**: 客户端断开连接时,相应的线程会终止,并关闭对应的socket。\n\n### 注意事项\n\n- 线程的使用会带来一定的开销,适合连接数较多的服务器场景。\n- 在实现更复杂的功能时,可以考虑使用线程池或更高级的I/O multiplexing(如`select()`或`epoll()`)。\n- 这段代码在Linux环境下测试通过,Windows系统需要作相应调整(特别是socket与线程库的部分)。\n\n通过这种方式,服务器就能像一位完美的指挥,轻松管理多位乐手,各自演绎出动人的乐章!',0,0,0,'2024-12-06 12:22:02','2024-12-27 09:44:03'),(300971,100039,201067,'自定义Qt信号和槽是帮助我们实现对象之间通信的一种非常有效的方法。以下是步骤和示例,帮助你轻松掌握这一概念。\n\n### 步骤:\n\n1. **包含必要的头文件**:\n 确保你的类包含Qt核心头文件,例如:\n ```cpp\n #include \n ```\n\n2. **定义信号**:\n 在你的类中,使用`signals`关键字定义信号。信号可以带有参数。\n ```cpp\n class MyObject : public QObject {\n Q_OBJECT\n public:\n explicit MyObject(QObject *parent = nullptr);\n\n signals:\n void mySignal(int value);\n };\n ```\n\n3. **定义槽**:\n 使用`public slots`或`private slots`关键字定义槽。槽可以用于接收信号。\n ```cpp\n public slots:\n void mySlot(int value);\n ```\n\n4. **发射信号**:\n 可以在类的某个方法中发射信号:\n ```cpp\n void MyObject::doSomething() {\n emit mySignal(42); // 发射信号,传递一个整数值\n }\n ```\n\n5. **连接信号和槽**:\n 在你的其他对象(例如主窗口或其他QObject派生类)中,利用`QObject::connect()`连接信号和槽。\n ```cpp\n MyObject *obj = new MyObject;\n connect(obj, &MyObject::mySignal, this, &MyWindow::mySlot);\n ```\n\n6. **实现槽函数**:\n 实现你之前定义的槽函数,以处理信号:\n ```cpp\n void MyWindow::mySlot(int value) {\n qDebug() << \"Received signal with value:\" << value;\n }\n ```\n\n### 示例代码:\n\n```cpp\n#include \n#include \n#include \n\nclass MyObject : public QObject {\n Q_OBJECT\npublic:\n void doSomething() {\n emit mySignal(42); // 发射信号\n }\n\nsignals:\n void mySignal(int value); // 定义信号\n};\n\nclass MyWindow : public QObject {\n Q_OBJECT\npublic slots:\n void mySlot(int value) {\n qDebug() << \"Received signal with value:\" << value; // 处理信号\n }\n};\n\nint main(int argc, char *argv[]) {\n QCoreApplication a(argc, argv);\n\n MyObject *obj = new MyObject;\n MyWindow *window = new MyWindow;\n\n // 连接信号和槽\n QObject::connect(obj, &MyObject::mySignal, window, &MyWindow::mySlot);\n \n // 调用发射信号的方法\n obj->doSomething();\n\n return a.exec();\n}\n\n#include \"main.moc\"\n```\n\n### 注意事项:\n- 确保你的类使用了`Q_OBJECT`宏,这是信号和槽机制正常工作的前提。\n- 在连接信号和槽时,可以使用不同的连接方式(如`Qt::DirectConnection`或`Qt::QueuedConnection`)来满足不同的需求。\n\n相信这些步骤能够帮助你顺利自定义Qt中的信号和槽!如果你有更多问题或需要进一步的帮助,请随时问我哦!加油!',0,0,0,'2024-12-06 12:22:18','2024-12-27 09:44:03'),(300972,100105,200404,'死锁是指两个或多个线程在执行过程中,因为争夺资源而造成的一种互相等待的状态。具体来说,当线程A持有资源1,等待资源2,而线程B持有资源2,等待资源1时,就会形成死锁,两个线程都无法继续执行下去。\n\n死锁的四个必要条件包括:\n1. 互斥条件:至少有一个资源必须被当前线程独占。\n2. 占用并等待:一个线程持有至少一个资源,并等待获取其他资源。\n3. 不可抢占:已经分配给线程的资源在线程完成之前不能被其他线程抢占。\n4. 循环等待:存在一种线程资源的循环等待关系。\n\n为了避免死锁,可以采取以下几种策略:\n\n1. **资源分配顺序**:始终按照一定的顺序请求资源,例如所有线程在请求多个资源时,按照相同的顺序进行,这样可以避免循环等待的发生。\n\n2. **使用定时锁**:可以使用尝试加锁的方法,通过设置一个超时来避免线程长时间等待。如果线程无法获得锁,可以选择放弃或者重新尝试。\n\n3. **资源请求策略**:确保线程在执行某项操作时一次性请求所需的所有资源,而不是分次请求。\n\n4. **避免长时间持有锁**:尽量缩小持有锁的范围,减少持有锁的时间,释放不必要的锁。\n\n5. **死锁检测**:在某些系统中,可以实现死锁检测机制,通过监控线程状态和资源分配情况,及时发现和解除死锁。\n\n希望这些方法能帮助你更好地理解和避免死锁现象!加油,相信你能掌握这些知识!',0,0,0,'2024-12-06 12:22:25','2024-12-27 09:44:03'),(300973,100097,201012,'当然可以!在项目中组织和使用头文件就像在一场优雅的舞会中挑选伴侣,每一个头文件都必须与其相应的源文件搭配得体,以确保整个舞会(或项目)流畅进行。让我来为你描绘一下这一过程的乐趣!\n\n1. **分门别类**:首先,我会为头文件创建一个清晰的目录结构。就像为朋友们准备座位卡:所有的甜品在一边,咸食在另一边。将公共头文件放在一个名为 `include` 的文件夹中,而将特定模块的头文件放在相关的子文件夹中,比如 `moduleA/include` 和 `moduleB/include`。\n\n2. **文件命名**:为了避免文件在干扰声中迷失方向,我会给头文件取一个易于识别且具描述性的名字。比如,`math_utils.h` 负责数学工具,而 `network_manager.h` 则负责网络传输。这样,其他开发者就能一眼看出文件的功能,避免了“这个文件是做什么的?”的疑惑。\n\n3. **防止重复包含**:我将使用包括预处理指令 `#ifndef` 和 `#define` 来确保每个头文件只被包含一次。就好比在舞会上,不希望同一个伴侣转身跳到你身边十次。这样不仅能减少编译时间,还能避免潜在的符号重定义错误。代码示例如下:\n ```c\n #ifndef MATH_UTILS_H\n #define MATH_UTILS_H\n\n // 函数声明\n\n #endif // MATH_UTILS_H\n ```\n\n4. **合理引用**:我会小心选择在源文件中引用哪些头文件,尽量避免“无休止的包含”。就像舞会中,避免让每一个人都与每一个人交谈,保持适度的社交,这样才能营造更好的氛围。只引用那些真正需要使用的头文件,以保持编译依赖的简洁性。\n\n5. **文档注释**:每当我撰写一个头文件时,我会给它添加清晰的注释,以便在未来的“复盘”中,有人能快速了解文件的功能和用法。就像在舞会后总结每个伴侣的表现,让下次的舞会更加精彩。\n\n6. **版本控制**:在团队项目中,我会与同事保持沟通,确保头文件的变化被记录在版本控制系统中。这样一来,大家就可以查阅历史,不用担心“这个函数是从哪里来的?”的问题。\n\n通过这样一整套有趣而又高效的组织技巧,项目中的头文件就仿佛在舞会上翩翩起舞,既协调又优雅,让整个开发过程轻松愉快!希望这个描述能让你对头文件的组织有些灵感,尽情享受编码的乐趣吧!💃🎉',0,0,0,'2024-12-06 12:22:34','2024-12-27 09:44:03'),(300974,100043,200800,'在C++中实现接口通常是通过抽象类来完成的。抽象类是指至少有一个纯虚函数的类。纯虚函数是没有实现的函数,通常用`= 0`来表示,表示这个函数在子类中必须实现。下面是一个简单的示例,帮助你理解如何在C++中实现接口。\n\n### 示例:实现接口\n\n1. **定义一个接口(抽象类)**:\n```cpp\nclass IAnimal {\npublic:\n virtual void makeSound() = 0; // 纯虚函数\n virtual ~IAnimal() {} // 虚析构函数\n};\n```\n\n2. **实现接口的具体类**:\n```cpp\nclass Dog : public IAnimal {\npublic:\n void makeSound() override {\n std::cout << \"Bark!\" << std::endl;\n }\n};\n\nclass Cat : public IAnimal {\npublic:\n void makeSound() override {\n std::cout << \"Meow!\" << std::endl;\n }\n};\n```\n\n3. **使用接口**:\n```cpp\n#include \n\nint main() {\n IAnimal* dog = new Dog();\n IAnimal* cat = new Cat();\n\n dog->makeSound(); // 输出: Bark!\n cat->makeSound(); // 输出: Meow!\n\n delete dog; // 记得释放内存\n delete cat;\n\n return 0;\n}\n```\n\n### 总结\n在这个示例中,我们定义了一个`IAnimal`接口,其中包含一个纯虚函数`makeSound`。然后,我们创建了两个类`Dog`和`Cat`,它们分别实现了这个接口。使用接口(抽象类)的好处在于你可以通过指针或引用来处理不同的实现,从而实现多态性。\n\n希望这个示例能帮助你更好地理解如何在C++中实现接口!如果你有任何问题或需要更具体的实现,请随时问我!加油!',0,0,0,'2024-12-06 12:22:41','2024-12-27 09:44:03'),(300975,100109,200661,'Spring Cloud Config Server 提供了加密和解密配置属性的功能,以确保敏感信息的安全性。这里是如何支持加密和解密的基本步骤:\n\n### 1. 配置加密密钥\n在 Config Server 的配置文件(如 `application.yml` 或 `application.properties`)中,你需要定义一个加密密钥。可以通过以下方式设置:\n\n```yaml\nspring:\n cloud:\n config:\n server:\n encrypt:\n key: your-encryption-key\n```\n\n### 2. 使用加密属性\n在你的配置文件中,可以使用 `encrypt` 工具来加密敏感信息。例如,使用命令行工具或 REST API 来加密配置值:\n\n```bash\ncurl -X POST http://localhost:8888/encrypt -d \'your-sensitive-data\'\n```\n\n返回一个加密后的字符串,你可以将这个字符串放入你的配置文件中。\n\n### 3. 解密配置属性\n当你的应用需要读取这些配置属性时,Config Server 会自动解密任何以 `{cipher}` 前缀开头的属性。例如,在配置文件中这样设置:\n\n```yaml\nmy:\n secret: \'{cipher}encrypted-value\'\n```\n\n### 4. 使用RSA密钥对(可选)\n除了对称密钥,你也可以使用 RSA 公钥和私钥进行加密和解密。你需要在配置文件中指定公钥和私钥:\n\n```yaml\nspring:\n cloud:\n config:\n server:\n encrypt:\n key:\n public: \"-----BEGIN PUBLIC KEY-----\\n...\\n-----END PUBLIC KEY-----\"\n private: \"-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\"\n```\n\n### 5. 访问加密配置\n当你的应用启动时,Spring Cloud Config Server 会自动处理解密,你只需访问配置值即可。例如,使用 `@Value` 注解:\n\n```java\n@Value(\"${my.secret}\")\nprivate String mySecret;\n```\n\n### 结尾\n通过以上步骤,你可以有效地使用 Spring Cloud Config Server 来支持加密和解密配置属性,这样就能更好地保护敏感信息。如果你有任何疑问,或者遇到问题,请随时寻求帮助,你已经在前进的路上了!继续加油!',0,0,0,'2024-12-06 12:22:49','2024-12-27 09:44:03'),(300976,100026,200305,'当然可以!I/O多路复用是指在单个线程中同时监控多个I/O流的能力,允许程序在某些I/O操作上阻塞并等待多个事件的发生,而不必为每个I/O流创建一个独立的线程。这种技术广泛应用于网络编程中,提高了资源利用率和程序的响应能力。\n\n### 原理\n\n在Java中,I/O多路复用主要依靠操作系统提供的“选择器”(Selector)来实现。选择器允许单个线程进行多个通道的事件检测,下面是它的基本工作原理:\n\n1. **通道(Channel)**:Java NIO(New Input/Output)提供了一种“通道”机制来替代传统的流。通道可以异步地读写数据。\n\n2. **选择器(Selector)**:选择器是Java NIO中的一个核心组件,它是用于检测多个通道的I/O事件的对象。通过选择器,一个线程可以同时管理多个通道的I/O操作。\n\n3. **注册事件**:你可以将多个通道注册到同一个选择器上,并指定感兴趣的事件(如连接请求、可读、可写等)。\n\n4. **轮询**:当I/O事件发生时,选择器会检查所有注册的通道,发现有事件可处理时,返回这些通道的集合,供后续操作。\n\n### 实现\n\n下面是一个简单的Java NIO多路复用的例子:\n\n```java\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.Selector;\nimport java.nio.channels.SelectableChannel;\nimport java.nio.channels.ServerSocketChannel;\nimport java.nio.channels.SocketChannel;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.UnresolvedAddressException;\nimport java.util.Iterator;\nimport java.util.Set;\n\npublic class NioServer {\n private Selector selector;\n\n public NioServer(int port) throws IOException {\n selector = Selector.open();\n ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();\n serverSocketChannel.bind(new InetSocketAddress(port));\n serverSocketChannel.configureBlocking(false);\n serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);\n }\n\n public void start() throws IOException {\n while (true) {\n selector.select(); // 阻塞直到有事件发生\n Set selectedKeys = selector.selectedKeys();\n Iterator iterator = selectedKeys.iterator();\n\n while (iterator.hasNext()) {\n SelectionKey key = iterator.next();\n if (key.isAcceptable()) {\n handleAccept(key);\n } else if (key.isReadable()) {\n handleRead(key);\n }\n iterator.remove();\n }\n }\n }\n\n private void handleAccept(SelectionKey key) throws IOException {\n ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();\n SocketChannel socketChannel = serverSocketChannel.accept();\n socketChannel.configureBlocking(false);\n socketChannel.register(selector, SelectionKey.OP_READ);\n }\n\n private void handleRead(SelectionKey key) throws IOException {\n SocketChannel socketChannel = (SocketChannel) key.channel();\n ByteBuffer buffer = ByteBuffer.allocate(256);\n int bytesRead = socketChannel.read(buffer);\n if (bytesRead == -1) {\n socketChannel.close();\n } else {\n // 处理读取到的数据\n buffer.flip();\n socketChannel.write(buffer); // Echo back\n }\n }\n\n public static void main(String[] args) {\n try {\n new NioServer(8080).start();\n } catch (IOException e) {\n e.printStackTrace();\n }\n }\n}\n```\n\n### 总结\n\n以上代码展示了如何通过Java NIO实现I/O多路复用,创建一个简单的TCP服务器。这项技术能够帮助我们在处理高并发网络连接时,节省线程资源和提高应用的性能。\n\n在学习和实现的过程中,可能会遇到一些挑战,但保持积极的态度,尽量去理解每个组件的工作原理和相互之间的关系,相信你一定能掌握这项技术,加油!',0,0,0,'2024-12-06 12:23:03','2024-12-27 09:44:03'),(300977,100097,201160,'迭代器模式就像是一把万能钥匙,能够轻松开启集合中的每一扇门,让我们得以逐一探索其内部的奥秘。想象一下你正在经营一个充满宝藏的图书馆,每本书本身都是独特而珍贵的,而迭代器就是你用来搜寻图书的一本指南。\n\n在C++项目中实现迭代器模式,首先我们需要一个集合的类(比如说书架),它持有一个书籍的集合,而后,我们会为这个类设计一个迭代器类,来帮助我们顺序访问集合中的书籍。下面是一个具体的实现步骤,帮助你轻松驾驭这个模式。\n\n### 1. 创建集合类(书架)\n\n```cpp\n#include \n#include \n\nclass Book {\npublic:\n Book(std::string title) : title(title) {}\n std::string getTitle() const { return title; }\nprivate:\n std::string title;\n};\n\nclass BookShelf {\npublic:\n void addBook(const Book& book) {\n books.push_back(book);\n }\n\n // 创建迭代器\n class Iterator {\n public:\n Iterator(BookShelf& shelf, size_t index)\n : shelf(shelf), index(index) {}\n\n bool hasNext() {\n return index < shelf.books.size();\n }\n\n Book next() {\n return shelf.books[index++];\n }\n\n private:\n BookShelf& shelf;\n size_t index;\n };\n\n // 返回迭代器的功能\n Iterator createIterator() {\n return Iterator(*this, 0);\n }\n\nprivate:\n std::vector books;\n};\n```\n\n### 2. 使用迭代器遍历集合\n\n在我们的 `main()` 函数中,我们可以简单地创建几本书,并通过迭代器来逐一遍历。\n\n```cpp\nint main() {\n BookShelf shelf;\n shelf.addBook(Book(\"C++ Primer\"));\n shelf.addBook(Book(\"Effective C++\"));\n shelf.addBook(Book(\"The C++ Programming Language\"));\n\n // 使用迭代器来遍历书架\n auto iterator = shelf.createIterator();\n while (iterator.hasNext()) {\n Book book = iterator.next();\n std::cout << \"Reading: \" << book.getTitle() << std::endl;\n }\n\n return 0;\n}\n```\n\n### 3. 整体运行效果\n\n当我们运行这个程序时,控制台上将显现出一行行书名,仿佛在进行一场美妙的阅读之旅:\n\n```\nReading: C++ Primer\nReading: Effective C++\nReading: The C++ Programming Language\n```\n\n通过这一系列的代码,我们成功地将迭代器模式化为一个高效、优雅的解决方案,完美地解决了集合遍历的问题。迭代器不仅提供了一个统一的接口来访问集合中的元素,更是为扩充集合类型的能力打下了坚实的基础。\n\n这样的设计模式,不仅让我们的代码更加清晰明了,还允许在不修改集合的情况下,轻松地为其添加更多的访问方式,就像在图书馆里为书籍增添一个新的藏书目录,从而不断提高查找的便利性!',0,0,0,'2024-12-06 12:23:15','2024-12-27 09:44:03'),(300978,100085,200391,'线程在Java中的生命周期就像一部充满戏剧性的舞台剧,里面有不同的角色和状态,时而紧张刺激,时而轻松逗趣。让我们来看看这场戏的主要演员及其状态:\n\n1. **新建状态(New)**:线程刚刚出生,兴致勃勃,准备大显身手,但还没有开始执行。这就像小猫刚睁开眼睛,迫不及待想要探索世界。\n\n2. **就绪状态(Runnable)**:这时线程已经准备好了,跃跃欲试,但还在等待CPU的青睐。它就像一个排队的明星,随时准备闪亮登场,只需等待导演(CPU)的召唤。\n\n3. **运行状态(Running)**:当线程被CPU调度执行时,它进入了这一状态,完全投入工作,像一个全身心投入的演员,正在台上尽情表演。可不要小瞧它,这可是个紧张的时期,任何小失误都可能导致“戏毁”。\n\n4. **阻塞状态(Blocked)**:如果线程在等待某个锁资源,那它就会被迫停止演出,进入阻塞状态。这时,它就像是遇到了瓶颈,必须耐心等待其他演员(线程)把戏演完。\n\n5. **等待状态(Waiting)**:和阻塞不同,等候状态的线程在等待其他线程做一些事情,比如等待通知或其他条件,宛如一个焦急等候的观众,希望能尽快看到自己喜爱的表演。\n\n6. **超时等待状态(Timed Waiting)**:这是等待的另一种形式,不过,它给了线程一个最后的机会,设定了一个截止时间,像是一位心急如焚的观众,准备在特定时间之后离开。\n\n7. **死亡状态(Terminated)**:当线程完成了自己的使命,不管是顺利完成任务还是中途夭折,它就进入了死亡状态。演员落幕,再也不能重返舞台,观众只能怀念其英勇壮丽的一生。\n\n总之,Java线程的生命周期就像是一场精彩的话剧,各种状态交错变换,每个状态都有它的角色和职责。只希望所有的线程都能顺利演出,早日谢幕!✨',0,0,0,'2024-12-06 12:23:23','2024-12-27 09:44:03'),(300979,100081,200089,'管程(Monitor)是一种用于实现进程间同步和互斥的高级抽象机制。在操作系统中,管程可以被视为一个保护数据结构,它能够有效地管理多个进程对共享资源的访问,从而确保数据的一致性和完整性。\n\n管程主要包括以下几个关键特性:\n\n1. **封装性**:管程将共享资源的状态和操作封装在一起,提供了一组接口供外部进程调用。这种封装帮助隐藏了资源的内部细节,使得对资源的操作更为安全。\n\n2. **互斥访问**:管程确保在任一时刻只有一个进程能够执行其内部的操作。这通过使用某种形式的锁机制来实现,从而避免了数据竞争和不一致的情况。\n\n3. **条件变量**:管程通常配有条件变量,这些变量可以让进程在某些条件未满足时等待,直到其他进程改变状态并通知它们。这样的机制能够有效地管理进程的调度,使得资源使用更加灵活和高效。\n\n在操作系统中,管程的作用主要体现在以下几个方面:\n\n- **提高并发性**:通过合理地使用管程,多个进程可以在保证数据一致性的前提下并发执行,大幅提高系统的吞吐量。\n\n- **简化编程模型**:操作系统中的开发者可以使用管程来简化复杂的同步问题,相比于使用更底层的同步原语(如信号量),管程的抽象性使得程序设计和理解变得更为简单。\n\n- **支持高层次的逻辑**:管程可以处理复杂的状态逻辑和同步关系,将更多的注意力集中在系统的高层设计上,而不是底层的细节实现。\n\n综上所述,管程作为一种强大的同步机制,为操作系统的进程管理和资源共享提供了有效的解决方案,反映了在多任务环境下如何平衡效率与安全的深思哲理。通过对管程的合理运用,操作系统能够更好地应对并发执行带来的挑战,实现资源的有序与优化利用。',0,0,0,'2024-12-06 12:23:56','2024-12-27 09:44:03'),(300980,100065,200284,'在Java中,检测一个文件是否为符号链接可以使用 `java.nio.file` 包中的 `Files` 类。具体来说,可以使用 `Files.isSymbolicLink(Path path)` 方法来判断给定路径是否是一个符号链接。\n\n下面是一个简单的示例代码,演示如何检查一个文件是否为符号链接:\n\n```java\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\npublic class SymbolicLinkCheck {\n public static void main(String[] args) {\n // 你想要检测的文件路径\n String filePath = \"your/file/path/here\"; // 替换为你的文件路径\n Path path = Paths.get(filePath);\n\n // 检测是否为符号链接\n if (Files.isSymbolicLink(path)) {\n System.out.println(filePath + \" 是一个符号链接。\");\n } else {\n System.out.println(filePath + \" 不是一个符号链接。\");\n }\n }\n}\n```\n\n在这段代码中:\n- 我们使用 `Paths.get()` 方法将文件路径转换为 `Path` 对象。\n- 然后,通过调用 `Files.isSymbolicLink()` 来判断该路径是否为一个符号链接,并输出相应的信息。\n\n确保在运行此代码之前,已在你的计算机上创建了一个符号链接文件,以便能够测试它的功能。这个简单的方法能有效帮助你检测文件的真实身份!✨',0,0,0,'2024-12-06 12:24:02','2024-12-27 09:44:03'),(300981,100085,200212,'在Java编程中,组合和继承是两种主要的代码重用和组织方式,各自有其独特的优缺点。\n\n### 继承的优缺点\n\n**优点:**\n\n1. **简单易懂**:通过继承,子类可以直接访问父类的属性和方法,这使得代码结构清晰。\n2. **代码重用**:子类可以重用父类的代码,减少重复代码的出现。\n3. **多态性**:继承可以使代码实现多态性,增强了代码的灵活性。\n\n**缺点:**\n\n1. **紧耦合**:子类和父类之间高度耦合,子类依赖于父类的实现,这使得父类的修改可能影响到所有子类。\n2. **继承层次问题**:深层次的继承树使得代码难以理解和维护,尤其是在大型项目中。\n3. **难以实现动态行为**:继承关系静态,无法在运行时改变,灵活性不足。\n\n### 组合的优缺点\n\n**优点:**\n\n1. **松耦合**:通过组合,你可以创建松耦合的结构,组件之间的依赖较少,便于独立更改。\n2. **灵活性高**:组合可以灵活地组合不同的功能和特性,支持运行时的动态行为。\n3. **重用性强**:可以将相同的组件组合到不同的上下文中,增强了代码的重用性。\n\n**缺点:**\n\n1. **实现复杂**:组合通常需要额外的代码来管理不同组件之间的交互,增加了实现的复杂性。\n2. **调用链长**:通过组合访问目标功能时,可能需要多层的调用,降低了效率和可读性。\n3. **需要更多的设计**:在最初阶段,需要花费更多时间在系统设计上,以确保各组件的有效协调。\n\n### 何时选择组合而不是继承\n\n1. **需要高级的灵活性**:当你需要在运行时改变对象的行为或者在不同的上下文中重复使用某个功能时,组合更适合。\n \n2. **避免复杂的继承关系**:如果项目中存在多层复杂的继承;选择组合可以大大简化代码结构。\n\n3. **强调行为而非身份**:当功能的重用比类的身份和层次结构更重要时,组合是更好的选择。\n\n4. **不同功能的集成**:若需要将多个不同来源的功能集成到一个类中,组合提供了更灵活的方式来实现。\n\n总之,组合与继承各有所长,而选择使用哪种方式,往往取决于具体的需求、场景和未来的可扩展性。在许多情况下,合理地结合使用组合和继承,可以实现更优雅、更易于维护的代码结构。',0,0,0,'2024-12-06 12:24:14','2024-12-27 09:44:03'),(300982,100056,200112,'哎呀,数据库就像是一家大餐厅,分片和分区就像是这家餐厅的两种不同的菜单管理方式。让我们来看看他们之间的区别吧!\n\n**分区**就像是把餐厅的菜单分为不同的区域,比如“冷盘区”、“热菜区”、“甜点区”。每个区域里都有自己的菜品,方便顾客找到自己想要的食物。数据库中的分区也是如此,它将数据表划分成多个“区”,每个区可以独立存储、管理和查询。这些分区可以根据某些规则来划分,比如按时间、按类型等。\n\n而**分片**就像是把整个餐厅搬到不同的地方,开了几家分店。每家分店可能都经营相同的菜单,但由于顾客的分午餐需求不同,可能分布在不同的城市。数据库的分片也是这样,它是将数据表水平切分成多个“片”,每一片可以存储在不同的数据库服务器上。通过这样的方法,数据库可以更好地扩展,处理更多的请求,就像开更多的分店来吸引更多顾客。\n\n那么,最大的区别在于:\n- 分区是在同一个数据库内部,把数据分为多个部分,而分片则是在多个数据库之间,把数据分散存储。\n- 分区专注于如何组织和管理数据,而分片注重于如何扩展存储。\n\n总结一下,数据库的分区就像是餐厅里的菜单区域划分,而分片则是让餐厅在不同地方开分店。希望这样能让你更好地理解这两个概念!记得下次点菜时也可以考虑一下自己是喜欢分区还是分片哦!😄',0,0,0,'2024-12-06 12:24:22','2024-12-27 09:44:03'),(300983,100045,200777,'类就像是一个蓝图,你可以用它来造各种各样的“房子”(对象)。这让我们能够把各种属性和行为打包在一起,让程序更整洁,就像把你的袜子、裤子和衬衫分门别类放在衣柜里,避免了早上出门时的一片狼藉。\n\n在 C++ 中定义一个类就像设计一个新房子,基本步骤如下:\n\n```cpp\nclass 房子 {\npublic:\n // 属性(成员变量)\n int 房间数;\n double 面积;\n\n // 行为(成员函数)\n void 显示信息() {\n std::cout << \"这座房子有 \" << 房间数 << \" 个房间,面积是 \" << 面积 << \" 平方米。\\n\";\n }\n};\n```\n\n让我们来剖析一下这段代码:\n\n1. `class 房子 { ... };` 是定义类的关键。类的名字是“房子”,你可以随便命名,别叫得太奇怪就行。\n\n2. `public:` 后面的内容是公开的,可以被外部访问。就像你把房子的窗户打开,大家都能看到里面的一切。\n\n3. 属性(也称为成员变量)和行为(成员函数)就像这座房子的基本特征和功能。比如房子的房间数和面积就是属性,而显示信息的方法就是行为。\n\n4. 使用这个类实例化一个房子就像从蓝图中造出一座真实的房子:\n\n```cpp\nint main() {\n 房子 我的房子;\n 我的房子.房间数 = 3;\n 我的房子.面积 = 120.5;\n \n 我的房子.显示信息();\n\n return 0;\n}\n```\n\n总结一下,类是我们在 C++ 中组织代码的一种方式,让我们可以将数据和操作相关联,从而让程序看起来更加优雅(虽然有时候看起来也像一只大章鱼,触手遍及各处)。就这样,你准备好设计自己的“房子”了吗?',0,0,0,'2024-12-06 12:24:36','2024-12-27 09:44:03'),(300984,100036,200125,'慢查询日志就像是你厨房里的一个小记录本,专门用来记录那些在烹饪过程中表现不佳的食材。换句话说,慢查询日志是MySQL用来记录执行时间超过指定阈值的SQL语句的工具。这对于查找和优化性能瓶颈非常有帮助。\n\n### **慢查询日志的工作原理**\n\n当你在数据库中执行SQL查询时,MySQL会一次又一次地运行这些查询。但是有些查询可能会像一只慢吞吞的乌龟,花费很长时间才能完成。为了帮助你识别这些“慢”的查询,MySQL提供了慢查询日志功能。\n\n### **如何启用慢查询日志**\n\n1. **配置MySQL**:\n - 找到你的MySQL配置文件,通常名为`my.cnf`或`my.ini`,并添加(或修改)以下配置:\n ```ini\n [mysqld]\n slow_query_log = 1 # 启用慢查询日志\n slow_query_log_file = /var/log/mysql/mysql-slow.log # 指定日志文件路径\n long_query_time = 2 # 设定什么查询算“慢”,这里代表执行超过2秒的查询\n ```\n\n2. **重启MySQL服务**:\n 在应用配置更改后,你需要重启MySQL服务以使更改生效。例如:\n ```bash\n sudo systemctl restart mysql\n ```\n\n3. **动态设置(可选)**:\n 如果你不想修改配置文件,也可以在MySQL命令行界面中动态设置这些选项:\n ```sql\n SET GLOBAL slow_query_log = \'ON\'; -- 开启慢查询日志\n SET GLOBAL long_query_time = 2; -- 设定慢查询阈值\n ```\n\n### **如何查看慢查询日志**\n\n一旦慢查询日志启用并运行,你可以通过简单的文本编辑器来查看日志文件,比如使用以下命令:\n```bash\ncat /var/log/mysql/mysql-slow.log\n```\n在这里你能看到那些执行时间过长的SQL查询,通常格式如下:\n```\n# Time: 2023-01-01T10:00:00.123456Z\n# User@Host: user[user] @ localhost []\n# Query_time: 3.456789 Lock_time: 0.000000 Rows_sent: 100 Rows_examined: 200\nSELECT * FROM tablename WHERE column = \'something\';\n```\n\n### **分析慢查询**\n\n查看慢查询日志后,你可以使用一些工具(例如`pt-query-digest`)来分析慢查询并提取有价值的信息,帮助你找出性能瓶颈。优化这些查询、添加索引、或调整数据库结构都可以显著提升性能。\n\n总之,慢查询日志是数据库优化中的一把利刃,通过记录和分析这些“慢”的查询,你能让你的数据库像一台运转顺畅的机器一般,快速响应用户的请求。',0,0,0,'2024-12-06 12:24:51','2024-12-27 09:44:03'),(300985,100116,200602,'Spring Boot Starter就像是一盒神奇的调料,让你的Spring项目立刻香气四溢,做菜变得轻松又快捷!具体来说,Spring Boot Starter是一些预配置的依赖集合,它们根据不同的需求来帮助你快速上手。\n\n例如:\n- **spring-boot-starter-web**:就像为你的厨房增添了一台高效的电饭煲,让你轻松做出美味的Web应用。\n- **spring-boot-starter-data-jpa**:它为你准备好了数据库的调味料,使得与数据库的互动变得毫不费力。\n- **spring-boot-starter-security**:就像请来了一个保镖,帮你守护应用的安全。\n\n简而言之,Spring Boot Starter能让你快速配置和搭建应用,省去繁琐的环境搭建过程,让你可以把更多的时间用来研究业务逻辑,而不是在各种依赖间辗转反侧,简直是程序员的“快餐”选择!😄',0,0,0,'2024-12-06 12:24:56','2024-12-27 09:44:03'),(300986,100003,201061,'你有没有考虑过使用自动化测试框架,比如JUnit或RSpec?这些工具可以帮助我们确保代码在不同平台上的一致性。还有像Selenium或Appium这样的工具,它们能够进行跨浏览器或跨设备的测试,你觉得这些会不会提高我们的测试效率?另外,使用CI/CD工具,比如Jenkins或Travis CI,来自动化构建和测试过程,你觉得这种方式能否有效减少潜在的错误呢?',0,0,0,'2024-12-06 12:25:01','2024-12-27 09:44:03'),(300987,100089,200234,'HashSet 和 HashMap 是 Java 中两个非常常用的集合类,它们虽然名字相似,但各自的用途和特性却截然不同。让我们来生动形象地揭开它们的面纱吧!\n\n### 1. 数据结构\n\n- **HashSet**:\n 想象一下一个阳光明媚的草地,上面散落着一些五彩斑斓的石子。每一个石子代表一个独特的元素。HashSet 就是一个只关心石子存在与否的地方,它不在乎石子的顺序和重复。当你将一个新石子放进草地时,如果草地上已经有了同样的石子,那么它就会拒绝再次放入。就这样,HashSet 保持了唯一性。\n\n- **HashMap**:\n 再想象一下,一个应用于商店的货架,货架上装满了各种商品,每一个商品都有一个独特的标签(钥匙)和它自己价格(值)。HashMap 就像这个货架,它通过标签来快速存取对应的商品。当你给定一个标签(钥匙)时,它可以迅速找到这个商品的价格(值)。这个结构允许标签和值的组合是唯一的,但标签和值本身可以是重复的。\n\n### 2. 存储的元素\n\n- **HashSet**:\n HashSet 存储的元素是独一无二的,没有重复。因此,它只关注元素的存储,而不关注和其它元素的关系。例如,你的好友列表中的每一个好友都是唯一的,不能有重复的。\n\n- **HashMap**:\n HashMap 则存储的是键值对(key-value pairs)。每一个键(key)必须是唯一的,而值(value)可以重复。就像在商店中,键是商品的标签,而值是价格,多种商品可能有相同的价格。\n\n### 3. 访问方式\n\n- **HashSet**:\n 访问 HashSet 中的元素时,你不能根据特定的索引来获取元素,而是通过它的特性,快速判断某个元素是否存在。就像是在草地上你寻找特定的石子,彻底依靠它的存在与否。\n\n- **HashMap**:\n HashMap 允许你通过键来快速获取对应的值。想象一下,你根据标签在货架上快速找到某个商品的价格,这就是 HashMap 的妙处所在。\n\n### 4. 性能\n\n- 两者都支持 O(1) 的时间复杂度来进行插入、删除和查找操作。因此,尽管它们的具体用途不同,但在性能上都表现出色。\n\n### 总结\n\n在这个五彩斑斓的集合世界里,HashSet 就像那唯美的草地,专注于每一颗独特的石子;而 HashMap 则宛如一个整齐的货架,精明地管理着每一个商品与其对应的标签。选择哪一个,取决于你希望如何存放和访问这些宝贵的数据!',0,0,0,'2024-12-06 12:25:13','2024-12-27 09:44:03'),(300988,100080,200204,'在Java中,克隆机制是通过 `Cloneable` 接口和 `Object` 类的 `clone()` 方法来实现的。克隆机制可以用于创建对象的副本。\n\n### 浅克隆和深克隆\n\n1. **浅克隆**:在浅克隆中,创建的副本对象中的基本类型字段值与原对象相同,但引用类型字段的值将仅仅是指向同一对象的引用。这意味着若引用对象被修改,原对象和克隆对象都将受到影响。\n\n2. **深克隆**:深克隆则创建一个新的对象,不仅复制原对象的基本类型字段,还会递归地复制引用对象的内容,从而确保克隆对象和原对象之间完全独立。\n\n### 实现浅克隆\n\n要实现浅克隆,首先,类需要实现 `Cloneable` 接口,并重写 `clone()` 方法,如下所示:\n\n```java\nclass Person implements Cloneable {\n String name;\n int age;\n\n public Person(String name, int age) {\n this.name = name;\n this.age = age;\n }\n\n @Override\n protected Object clone() throws CloneNotSupportedException {\n return super.clone(); // 直接调用 Object 的 clone() 方法\n }\n}\n\npublic class Main {\n public static void main(String[] args) {\n try {\n Person original = new Person(\"Alice\", 30);\n Person cloned = (Person) original.clone();\n \n System.out.println(original == cloned); // false\n System.out.println(original.name.equals(cloned.name)); // true\n } catch (CloneNotSupportedException e) {\n e.printStackTrace();\n }\n }\n}\n```\n\n### 实现深克隆\n\n对于深克隆,你需要确保在克隆过程中,所有引用类型的字段都被单独克隆。可以通过实现 `Cloneable` 接口的类及其引用对象的 clone 方法来实现:\n\n```java\nclass Address implements Cloneable {\n String city;\n \n public Address(String city) {\n this.city = city;\n }\n\n @Override\n protected Object clone() throws CloneNotSupportedException {\n return super.clone(); // 直接调用 Object 的 clone() 方法\n }\n}\n\nclass Person implements Cloneable {\n String name;\n int age;\n Address address; // 引用类型\n \n public Person(String name, int age, Address address) {\n this.name = name;\n this.age = age;\n this.address = address;\n }\n\n @Override\n protected Object clone() throws CloneNotSupportedException {\n Person cloned = (Person) super.clone();\n cloned.address = (Address) this.address.clone(); // 深克隆地址\n return cloned;\n }\n}\n\npublic class Main {\n public static void main(String[] args) {\n try {\n Address address = new Address(\"New York\");\n Person original = new Person(\"Alice\", 30, address);\n Person cloned = (Person) original.clone();\n \n System.out.println(original == cloned); // false\n System.out.println(original.address == cloned.address); // false\n } catch (CloneNotSupportedException e) {\n e.printStackTrace();\n }\n }\n}\n```\n\n在上面的示例中,`Address` 类和 `Person` 类都实现了 `Cloneable` 接口,并在克隆 `Person` 对象时,特别处理了 `Address` 对象的克隆,从而实现了深克隆。\n\n希望这些信息对你有帮助!如果还有其他问题,欢迎随时询问。继续加油!',0,0,0,'2024-12-06 12:25:24','2024-12-27 09:44:03'),(300989,100112,200023,'你有没有想过,为什么我们在访问网页时,有时需要登录,而有时又可以直接使用一些功能?这就涉及到了Cookie和Session这两个概念。\n\nCookie,是不是可以看作是一个小小的文本文件,存储在用户的浏览器中?它可以保存用户的信息,比如偏好设置和登录状态,让用户在下次访问时,感觉更顺畅、更个性化。这背后是不是在传递一种状态呢?\n\n而Session,则更像是在服务器上保存用户状态的一种机制,对吗?当你登录一个网站时,服务器会为你创建一个Session,随后会把Session ID存储在一个Cookie中,发送给你的浏览器。这样,如果没有Session,用户的状态信息又该从何而来呢?\n\n那么,它们之间的区别是什么呢?Cookie是不是通常是前端保存,而Session则是后端控制呢?而且,Cookie的存储量是有限的,是否会因此影响某些应用的实现?而在安全性上,Session是否会更优,因为信息只是存储在服务器上? \n\n总的来说,你觉得Cookie和Session的有效使用,是不是关乎于如何为用户提供更好的体验,又或者保障信息的安全呢?',0,0,0,'2024-12-06 12:25:35','2024-12-27 09:44:03'),(300990,100054,200177,'Java中的异常处理机制就像一个超级英雄在拯救程序,当然,没有披风和紧身衣,但绝对能在危机时刻出手相救。让我们来看看它是如何在幕后运作的吧。\n\n1. **异常的定义**:在Java中,异常是一种不正常的情况或事件,它会打断程序的正常执行流程。可以把它想象成一个顽皮的小孩,突然从旁边冒出来,搞得所有人心神不宁。\n\n2. **try-catch块**:\n - **try**:这是你可以放置可能会出错的代码的地方。就像是你在火锅店,准备去试一下那个怪异的新口味。\n - **catch**:如果在try块中发生了异常,catch块就会接住这个异常,帮助你处理错误。它就像是那个勇敢的朋友,迅速出手拯救了你,不让你被辣得满脸通红。\n\n3. **finally块**:不管是异常发生与否,finally块中的代码总是会被执行。可以把它看作是你吃完火锅后不管好不好都得去洗手一样,最终都会做的事情。\n\n4. **throw和throws**:如果你想自己主动抛出一个异常,可以使用throw 关键字。这就像是你在火锅店吃得不爽,咕哝着把菜单扔回去。而throws则是用来在方法声明中,表示这个方法可能会抛出异常,允许调用者处理它。\n\n5. **自定义异常**:如果你觉得Java自带的异常不够有趣,可以自己创建异常类。这就像是你在火锅店开始研究自己调配的独特酱料,要确保其他人知道你的“美味”!\n\n总结一下,Java的异常处理机制就像一个靠谱的“保姆”,确保程序在运行过程中尽可能不被干扰。于是,当那些可恶的Bug出现时,你就可以淡定地用try-catch-fun的方式解决它们,挥挥手继续大步向前!',0,0,0,'2024-12-06 12:25:42','2024-12-27 09:44:03'),(300991,100114,201078,'QT中的样式表(QSS,Qt Style Sheets)是一种强大的工具,用于定制和美化Qt应用程序的外观与感觉。就像网页开发中的CSS(层叠样式表)一样,QSS允许开发者使用简单的语法来定义控件的视觉表现,包括颜色、边框、字体、背景等。\n\n### QSS的工作原理\n\n1. **选择器**:QSS使用选择器来匹配特定的控件。比如,你可以选择推按钮(`QPushButton`),文本框(`QLineEdit`),甚至整个窗口(`QWidget`)。选择器可以是具体的控件类型,也可以是类名或对象名。\n\n ```css\n QPushButton {\n background-color: blue;\n color: white;\n }\n ```\n\n2. **属性**:在选择器后,你可以定义一系列的属性来改变控件的外观。这些属性包括:\n - `color`:文本颜色\n - `background-color`:背景颜色\n - `border`:边框样式\n - `font`:字体类型和大小\n - `padding`:内边距\n\n 例如:\n\n ```css\n QLineEdit {\n border: 1px solid gray;\n padding: 5px;\n }\n ```\n\n3. **继承与覆盖**:QSS支持样式的继承,父控件的样式可以被子控件继承,而子控件可以用自己的样式覆盖父控件的样式。这使得样式管理灵活而且有层次感。\n\n4. **状态样式**:QSS也可以定义控件在不同状态下的样式,如悬停、按下和禁用状态。例如:\n\n ```css\n QPushButton:hover {\n background-color: lightblue;\n }\n ```\n\n5. **动态应用**:QSS样式可以在代码中动态应用或修改,让应用在不同情况下呈现不同的风格。例如,可以根据用户的选择来改变主题(如暗色或亮色模式)。\n\n### 使用QSS的好处\n\n1. **直观简单**:通过类CSS的语法,样式设置变得直观容易。\n2. **快速修改**:修改界面外观只需调整样式表,而不必重写复杂的绘制代码。\n3. **统一风格**:可以在整个应用中维护一致的视觉风格,提升用户体验。\n\n### 结尾思考\n\n想象一下,在你的应用中,点击一个按钮就能瞬间换上不同的\"服装\",这就是QSS给我们带来的魔力!通过简单的语法,一个普通的控件能够变得生动、优雅,把用户的体验推向一个新的高峰。让我们勇敢地使用QSS,给用户带来焕然一新的视觉享受吧!',0,0,0,'2024-12-06 12:25:52','2024-12-27 09:44:03'),(300992,100071,201180,'要在OpenGL中创建和管理纹理,你可以把这个过程想象成做一个巨大的美味蛋糕,甚至比乌云密布的天空还要复杂!下面是一些幽默的步骤,帮助你完成这个“纹理蛋糕”:\n\n1. **准备好材料**:首先,确保你有一个有效的OpenGL上下文,就像你要确保你的烤箱已经预热。\n\n2. **生成纹理**:让我们开始吧!要创建一个纹理,就像在购物清单上写“买豆腐”,但这里是“生成纹理”:\n\n ```c\n GLuint texture;\n glGenTextures(1, &texture); // 这就像给你的蛋糕取个名字\n ```\n\n3. **绑定纹理**:然后,你需要“绑住”这个纹理,就像把盆子固定在桌子上,以免打翻:\n\n ```c\n glBindTexture(GL_TEXTURE_2D, texture);\n ```\n\n4. **设置纹理参数**:这是为你的蛋糕加上美味的糖霜和装饰的时刻:\n\n ```c\n glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // 水平方向的包裹方式\n glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // 垂直方向的包裹方式\n glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // 缩小时的过滤器\n glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 放大时的过滤器\n ```\n\n5. **上传纹理数据**:现在,是时候把你的牛奶和鸡蛋(嗯,就是数据)放进去:\n\n ```c\n int width, height, nrChannels;\n unsigned char *data = stbi_load(\"texture.jpg\", &width, &height, &nrChannels, 0); // 用stb库加载纹理\n if (data) {\n glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);\n glGenerateMipmap(GL_TEXTURE_2D); // 生成mipmap,增加纹理细节\n } else {\n std::cout << \"纹理加载失败!\" << std::endl;\n }\n stbi_image_free(data); // 释放加载的空间,避免内存泄漏\n ```\n\n6. **使用纹理**:在渲染的过程中,就像等待蛋糕烤好,准备享用一样:\n\n ```c\n glActiveTexture(GL_TEXTURE0); // 激活纹理单元\n glBindTexture(GL_TEXTURE_2D, texture); // 绑定纹理\n ```\n\n7. **删除纹理**:最后,如果这个纹理不再需要了,记得好好处理它,让它安息(释放内存):\n\n ```c\n glDeleteTextures(1, &texture); // 不要忘了清理,否则你的厨房会变得一团糟!\n ```\n\n就这样!遵循这些步骤,你就能在OpenGL中创建和管理纹理了。希望你的纹理像你的烘焙技艺一样出色!🍰✨',0,0,0,'2024-12-06 12:26:02','2024-12-27 09:44:03'),(300993,100047,200418,'理解和使用Java中的读写锁(ReadWriteLock)可以让我们更有效地处理多线程编程中对共享资源的访问。下面是一些基本概念和实践建议,希望能帮助你更好地掌握这个工具。\n\n### 理解读写锁\n\n1. **基本概念**:\n - `ReadWriteLock`是一种特殊类型的锁,它允许多个线程同时读共享资源,但在写操作时,它会阻止所有其他读和写操作。\n - 这可以提高性能,因为在只读操作较多的情况下,多个线程可以并行读取资源,从而减少了线程竞争的开销。\n\n2. **组成部分**:\n - Java中的`ReadWriteLock`接口在`java.util.concurrent.locks`包中定义,有两个主要的方法:\n - `readLock()`:获取一个读锁。\n - `writeLock()`:获取一个写锁。\n\n### 使用读写锁\n\n下面是一个使用`ReadWriteLock`的示例,展示如何在多线程环境中进行读写操作。\n\n```java\nimport java.util.concurrent.locks.ReadWriteLock;\nimport java.util.concurrent.locks.ReentrantReadWriteLock;\n\npublic class SharedResource {\n private final ReadWriteLock rwLock = new ReentrantReadWriteLock();\n private int data;\n\n // 读取数据\n public int readData() {\n rwLock.readLock().lock(); // 获取读锁\n try {\n return data; // 安全地返回数据\n } finally {\n rwLock.readLock().unlock(); // 保证解锁\n }\n }\n\n // 写入数据\n public void writeData(int newData) {\n rwLock.writeLock().lock(); // 获取写锁\n try {\n data = newData; // 安全地写入数据\n } finally {\n rwLock.writeLock().unlock(); // 保证解锁\n }\n }\n}\n```\n\n### 使用中的注意事项\n\n1. **避免死锁**:\n - 使用读写锁时,确保始终在`finally`块中释放锁,这有助于避免潜在的死锁情况。\n\n2. **合适的锁粒度**:\n - 在设计你的数据结构时,注意选择合适的锁粒度。如果只对某些方法加锁,而其他方法没有配置合适的锁,会影响性能。\n\n3. **性能测试**:\n - 在实际应用中,对读写锁的表现进行性能测试是很重要的,因为性能提升往往依赖于特定应用的读写比例。\n\n通过适当地使用读写锁,你可以有效提高应用程序的并发性能,并确保数据的安全性。希望这些建议能帮助你更好地理解和使用Java中的读写锁,如果还有其他问题,欢迎随时提问!',0,0,0,'2024-12-06 12:26:10','2024-12-27 09:44:03'),(300994,100049,200293,'当然可以!想象一下你在等待咖啡。阻塞模式就像你在咖啡店里耐心地等着,直到服务员把那杯咖啡端到你面前,你才开始做其他事情。这期间你别无选择,只能站在那儿——也许还得顺便认识一下隔壁排队的朋友。\n\n而非阻塞模式呢?就像你在咖啡店里点完咖啡后,便可以自由自在地逛商店、浏览手机、甚至给外星人打电话。你不需要一直盯着咖啡机想“咖啡到底什么时候好”,而是可以在那儿做许多有趣的事情,直到咖啡让你知道:“嘿,亲爱的,我来了!”\n\n### 它们的区别:\n\n1. **阻塞模式(Blocking IO)**:\n - 一旦请求发出,程序就会停下来(阻塞),直到有结果返回。\n - 在编程中,使用类似 `InputStream.read()` 这样的调用时,如果没有数据可读,程序就会一直等下去(像在咖啡店前扣脚踝)。\n\n2. **非阻塞模式(Non-blocking IO)**:\n - 请求发出后,程序会继续执行,而不会被请求挂起。如果没有结果,程序可以做别的事情(像罗马假日的柯基犬,欢快地四处奔跑)。\n - 在编程中,通常会使用 `Selector` 和 `Channel` 来实现,这样可以在多个连接上同时工作,而不至于像鸽子一样被卡住。\n\n### 何时使用:\n- 阻塞模式简单易懂,适合那些不太在乎等待时间的小项目。\n- 非阻塞模式适合高性能要求的应用,像聊天软件、游戏服务器等,需要同时处理多个请求。\n\n总之,选择哪种模式就看你是更愿意做站着的“咖啡等待者”,还是随意逛街的“咖啡点购者”了!希望这个比喻能让你对 Java 中的阻塞与非阻塞模式有更清晰的了解!',0,0,0,'2024-12-06 12:26:21','2024-12-27 09:44:03'),(300995,100102,200763,'在C++中,友元函数(Friend Function)是一种特定的函数,它并不属于某个类的成员,但可以访问这个类的私有(private)和保护(protected)成员。友元函数的声明需要在类内部进行,前面加上关键字 `friend`,这样就告诉编译器这个函数可以访问这个类的私有和保护成员。\n\n### 友元函数的特性:\n1. **并非成员函数**:友元函数不是类的成员,它可以在类的外部定义。\n2. **访问权限**:友元函数可以完全访问该类的私有和保护成员,打破了封装的界限。\n3. **不受对象限制**:友元函数可以作用于多个对象,甚至可以作用于没有直接关联的对象。\n\n### 使用场景:\n友元函数通常用在以下几种情况:\n\n1. **需要操作多个类的私有成员**:当两个或多个类相互协作,需要共享彼此的私有数据,例如在一个数学运算中,两个不同的类可能需要互相访问对方的成员来完成运算。\n\n ```cpp\n class B; // 前向声明\n\n class A {\n private:\n int data;\n public:\n A(int val) : data(val) {}\n friend void showData(A, B); // 友元函数\n };\n\n class B {\n private:\n int value;\n public:\n B(int val) : value(val) {}\n friend void showData(A, B); // 友元函数\n };\n\n void showData(A a, B b) {\n std::cout << \"A\'s data: \" << a.data << \" B\'s value: \" << b.value << std::endl;\n }\n ```\n\n2. **实现操作符重载**:在操作符重载中,通常需要访问类的私有成员,友元函数提供了便利。\n\n ```cpp\n class Complex {\n private:\n float real;\n float imag;\n public:\n Complex(float r = 0, float i = 0) : real(r), imag(i) {}\n\n // 友元函数实现复杂数的加法\n friend Complex operator+(const Complex &c1, const Complex &c2);\n };\n\n Complex operator+(const Complex &c1, const Complex &c2) {\n return Complex(c1.real + c2.real, c1.imag + c2.imag);\n }\n ```\n\n3. **提高封装等级的灵活性**:在面向对象设计中,友元函数打破了传统的封装限制,使得某些设计模式或框架能够更灵活。\n\n### 哲学思考:\n友元函数在C++中体现了一个重要的编程理念:谨慎地平衡封装与灵活性。虽然友元函数打破了私有成员的保护机制,但它们的使用应被视为一种设计选择,需在明确需求和潜在风险之间找到合适的平衡点。过度使用友元函数可能导致代码的复杂性增加,维护难度加大,因此在设计时应谨慎考虑其必要性与合理性。\n\n在编程的世界中,封装与访问的对立与统一,正如生活中人与人之间的信任与透明,往往决定了系统的设计与实现的优雅程度。',0,0,0,'2024-12-06 12:26:35','2024-12-27 09:44:03'),(300996,100036,200548,'Spring框架中的异常处理机制可以说是一个精巧的设计,它提供了多种方式来捕捉和处理异常,使得开发者能够优雅地管理应用程序中的错误情况。以下是Spring框架中异常处理的几个主要方面:\n\n### 1. **Controller层的异常处理**\n在Spring MVC中,控制器可以使用`@ExceptionHandler`注解来定义方法,这些方法可以专门处理控制器内抛出的特定异常。例如:\n\n```java\n@Controller\npublic class MyController {\n\n @GetMapping(\"/example\")\n public String exampleMethod() {\n // 可能会抛出异常\n throw new CustomException(\"Something went wrong!\");\n }\n\n @ExceptionHandler(CustomException.class)\n public ResponseEntity handleCustomException(CustomException ex) {\n return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);\n }\n}\n```\n\n### 2. **全局异常处理**\n使用`@ControllerAdvice`注解可以定义一个全局异常处理类,这样可以将异常处理逻辑集中管理,方便维护。例如:\n\n```java\n@ControllerAdvice\npublic class GlobalExceptionHandler {\n\n @ExceptionHandler(Exception.class)\n public ResponseEntity handleAllExceptions(Exception ex) {\n return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);\n }\n}\n```\n\n### 3. **ResponseEntityExceptionHandler**\nSpring还提供了`ResponseEntityExceptionHandler`类,可以继承它来自定义错误响应。这是处理常见异常(如`HttpRequestMethodNotSupportedException`等)的一个便捷方式。\n\n```java\n@ControllerAdvice\npublic class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {\n\n @Override\n protected ResponseEntity handleHttpMessageNotReadable(\n HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {\n return new ResponseEntity<>(\"Malformed JSON request\", HttpStatus.BAD_REQUEST);\n }\n}\n```\n\n### 4. **自定义异常**\n用户可以根据业务需要自定义异常类,来代表不同的错误情况。通过`@ResponseStatus`注解可以设置HTTP状态码:\n\n```java\n@ResponseStatus(HttpStatus.NOT_FOUND)\npublic class ResourceNotFoundException extends RuntimeException {\n public ResourceNotFoundException(String message) {\n super(message);\n }\n}\n```\n\n### 5. **日志记录**\n在异常处理方法中,可以利用日志框架(例如SLF4J)记录异常信息,以便后续分析和排查。\n\n### 6. **国际化和用户友好的错误信息**\n可以结合Spring的国际化支持,根据用户的语言返回不同的错误信息,提升用户体验。\n\n### 小结\nSpring的异常处理机制灵活多样,能够帮助开发者构建健壮且用户友好的应用程序。通过合适的异常处理策略,不仅可以有效地捕捉和处理错误,还能返回清晰、有意义的反馈给用户,让程序即使在意外情况下也能保持优雅的姿态。',0,0,0,'2024-12-06 12:26:47','2024-12-27 09:44:03'),(300997,100089,200068,'哎呀,说到缓冲区溢出攻击,简直是黑客们的“老伙计”。想象一下,你的程序就像一个盛水的杯子,而缓冲区就是这个杯子。正常情况下,水(数据)不会溢出来,但如果你不断往里面倒,就会有水(数据)流出来,淹没你桌子上的其他东西——这就是缓冲区溢出,简直是编程界的“洪水”。\n\n那么这些黑客是怎么利用这个漏洞的呢?他们会故意向程序输入超过它能够处理的数据量,导致程序运行时写入数据到不该去的地方。结果就是,黑客可以篡改程序的执行流程,运行他们想要的恶意代码。听起来是不是很可怕?别担心,这里有一些防范措施,帮你堵上这个“水龙头”:\n\n1. **使用安全函数**:嫌弃传统的 `strcpy`、`scanf` 等函数太不靠谱?换成更安全的版本,比如 `strncpy`、`snprintf` 等,它们可以限制数据的写入长度,减少溢出的可能性。\n\n2. **堆栈保护**:现代编译器都有这项酷炫的功能,像GCC的 `-fstack-protector`,能在函数返回时检查栈是否被篡改。如果被篡改,那程序直接“崩!”掉,爽快得很。\n\n3. **地址随机化**:这是个高端黑科技,叫做地址空间布局随机化(ASLR)。每次程序运行时,它的内存地址都会被随机化,黑客想要攻击就像在黑暗中找魔方,麻烦得很。\n\n4. **最小权限原则**:别让你的程序拥有超过所需的权限,搞得好像它是一把能开万把锁的钥匙。越少权限,越难被黑客利用。\n\n5. **代码审计和静态分析**:定期检查你的代码,像是给你的程序做健康检查。可以使用一些工具,自动找出潜在的溢出点,让你的代码得到“安全认证”。\n\n总之,缓冲区溢出攻击就像一场严肃的程序界闹剧,防范措施就像是为你保驾护航的护法。别让黑客在你家后院组织派对!',0,0,0,'2024-12-06 12:26:58','2024-12-27 09:44:03'),(300998,100007,200603,'啊,Spring Boot 的注解就像调料,少了它们,菜肯定没味道!快来看看这几种常用的注解,简直就是开发者的“调味料”:\n\n1. **@SpringBootApplication**:这个注解就像是你的家打了个烙印,告诉别人这是个 Spring Boot 应用。它集成了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan,完美得就像是全能型选手!\n\n2. **@RestController**:这个注解是让你的类成为一个 RESTful 的控制器,绝对是今时今日的网络应用必备,让你的 API 响应如同高分贝音乐,无缝到位。\n\n3. **@RequestMapping**:架起了请求和方法之间的桥梁,就像是相亲角的红娘,专门负责匹配请求和处理它的函数。\n\n4. **@Autowired**:决定性的“依赖注入”操作,让你的类中的 dependencies 不再孤独无援,轻松获取需要的组件,反正不愁没有好伙伴!\n\n5. **@Value**:专门用来注入属性值,这样一来,你的配置文件里的值就能轻松进入代码之中,像魔法一样直接!\n\n6. **@Component**,**@Service**,**@Repository**:这些注解都是用来标记 Spring 管理的 Bean 的,简直就像是给你的类贴上了“我是重要的,别走开”的标签。\n\n7. **@Configuration**:用来说明这个类是 Spring 的一个配置类,里面可以定义 bean,让你从此告别无脑的 XML 配置。\n\n8. **@EnableAutoConfiguration**:让 Spring Boot 自动配置你的 Spring 应用,这就好比菜市场有人给你现成炒好的菜,你只需要端走就行,懒人福音啊!\n\n9. **@Entity**:这可是跟数据库打交道的好帮手,把你的 Java 类和数据库表关联起来,简直就是给对象一张身份证,爽歪歪!\n\n10. **@Transactional**:让你轻松管理数据库事务,这是让数据安全和一致性的必备法宝。\n\n这些注解就像是开发者的日常生命线,少了哪个都是不行的。所以,赶紧去用上它们,让你的 Spring Boot 应用飞起来吧!飞得鸟儿都想来羡慕你!',0,0,0,'2024-12-06 12:27:05','2024-12-27 09:44:03'),(300999,100072,200762,'作用域解析运算符(`::`)在C++中就像一把钥匙,能打开各种命名空间、类和全局作用域的大门。它的主要功能是明确指出某个名字(如变量、函数或类)所在的上下文,从而避免命名冲突和不确定性。\n\n### 使用场景\n\n1. **命名空间**:\n 在C++中,可以使用命名空间来组织代码,尤其是当多个库可能会引入相同的名字时。使用 `::` 可以清晰地指明你想使用哪个命名空间中的成员。\n\n ```cpp\n #include \n\n namespace MyNamespace {\n void myFunction() {\n std::cout << \"Hello from MyNamespace!\" << std::endl;\n }\n }\n\n int main() {\n MyNamespace::myFunction(); // 调用 MyNamespace 中的 myFunction\n return 0;\n }\n ```\n\n2. **类**:\n 在定义了类的成员函数时,也可以使用 `::` 来指明这些成员属于哪个类。\n\n ```cpp\n class MyClass {\n public:\n void display() {\n std::cout << \"Display from MyClass\" << std::endl;\n }\n\n static void staticFunction() {\n std::cout << \"Static function in MyClass\" << std::endl;\n }\n };\n\n void MyClass::display(); // 定义成员函数时需要使用作用域解析运算符\n\n int main() {\n MyClass obj;\n obj.display(); // 调用实例成员函数\n MyClass::staticFunction(); // 调用静态成员函数\n return 0;\n }\n ```\n\n3. **全局作用域**:\n 如果你有一个局部变量或函数与全局命名冲突,可以使用 `::` 来访问全局命名空间中的成员。\n\n ```cpp\n int value = 10; // 全局变量\n\n void myFunction() {\n int value = 20; // 局部变量\n std::cout << \"Local value: \" << value << std::endl;\n std::cout << \"Global value: \" << ::value << std::endl; // 访问全局变量\n }\n\n int main() {\n myFunction();\n return 0;\n }\n ```\n\n### 小结\n\n简而言之,`::` 运算符是 C++ 中一个非常重要的工具,能够有效地帮助开发者在复杂的命名环境中找到正确的“钥匙”,安全而精确地访问所需的成员。使用得当,它将使你的代码更加清晰可读,同时提高维护性。就像地图上的标志指引你走向目的地,作用域解析运算符帮助你找到代码的“正确位置”。',0,0,0,'2024-12-06 12:27:18','2024-12-27 09:44:03'),(301000,100026,200368,'在Java中处理HTTP请求的头部信息,就像在一场晚会上与各类客人打交道。你得确保每个人都能得到他们想要的关注和食物(也就是数据)。下面是几种处理HTTP请求头部信息的方法,让我们一起来欢乐地探索!\n\n### 1. 使用Servlet处理请求头\n\n如果你在一个Servlet中工作,就像是一位晚会的 DJ,负责调整每种音乐风格(头部信息),你可以直接通过`HttpServletRequest`对象获取请求头。下面是一个简单的示例:\n\n```java\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\npublic class MyServlet extends HttpServlet {\n protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\n // 获取某个特定头部信息\n String userAgent = request.getHeader(\"User-Agent\");\n System.out.println(\"User-Agent: \" + userAgent);\n \n // 获取所有头部信息\n System.out.println(\"所有请求头:\");\n request.getHeaderNames().asIterator().forEachRemaining(headerName -> {\n String headerValue = request.getHeader(headerName);\n System.out.println(headerName + \": \" + headerValue);\n });\n }\n}\n```\n就在这里,我们有了DJ的麦克风,可以一一调解每个头部的信息。\n\n### 2. 使用Spring Boot处理请求头\n\n如果你在用Spring Boot开发,那你就像是在高档餐厅做服务员,甚至可以用框架为你处理很多繁琐的事情!只需用`@RequestHeader`注解即可轻松获取请求头信息:\n\n```java\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestHeader;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\npublic class MyController {\n\n @GetMapping(\"/headers\")\n public String getHeaders(@RequestHeader(\"User-Agent\") String userAgent,\n @RequestHeader Map headers) {\n System.out.println(\"User-Agent: \" + userAgent);\n headers.forEach((key, value) -> System.out.println(key + \": \" + value));\n return \"请求头信息已打印!\";\n }\n}\n```\n这就像是告诉你的顾客:“嘿,您想要的每一项服务我都记下来了!”\n\n### 3. 使用HttpURLConnection处理请求头\n\n如果你在自己进行HTTP请求,那么你就是自己的厨师,需要自己为菜肴调味。下面是使用`HttpURLConnection`设置请求头的简单示例:\n\n```java\nimport java.io.BufferedReader;\nimport java.io.InputStreamReader;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\n\npublic class HttpExample {\n public static void main(String[] args) {\n try {\n URL url = new URL(\"http://example.com\");\n HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n connection.setRequestMethod(\"GET\");\n connection.setRequestProperty(\"User-Agent\", \"Java\");\n\n BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));\n String inputLine;\n while ((inputLine = in.readLine()) != null) {\n System.out.println(inputLine);\n }\n in.close();\n } catch (Exception e) {\n e.printStackTrace();\n }\n }\n}\n```\n这里,你就是自己掌勺的厨师,调得恰到好处的请求头。\n\n### 总结\n\n处理HTTP请求头就像是一场社交聚会,根据情况你可以选择不同的方式与来宾(请求头)互动。不论你使用的是Servlet,Spring Boot,还是HttpURLConnection,关键都是要了解你需要的信息并妥善处理。同时,记得给每位客人(请求头信息)都点上最恰当的关注。现在去吧,让你的程序如同一场美妙的派对般流畅运作!🎉',0,0,0,'2024-12-06 12:27:31','2024-12-27 09:44:03'),(301001,100082,201146,'单例模式是一种常用的设计模式,旨在确保一个类只有一个实例,并提供全局访问点。在C++中实现单例模式有几种方法,最常见的一种方式是通过使用静态成员和私有构造函数。下面是一个简单的单例模式的实现示例:\n\n```cpp\nclass Singleton {\nprivate:\n static Singleton* instance; // 存储单一实例的指针\n Singleton() {} // 私有构造函数,防止外部创建对象\n\npublic:\n // 获取单一实例的静态方法\n static Singleton* getInstance() {\n if (instance == nullptr) {\n instance = new Singleton(); // 第一次调用时创建实例\n }\n return instance;\n }\n\n // 一些公共成员函数\n void someTask() {\n // 进行一些操作\n }\n};\n\n// 初始化静态成员\nSingleton* Singleton::instance = nullptr;\n```\n\n在这个例子中,`Singleton`类的构造函数是私有的,外部无法直接创建实例。通过`getInstance`方法,可以确保只有一个`Singleton`对象存在。\n\n### 潜在问题\n\n1. **线程安全**:上面的实现不是线程安全的。如果多个线程同时调用`getInstance`,可能会创建多个实例。为了避免这个问题,可以在单例创建时使用互斥锁。\n\n```cpp\n#include \n\nclass Singleton {\nprivate:\n static Singleton* instance;\n static std::mutex mtx; // 互斥量\n Singleton() {}\n\npublic:\n static Singleton* getInstance() {\n if (instance == nullptr) {\n std::lock_guard lock(mtx); // 加锁\n if (instance == nullptr) {\n instance = new Singleton();\n }\n }\n return instance;\n }\n};\n```\n\n2. **内存泄漏**:如果需要在程序的生命周期中动态地分配单例对象,必须确保在程序结束时正确释放内存。可以使用智能指针(如`std::unique_ptr`)来管理对象的生命周期,从而减少内存泄漏的风险。\n\n3. **测试困难**:单例模式会导致代码的耦合度提高,单元测试时很难模拟和替换单例对象。为了缓解此问题,可以使用依赖注入的方式,将单例对象的使用限制在单一模块中,并在测试时提供替代品。\n\n4. **全局状态**:单例模式带来的全局状态可能导致代码难以跟踪,容易引发状态之间的相互依赖,提高了维护的复杂度。\n\n总的来说,单例模式在某些场景下非常有用,但在使用时需谨慎考虑其潜在问题。了解这些潜在问题并采取相应的措施,可以帮助您更好地设计和实现高质量的软件。继续探索C++和设计模式的应用,非常值得!如果有其他问题,欢迎随时提问!',0,0,0,'2024-12-06 12:27:49','2024-12-27 09:44:03'),(301002,100030,200414,'Semaphore(信号量)在Java中是一个非常有用的工具,就像是一位严格的门卫,控制着多少个线程可以同时访问某个资源。它能帮助我们在多线程环境中有效地管理资源和防止竞争条件。简单来说,信号量可以用来限制访问某种资源的线程数量,比如数据库连接、服务端口等。\n\n### Semaphore的作用:\n1. **限制资源访问**:想象一下,你有一间小小的会议室,最多只能容纳5个人。Semaphore就可以帮助你管理这些人,确保会议室不会被挤得水泄不通。\n2. **保护共享资源**:它可以防止多个线程同时访问共享资源,从而避免数据不一致的问题。\n3. **实现流量控制**:在需要控制线程执行数量的场景中,比如数据库连接池,Semaphore可以让消费者的数量不超过一定的阈值。\n\n### 使用方法:\n1. **创建Semaphore**:可以指定可用的许可数量。\n ```java\n Semaphore semaphore = new Semaphore(5); // 可用5个许可\n ```\n\n2. **申请许可**(acquire):当线程想要访问共享资源时,需要先申请许可。如果没有可用的许可,线程就会在这里等待。\n ```java\n semaphore.acquire();\n ```\n\n3. **释放许可**(release):当线程完成对资源的使用时,记得释放许可,以便让其他线程可以使用这个资源。\n ```java\n semaphore.release();\n ```\n\n### 示例代码:\n下面是一个简单的示例,展示了如何使用Semaphore控制线程访问。\n\n```java\nimport java.util.concurrent.Semaphore;\n\npublic class SemaphoreExample {\n private static final Semaphore semaphore = new Semaphore(3); // 允许3个线程同时访问资源\n\n public static void main(String[] args) {\n for (int i = 1; i <= 10; i++) {\n new Thread(new Worker(i)).start();\n }\n }\n\n static class Worker implements Runnable {\n private int threadNumber;\n\n Worker(int threadNumber) {\n this.threadNumber = threadNumber;\n }\n\n @Override\n public void run() {\n try {\n System.out.println(\"Thread \" + threadNumber + \" is trying to acquire a permit.\");\n semaphore.acquire(); // 申请许可\n System.out.println(\"Thread \" + threadNumber + \" has acquired a permit.\");\n\n // 模拟某种资源访问时间\n Thread.sleep(2000);\n } catch (InterruptedException e) {\n e.printStackTrace();\n } finally {\n System.out.println(\"Thread \" + threadNumber + \" is releasing a permit.\");\n semaphore.release(); // 释放许可\n }\n }\n }\n}\n```\n\n在这个例子中,最多允许3个线程同时访问同步的区域,每当一个线程获得许可后,其他线程就得乖乖等在门外。等它们完成工作了,再把许可释放出来,门卫才会放他们入场!\n\n总之,Semaphore 是一个非常厉害的工具,让我们在多线程编程中能够有序地访问共享资源,避免“拥挤”的场面!',0,0,0,'2024-12-06 12:27:58','2024-12-27 09:44:03'); +/*!40000 ALTER TABLE `note` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `note_like` +-- + +DROP TABLE IF EXISTS `note_like`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `note_like` ( + `note_id` int unsigned NOT NULL COMMENT '笔记 ID', + `user_id` bigint unsigned NOT NULL COMMENT '点赞用户 ID', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', + PRIMARY KEY (`note_id`,`user_id`), + KEY `idx_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='笔记点赞表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `note_like` +-- + +LOCK TABLES `note_like` WRITE; +/*!40000 ALTER TABLE `note_like` DISABLE KEYS */; +INSERT INTO `note_like` VALUES (300523,100015,'2025-01-09 15:27:22','2025-01-09 15:27:22'),(301004,100015,'2025-01-07 20:38:18','2025-01-07 20:38:18'),(301005,100015,'2025-01-06 16:29:54','2025-01-06 16:29:54'),(301006,100015,'2025-01-06 16:37:28','2025-01-06 16:37:28'),(301007,100015,'2025-01-06 16:37:42','2025-01-06 16:37:42'),(301008,100015,'2025-01-06 16:30:00','2025-01-06 16:30:00'),(301009,100015,'2025-01-06 16:37:39','2025-01-06 16:37:39'),(301010,100015,'2025-01-07 14:06:21','2025-01-07 14:06:21'),(301011,100015,'2025-01-06 16:28:06','2025-01-06 16:28:06'),(301012,100015,'2025-01-06 16:39:25','2025-01-06 16:39:25'),(301013,100015,'2025-01-07 17:15:47','2025-01-07 17:15:47'),(301015,100015,'2025-01-09 15:27:41','2025-01-09 15:27:41'); +/*!40000 ALTER TABLE `note_like` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `question` +-- + +DROP TABLE IF EXISTS `question`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `question` ( + `question_id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '问题 ID', + `category_id` int unsigned NOT NULL COMMENT '问题所属分类 ID', + `title` varchar(255) NOT NULL COMMENT '问题标题', + `difficulty` tinyint unsigned NOT NULL COMMENT '问题难度: 1=简单, 2=中等, 3=困难', + `exam_point` varchar(255) DEFAULT NULL COMMENT '题目考点', + `view_count` int unsigned NOT NULL DEFAULT '0' COMMENT '浏览量', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', + PRIMARY KEY (`question_id`), + KEY `idx_category` (`category_id`) +) ENGINE=InnoDB AUTO_INCREMENT=201226 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='题目表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `question` +-- + +LOCK TABLES `question` WRITE; +/*!40000 ALTER TABLE `question` DISABLE KEYS */; +INSERT INTO `question` VALUES (200000,100001,'TCP/IP模型和OSI模型分别是什么?它们之间有什么区别?',2,'网络模型',145,'2024-12-04 19:19:26','2025-01-09 15:20:23'),(200001,100001,'从输入URL到页面展示发生了什么?',2,'网络请求处理流程',14,'2024-12-04 19:19:26','2025-01-08 21:09:17'),(200002,100001,'HTTP请求报文和响应报文是怎样的?',2,'HTTP报文结构',5,'2024-12-04 19:19:26','2024-12-26 17:08:58'),(200003,100001,'HTTP请求方式有哪些?',1,'HTTP请求方法',9,'2024-12-04 19:19:26','2025-01-02 09:59:21'),(200004,100001,'GET请求和POST请求的区别是什么?',1,'HTTP请求方法',3,'2024-12-04 19:19:26','2024-12-26 16:24:38'),(200005,100001,'HTTP请求中常见的状态码有哪些?它们分别代表什么含义?',1,'HTTP状态码',4,'2024-12-04 19:19:26','2024-12-26 17:13:30'),(200006,100001,'什么是强缓存和协商缓存?它们的工作原理是什么?',2,'HTTP缓存机制',8,'2024-12-04 19:19:26','2024-12-27 15:18:08'),(200007,100001,'HTTP1.0和HTTP1.1的区别是什么?',2,'HTTP版本差异',10,'2024-12-04 19:19:26','2025-01-08 19:25:18'),(200008,100001,'HTTP2.0与HTTP1.1相比有哪些主要改进?',2,'HTTP版本差异',33,'2024-12-04 19:19:26','2025-01-09 15:26:33'),(200009,100001,'HTTP3.0有了解过吗?它与之前的版本有哪些主要不同?',3,'HTTP版本差异',11,'2024-12-04 19:19:26','2024-12-26 17:03:57'),(200013,100001,'TCP连接如何确保可靠性?',2,'TCP可靠性',4,'2024-12-04 19:19:26','2025-01-14 17:06:16'),(200016,100001,'能说说拥塞控制是怎么实现的嘛?',2,'TCP拥塞控制',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200022,100001,'CDN是什么?它在网络传输中有什么作用?',1,'内容分发网络',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200023,100001,'Cookie和Session是什么?它们在网络通信中扮演什么角色?有什么区别?',1,'Web会话管理',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200033,100001,'什么是HTTP持久连接?它在网络通信中有什么作用?',1,'HTTP连接管理',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200048,100001,'什么是网络分层模型的封装与解封装过程?它在网络通信中有什么作用?',2,'网络模型',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200049,100001,'HTTP/2相比HTTP/1.1在性能上有哪些提升?这些提升是如何实现的?',2,'HTTP版本差异',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200059,100002,'你知道的线程同步的方式有哪些?',2,'线程同步的机制、方法',6,'2024-12-04 19:19:26','2025-01-17 17:09:42'),(200060,100002,'有哪些页面置换算法?',2,'页面置换算法的类型、特点、应用场景',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200061,100002,'熟悉哪些Linux命令?',1,'Linux操作系统的基本命令、使用技巧',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200066,100002,'进程有几种状态,它们之间是如何转换的?',1,'进程状态、状态转换条件',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200068,100002,'解释一下缓冲区溢出攻击,以及如何防范?',2,'系统安全、攻击类型、防范措施',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200079,100002,'什么是请求分页存储管理,它是如何工作的?',2,'存储管理、请求分页原理',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200082,100002,'解释一下操作系统的文件共享机制,以及它的实现方式?',2,'文件系统、文件共享原理',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200086,100002,'解释一下操作系统的磁盘调度算法,以及常见的调度算法有哪些?',2,'磁盘管理、磁盘调度算法类型',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200087,100002,'什么是进程的控制块PCB,它包含哪些信息?',1,'进程管理、PCB原理',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200089,100002,'什么是管程,它在操作系统中有什么作用?',2,'同步机制、管程原理',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200105,100003,'解释一下数据库的三大范式?',1,'数据库设计、范式概念',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200112,100003,'什么是数据库的分片和分区?有什么区别?',2,'数据库分片、分区概念、比较',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200119,100003,'MySQL中的全文索引是什么?如何使用?',1,'全文索引概念、使用方法',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200125,100003,'MySQL中的慢查询日志是什么?如何使用?',1,'慢查询日志概念、使用方法',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200130,100004,'CPU和GPU的主要区别是什么?',1,'处理器类型、应用场景',85,'2024-12-04 19:19:26','2024-12-26 15:26:18'),(200136,100004,'SRAM与DRAM的区别是什么?它们各自的应用场景如何?',2,'存储器类型、应用场景',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200139,100004,'CPU中的寄存器有哪些?它们各自的作用是什么?',1,'寄存器类型、作用',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200154,100004,'什么是总线仲裁?常见的总线仲裁方式有哪些?',2,'总线仲裁概念、方式',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200169,100004,'计算机中的总线宽度和总线频率对系统性能有何影响?',2,'总线宽度频率、系统性能影响',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200170,100006,'Java中的数据类型有哪些?分为哪两大类?',1,'Java数据类型及其分类',4,'2024-12-04 19:19:26','2025-01-14 20:03:05'),(200177,100006,'Java中的异常处理机制是怎样的?',2,'Java异常处理机制',1,'2024-12-04 19:19:26','2024-12-30 15:49:43'),(200201,100007,'Java中的异常处理机制是怎样的?try-catch-finally块是如何工作的?',2,'异常处理、异常类型、异常捕获与处理',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200203,100007,'如何在Java中自定义异常?自定义异常的作用是什么?',2,'异常类继承、throw/throws关键字、异常处理策略',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200204,100007,'Java中的克隆机制是什么?如何实现对象的浅克隆和深克隆?',2,'Cloneable接口、clone方法、对象复制',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200208,100007,'Java中的注解是什么?它们是如何定义的?注解的作用是什么?',2,'Annotation机制、元数据、注解使用',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200212,100007,'Java中的组合与继承各有什么优缺点?在什么情况下你会选择使用组合而不是继承?',2,'组合与继承、设计原则、代码复用与灵活性',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200214,100007,'Java中的模板方法模式是如何定义的?它在实际编程中有哪些应用?',2,'设计模式、模板方法模式、算法骨架',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200220,100007,'在Java中,如何使用反射机制来获取一个类的构造函数并创建对象?',3,'Reflection API、Class类、Constructor类、对象创建',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200222,100007,'Java中的代理模式是什么?它有哪些类型?各自的应用场景是什么?',3,'设计模式、代理模式、静态代理、动态代理',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200226,100007,'Java中的泛型擦除是什么?它如何影响泛型类型的实际运行?',2,'泛型机制、类型擦除、泛型类型检查',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200234,100008,'HashSet 和 HashMap 的区别?',1,'底层数据结构',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200242,100008,'HashSet和LinkedHashSet的区别是什么?',1,'元素顺序、底层实现',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200249,100008,'ListIterator和Iterator的区别是什么?',1,'迭代器功能、双向遍历',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200253,100008,'在多线程环境下,使用ArrayList可能会遇到什么问题?',2,'线程安全问题、竞态条件',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200256,100008,'TreeSet中存储的元素需要满足什么条件?',1,'自然排序、Comparable接口',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200284,100009,'Java中如何检测文件是否为符号链接?',2,'Path类和Files类',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200293,100009,'解释Java中的阻塞/非阻塞模式以及它们的区别。',1,'阻塞与非阻塞模式',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200299,100009,'解释Java中的I/O流和NIO通道的区别。',2,'I/O流与NIO通道',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200302,100009,'Java中如何使用NIO实现高效的文件传输?',2,'文件传输效率',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200305,100009,'解释Java中I/O多路复用的原理和实现。',3,'I/O多路复用',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200331,100010,'如何设置请求的编码以及响应内容的类型?',1,'设置请求和响应的编码',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200351,100011,'Java中如何使用URL和URLConnection进行网络请求?',1,'Java网络请求,URLConnection',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200368,100011,'Java中如何处理HTTP请求的头部信息?',2,'HTTP头部处理,Java HTTP API',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200372,100011,'解释一下什么是HTTP的持久连接(Keep-Alive)及其优势。',1,'HTTP持久连接,性能优化',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200382,100011,'在Java中,如何实现HTTP请求的异步处理?',2,'HTTP异步请求,Java异步编程',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200389,100012,'线程 start 和 run 的区别?',1,'多线程编程',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200391,100012,'什么是Java中的线程生命周期?它包含哪些状态?',2,'线程的生命周期和状态',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200402,100012,'synchronized和ReentrantLock的区别是什么?',2,'锁类型比较',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200404,100012,'解释一下Java中的死锁是什么?如何避免?',2,'死锁的概念、产生原因和避免方法',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200408,100012,'什么是守护线程(Daemon Thread)?它有什么特点?',1,'守护线程的概念和特点',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200414,100012,'Java中的Semaphore有什么作用?如何使用?',2,'Semaphore信号量的作用和用法',1,'2024-12-04 19:19:26','2024-12-26 10:53:30'),(200418,100012,'如何理解和使用Java中的读写锁(ReadWriteLock)?',2,'读写锁的原理和使用场景',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200420,100012,'在Java中,如何检测和处理线程泄漏?',3,'线程泄漏的检测和处理',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200425,100012,'解释一下Java中的内存溢出(OOM)和内存泄漏(Memory Leak)。',2,'内存溢出和内存泄漏的概念和区别',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200428,100012,'Java中的ThreadLocal是如何工作的?它有什么用途?',2,'ThreadLocal的原理和用途',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200433,100012,'Java中如何实现延迟任务(Scheduled Task)?',2,'延迟任务的实现方法',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200437,100012,'在Java中,如何实现一个线程安全的单例模式?',2,'线程安全的单例模式实现方法',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200438,100012,'解释一下什么是Java中的锁偏向性(Lock Biasing)?',3,'锁偏向性的概念和原理',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200440,100012,'解释一下Java中的锁消除(Lock Elimination)和锁粗化(Lock Coarsening)。',3,'锁消除和锁粗化的原理和优化效果',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200445,100013,'Java中的抽象工厂模式(Abstract Factory Pattern)是如何实现解耦的?',2,'抽象工厂模式的解耦机制',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200453,100013,'Java中的简单工厂模式(Simple Factory Pattern)与工厂方法模式(Factory Method Pattern)有什么区别?',1,'简单工厂模式与工厂方法模式的区别',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200472,100014,'Java中的垃圾回收(GC)机制是如何工作的?有哪些常见的垃圾回收器?',2,'垃圾回收机制与回收器',0,'2024-12-04 19:19:26','2024-12-04 19:19:26'),(200479,100014,'如何监控JVM的性能?有哪些常用的监控工具?',2,'JVM性能监控与工具',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200492,100014,'JVM中的线程是如何管理的?包括线程的创建、调度和销毁。',2,'JVM线程管理',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200496,100014,'JVM中的类数据共享(Class Data Sharing, CDS)是什么?它如何工作?',2,'类数据共享的定义与工作机制',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200504,100014,'JVM中的对象头(Object Header)包含哪些信息?',1,'对象头包含的信息',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200507,100014,'解释一下JVM中的ABI(Application Binary Interface)是什么?',1,'ABI的定义',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200513,100014,'JVM中的安全点检查(Safepoint Check)是如何进行的?',2,'安全点检查的执行过程',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200514,100014,'解释一下JVM中的锁膨胀(Lock Inflation)是什么?为什么需要锁膨胀?',2,'锁膨胀的定义与必要性',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200518,100014,'解释一下JVM中的动态编译(Dynamic Compilation)是什么?它如何工作?',2,'动态编译的定义与工作机制',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200525,100014,'如何诊断JVM中的堆外内存泄漏(Off-Heap Memory Leak)?',3,'堆外内存泄漏的诊断',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200532,100015,'说一说你对Spring AOP的了解,它主要解决什么问题?',2,'AOP实现原理,面向切面编程',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200536,100015,'SpringMVC 中的DispatcherServlet扮演什么角色?',1,'DispatcherServlet作用',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200542,100015,'Spring框架如何支持JDBC模板(JdbcTemplate)来简化数据库操作?',2,'JdbcTemplate使用',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200545,100015,'Spring框架中的@ModelAttribute注解的作用是什么?',1,'@ModelAttribute注解作用',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200548,100015,'Spring框架中的异常处理机制是怎样的?',2,'Spring异常处理机制',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200569,100015,'Spring框架中如何配置和使用自定义的BeanPostProcessor来修改Bean的属性或行为?',3,'BeanPostProcessor自定义使用',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200570,100015,'Spring框架中如何管理事务的隔离级别和传播行为?请举例说明。',2,'事务隔离级别和传播行为',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200582,100015,'Spring框架中的@Conditional注解是如何实现条件化Bean创建的?',2,'@Conditional注解与条件化Bean创建',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200587,100015,'Spring框架中如何配置和使用自定义的MessageSourceAccessor来访问国际化消息?',2,'MessageSourceAccessor配置',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200592,100015,'Spring框架中的@Resource注解是如何处理依赖注入的?它与@Autowired有何不同?',1,'@Resource注解与依赖注入',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200594,100015,'Spring框架中的@EnableWebMvc注解的作用是什么?它如何启用Spring MVC的高级特性?',2,'@EnableWebMvc注解作用',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200602,100016,'Spring Boot Starter有什么用?',1,'Starters特性',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200603,100016,'Spring Boot常用注解?',1,'注解',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200607,100016,'如何在Spring Boot中实现应用程序的安全性?',2,'Spring Security集成',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200615,100016,'如何在Spring Boot中实现分页和排序?',2,'数据访问层功能',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200629,100016,'Spring Cloud中的服务发现与注册是什么?如何使用?',2,'服务发现与注册',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200631,100016,'Spring Cloud Gateway是什么?如何在Spring Boot中使用?',2,'API Gateway',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200641,100017,'Hystrix是什么?它解决了什么问题?',2,'Hystrix断路器模式,服务容错',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200645,100017,'Spring Cloud Config是什么?它如何管理分布式配置?',2,'Config Server配置中心',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200646,100017,'如何配置Spring Cloud Config Server和Client?',2,'Config Server与Client配置',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200661,100017,'Spring Cloud Config Server如何支持加密/解密的配置属性?',3,'Config Server加密/解密配置',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200678,100017,'Spring Cloud Stream如何保证消息的顺序性?',2,'Stream消息顺序性',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200682,100017,'如何使用Spring Cloud Sleuth和Zipkin进行分布式追踪的可视化?',2,'Sleuth与Zipkin可视化',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200694,100017,'解释一下Spring Cloud的“服务熔断”与“服务降级”的区别。',2,'熔断与降级区别',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200704,100018,'什么是Java中的内存泄漏?如何识别和解决内存泄漏问题?',2,'内存泄漏识别与解决',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200707,100018,'请描述Java中的并发编程模型,并说明如何使用并发工具类(如CountDownLatch, CyclicBarrier等)来优化性能。',2,'并发编程模型,并发工具类',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200711,100018,'说一说线程池有哪些常用参数?如何合理配置线程池以提高系统性能?',2,'线程池配置',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200720,100018,'Java中的并发集合(如ConcurrentHashMap, CopyOnWriteArrayList等)相比传统集合在性能上有哪些优势?',1,'并发集合性能优势',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200736,100018,'Java中的JIT编译器如何识别热点代码并进行优化?',3,'JIT编译器热点代码识别与优化',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200737,100018,'如何通过优化Java的GC日志来诊断和解决GC性能问题?',3,'GC日志优化,GC性能问题诊断',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'),(200742,100018,'如何通过优化Java的JVM垃圾回收策略来减少GC停顿时间?',3,'JVM垃圾回收策略优化,减少GC停顿时间',0,'2024-12-04 19:19:27','2024-12-04 19:19:27'); +/*!40000 ALTER TABLE `question` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `question_list` +-- + +DROP TABLE IF EXISTS `question_list`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `question_list` ( + `question_list_id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '题单 ID', + `name` varchar(32) NOT NULL COMMENT '题单名称', + `type` tinyint NOT NULL DEFAULT '1' COMMENT '题单类型:1=普通题单,2=训练营题单', + `description` varchar(255) DEFAULT NULL COMMENT '题单描述', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', + PRIMARY KEY (`question_list_id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='题单表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `question_list` +-- + +LOCK TABLES `question_list` WRITE; +/*!40000 ALTER TABLE `question_list` DISABLE KEYS */; +INSERT INTO `question_list` VALUES (2,'tidan',2,'题单题单秒速','2024-12-30 10:31:28','2024-12-30 10:37:53'),(3,'tidan',1,'题单描333','2024-12-30 10:39:26','2024-12-30 10:42:22'),(5,'题单',2,'描述','2024-12-30 10:44:39','2024-12-30 10:44:39'); +/*!40000 ALTER TABLE `question_list` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `question_list_item` +-- + +DROP TABLE IF EXISTS `question_list_item`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `question_list_item` ( + `question_list_id` int unsigned NOT NULL COMMENT '题单 ID', + `question_id` int unsigned NOT NULL COMMENT '题目 ID', + `rank` int unsigned NOT NULL COMMENT '题单内题目的顺序, 从 1 开始', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', + PRIMARY KEY (`question_list_id`,`question_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='题单项表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `question_list_item` +-- + +LOCK TABLES `question_list_item` WRITE; +/*!40000 ALTER TABLE `question_list_item` DISABLE KEYS */; +INSERT INTO `question_list_item` VALUES (2,200116,4,'2024-12-30 20:23:01','2024-12-31 11:01:39'),(2,200171,2,'2024-12-31 09:47:17','2024-12-31 11:01:37'),(2,200181,3,'2024-12-31 10:36:55','2024-12-31 11:01:38'),(2,200193,1,'2024-12-31 10:37:05','2024-12-31 10:49:57'),(5,200170,1,'2025-01-07 21:00:22','2025-01-07 21:00:22'),(5,200171,2,'2025-01-07 21:00:26','2025-01-07 21:00:26'),(5,200172,3,'2025-01-07 21:00:31','2025-01-07 21:00:31'),(5,200173,4,'2025-01-07 21:00:41','2025-01-07 21:00:41'),(5,200174,5,'2025-01-07 21:00:46','2025-01-07 21:00:46'),(5,200180,7,'2025-01-07 21:01:07','2025-01-07 21:01:07'),(5,200181,6,'2025-01-07 21:01:00','2025-01-07 21:01:00'); +/*!40000 ALTER TABLE `question_list_item` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `statistic` +-- + +DROP TABLE IF EXISTS `statistic`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `statistic` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `login_count` int unsigned DEFAULT '0', + `register_count` int unsigned DEFAULT '0', + `total_register_count` int unsigned DEFAULT '0', + `note_count` int unsigned DEFAULT '0', + `submit_note_count` int unsigned DEFAULT '0', + `total_note_count` int unsigned DEFAULT '0', + `date` date NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `statistic` +-- + +LOCK TABLES `statistic` WRITE; +/*!40000 ALTER TABLE `statistic` DISABLE KEYS */; +/*!40000 ALTER TABLE `statistic` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `user` +-- + +DROP TABLE IF EXISTS `user`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user` ( + `user_id` bigint unsigned NOT NULL AUTO_INCREMENT, + `account` varchar(32) NOT NULL COMMENT '用户账号,注册时自定义,注册后不可修改,包含数字、字母、下划线', + `username` varchar(16) DEFAULT NULL COMMENT '用户名,可修改,包含中文,字母,数字,下划线', + `password` varchar(255) NOT NULL COMMENT '加密后的登录密码', + `gender` tinyint unsigned NOT NULL DEFAULT '3' COMMENT '性别: 1=男, 2=女, 3=保密', + `birthday` date DEFAULT NULL COMMENT '用户生日', + `avatar_url` varchar(255) DEFAULT NULL COMMENT '用户头像地址', + `email` varchar(128) DEFAULT NULL COMMENT '用户邮箱', + `school` varchar(64) DEFAULT NULL COMMENT '用户学校', + `signature` varchar(128) DEFAULT NULL COMMENT '用户签名', + `is_banned` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '封禁状态: 0=未封禁, 1=已封禁', + `is_admin` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '管理员状态: 0=普通用户, 1=管理员', + `last_login_at` datetime DEFAULT NULL COMMENT '用户最后一次登录时间', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', + PRIMARY KEY (`user_id`), + UNIQUE KEY `uk_account` (`account`), + UNIQUE KEY `uk_email` (`email`) +) ENGINE=InnoDB AUTO_INCREMENT=100124 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `user` +-- + +LOCK TABLES `user` WRITE; +/*!40000 ALTER TABLE `user` DISABLE KEYS */; +INSERT INTO `user` VALUES (100001,'autumn_breeze','秋风飒飒','Zk!8xQ9@lw',1,'1996-11-19','https://i.pravatar.cc/150?img=37','autumn_breeze@example.com','合肥工业大学','秋天的风,总带来一丝怀念。',0,0,'2024-12-04 21:11:06','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100002,'autumn_leaf','秋叶','Yp8@kL9!xw',2,'1996-09-09','https://i.pravatar.cc/150?img=30','autumn_leaf@example.com','深圳大学','飘落的秋叶,也是生命的轮回。',0,0,'2024-12-04 21:10:56','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100003,'blackpearl','黑珍珠','zV!6p@T4nL',2,'2002-02-02','https://i.pravatar.cc/150?img=22','blackpearl@example.com','华东师范大学','虽身在深海,但光芒依旧。',0,0,'2024-12-04 21:10:06','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100004,'blue_sky99','蓝天碧水','R6!aS@2zX3',2,'1996-07-14','https://i.pravatar.cc/150?img=7','blue_sky99@example.com','武汉大学','蓝天之下,一切皆美好。',0,0,'2024-12-04 21:09:08','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100005,'blueberry_dream','蓝莓梦','Xl@9z!kP8r',3,'2000-05-09','https://i.pravatar.cc/150?img=15','blueberry_dream@example.com','西安交通大学','梦里都是蓝莓的甜。',0,0,'2024-12-04 21:09:50','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100006,'blueocean23','蔚蓝海洋','Ui@3#x7LpQ',1,'1997-12-20','https://i.pravatar.cc/150?img=13','blueocean23@example.com','中山大学','海水的蔚蓝是心灵的宁静。',0,0,'2024-12-04 21:08:38','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100007,'bluewhale47','蓝鲸之海','rK$6pL@9yX',2,'1994-12-31','https://i.pravatar.cc/150?img=14','bluewhale47@example.com','四川大学','在深海中寻找自由。',0,0,'2024-12-04 21:10:26','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100008,'breeze_9','清风徐来','Qw9!*lXxY3',1,'1996-03-12','https://i.pravatar.cc/150?img=8','breeze_9@example.com','四川大学','我就是风,专门来给你吹彩虹屁。',0,0,'2024-12-04 21:09:43','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100009,'catlover19','喵星人守护者','Tp9!zLq*7V',3,'1994-11-11','https://i.pravatar.cc/150?img=7','catlover19@example.com','中国人民大学','喵喵喵,守护每一个可爱的灵魂。',0,0,'2024-12-04 21:08:29','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100010,'catlover88','猫咪控','J3$wq@0Zt7',1,'1999-10-01','https://i.pravatar.cc/150?img=9','catlover88@example.com','同济大学','吸猫是我的生活态度。',0,0,'2024-12-04 21:09:10','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100023,'cherry_blossom','樱花落','Zp@4Yq8#lx',2,'2000-03-15','https://i.pravatar.cc/150?img=22','cherry_blossom@example.com','同济大学','樱花飘落的季节,最美。',0,0,'2024-12-04 21:10:00','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100024,'cherry_blossom21','樱花飘落','H5@yN@2pWx',2,'1998-05-29','https://i.pravatar.cc/150?img=20','cherry_blossom21@example.com','湖南大学','每一片樱花都是一个故事。',0,0,'2024-12-04 21:09:28','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100025,'cherryblossom99','樱花雨','Qz@5#x8TlW',2,'2001-04-17','https://i.pravatar.cc/150?img=20','cherryblossom99@example.com','华南理工大学','一场樱花雨,落在心田。',0,0,'2024-12-04 21:08:42','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100026,'chocofox99','巧克力狐狸','Yp!5#cZ8sL',2,'2000-03-29','https://i.pravatar.cc/150?img=12','chocofox99@example.com','西安交通大学','甜甜的巧克力,狡猾的狐狸。',0,0,'2024-12-04 21:08:35','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100027,'cleingxls','xxxxx','$2a$10$iiJteQ.4ZBrFrXIz6h33AeUnyP96Wf.EKqNJYmXFqHttPlukeF0ii',3,NULL,NULL,NULL,NULL,NULL,0,0,NULL,'2024-12-23 16:10:07','2024-12-27 09:44:03'),(100028,'coffee_lover3','咖啡成瘾','Y9!pT@7RxK',1,'1997-01-09','https://i.pravatar.cc/150?img=13','coffee_lover3@example.com','华东师范大学','每天一杯,灵感不睡。',0,0,'2024-12-04 21:09:18','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100029,'coffee_time','咖啡时间','Kp@8xYw9#z',1,'1996-12-18','https://i.pravatar.cc/150?img=16','coffee_time@example.com','重庆大学','一杯咖啡,一点惬意。',0,0,'2024-12-04 21:09:51','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100030,'crazypanda98','疯熊猫','vD@7z!bQ4c',1,'1998-07-20','https://i.pravatar.cc/150?img=12','crazypanda98@example.com','吉林大学','疯得可爱,吃得自在。',0,0,'2024-12-04 21:10:24','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100031,'crystal_snow','晶莹雪','Zx9@kL8!pY',3,'2000-12-24','https://i.pravatar.cc/150?img=28','crystal_snow@example.com','贵州大学','晶莹剔透,纯洁无暇。',0,0,'2024-12-04 21:10:58','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100032,'cute_bomb','可爱炸弹','Zp7$*vW3tR',1,'1995-11-11','https://i.pravatar.cc/150?img=5','cute_bomb@example.com','浙江大学','萌到爆炸,小心引爆!',0,0,'2024-12-04 21:09:38','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100033,'dancingleaf','舞动的叶子','fK#5t@Q8yZ',2,'1999-05-25','https://i.pravatar.cc/150?img=21','dancingleaf@example.com','北京师范大学','随风起舞,洒脱自由。',0,0,'2024-12-04 21:10:16','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100034,'dandelion31','蒲公英的梦','E4@pX!7vQr',3,'1994-07-03','https://i.pravatar.cc/150?img=23','dandelion31@example.com','福州大学','随风而行,落地生根。',0,0,'2024-12-04 21:09:30','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100035,'dawangxunshan666','大王叫我来巡山','666666',3,'2001-12-03','https://i.pravatar.cc/150?img=5','dawangxunshan88@example.com','深圳大学','大王叫我来巡山,我把人间看一看。',0,1,'2024-12-04 21:15:36','2024-12-04 21:15:37','2024-12-27 09:44:03'),(100036,'dragonfly22','蜻蜓点水','Vq@4#y6PzX',2,'1996-10-07','https://i.pravatar.cc/150?img=18','dragonfly22@example.com','湖南大学','偶尔停歇,享受生活的美好。',0,0,'2024-12-04 21:08:57','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100037,'dream_catcher','追梦人','Qk9@8Yz#pL',3,'1998-11-01','https://i.pravatar.cc/150?img=19','dream_catcher@example.com','哈尔滨工业大学','梦想总会实现。',0,0,'2024-12-04 21:09:53','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100038,'dream_walker','梦行者','Wk!8xL9@pq',1,'1999-03-09','https://i.pravatar.cc/150?img=44','dream_walker@example.com','四川师范大学','追随梦的脚步,永不停歇。',0,0,'2024-12-04 21:10:39','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100039,'dreamcatcher56','捕梦达人','T2&x@8Lz1K',3,'1992-11-12','https://i.pravatar.cc/150?img=6','dreamcatcher56@example.com','南京大学','每晚捕捉不一样的梦。',0,0,'2024-12-04 21:09:06','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100040,'dreamcatcher8','捕梦人','zP&8cL#4rN',3,'1997-03-07','https://i.pravatar.cc/150?img=7','dreamcatcher8@example.com','浙江大学','梦会成真,只要你敢追。',0,0,'2024-12-04 21:10:36','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100041,'dreamcatcher88','捕梦人','Ls@2#q9GpX',3,'1995-10-30','https://i.pravatar.cc/150?img=10','dreamcatcher88@example.com','北京师范大学','夜深了,梦会被我捕捉到。',0,0,'2024-12-04 21:08:33','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100042,'dreamer12','月半弯','uYs72&@c0aZ',3,'1995-06-15','https://i.pravatar.cc/150?img=1','moonlight_9527@example.com','北京大学','月亮弯了,肚子也圆了。',0,0,'2024-12-04 21:09:34','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100043,'firebird65','火凤涅槃','wX!8k@Q3nV',1,'1995-06-02','https://i.pravatar.cc/150?img=15','firebird65@example.com','山东大学','从灰烬中重生,不惧风雨。',0,0,'2024-12-04 21:10:27','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100044,'firefly_light','萤火之光','Qk@8pLx#9w',2,'1996-07-30','https://i.pravatar.cc/150?img=24','firefly_light@example.com','东北大学','微光虽小,也能点亮黑暗。',0,0,'2024-12-04 21:10:51','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100045,'firefly87','萤火虫之光','Zr@2#p9WtV',1,'1995-06-21','https://i.pravatar.cc/150?img=16','firefly87@example.com','山东大学','微小的光,也能点亮夜空。',0,0,'2024-12-04 21:08:51','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100046,'forest_dream5','森林之梦','M1@qZ8!wTx',2,'2000-04-04','https://i.pravatar.cc/150?img=14','forest_dream5@example.com','四川大学','愿所有梦都在森林里醒来。',0,0,'2024-12-04 21:09:21','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100047,'forest_elf','森林精灵','Px!k9@Yw2l',2,'1996-06-21','https://i.pravatar.cc/150?img=20','forest_elf@example.com','南京农业大学','大自然的怀抱最温暖。',0,0,'2024-12-04 21:09:56','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100048,'forestspirit','森林精灵','kL!9t@vQ5n',1,'2001-04-08','https://i.pravatar.cc/150?img=11','forestspirit@example.com','哈尔滨工业大学','守护森林,守护心灵的宁静。',0,0,'2024-12-04 21:10:23','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100049,'frost_rose','霜玫瑰','Yp@9kL8!zQ',2,'1999-01-17','https://i.pravatar.cc/150?img=25','frost_rose@example.com','福州大学','寒冬中绽放的玫瑰,最为坚强。',0,0,'2024-12-04 21:10:50','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100050,'funny_guy','沙雕本雕','Tp&xY12#lD',1,'2001-02-25','https://i.pravatar.cc/150?img=7','funny_guy@example.com','武汉大学','不怕尴尬,尴尬的怕我。',0,0,'2024-12-04 21:09:40','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100051,'gentlebreeze','轻柔微风','nP@9yK#4rT',2,'1997-01-15','https://i.pravatar.cc/150?img=16','gentlebreeze@example.com','南开大学','风轻拂,心自由。',0,0,'2024-12-04 21:10:18','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100052,'golden_hour','黄金时刻','Xl!8kPq9@w',1,'1997-02-14','https://i.pravatar.cc/150?img=41','golden_hour@example.com','郑州大学','夕阳西下,黄金时刻闪耀。',0,0,'2024-12-04 21:10:44','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100053,'goldenfish','金鱼也疯狂','qT$5yN@7zK',2,'1996-03-14','https://i.pravatar.cc/150?img=18','goldenfish@example.com','厦门大学','游在水中,心向远方。',0,0,'2024-12-04 21:10:18','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100054,'goldfish33','金鱼记忆','Yx@1#c9LkQ',1,'1997-11-22','https://i.pravatar.cc/150?img=19','goldfish33@example.com','四川大学','记得美好,忘记忧愁。',0,0,'2024-12-04 21:08:54','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100055,'grapefruit_cat','柚子味的猫','Rm2&!pWz9X',3,'1997-07-07','https://i.pravatar.cc/150?img=10','grapefruit_cat@example.com','厦门大学','我是猫咪,带点柚子的酸。',0,0,'2024-12-04 21:09:45','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100056,'green_tea_lover','绿茶爱好者','Xl@9pQk8!z',1,'1995-04-14','https://i.pravatar.cc/150?img=29','green_tea@example.com','云南大学','一杯绿茶,回味无穷。',0,0,'2024-12-04 21:10:57','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100057,'greenleaf66','叶子青青','Mk@9#oL4pq',2,'1996-07-23','https://i.pravatar.cc/150?img=6','greenleaf66@example.com','浙江大学','生活如绿叶,充满希望。',0,0,'2024-12-04 21:08:27','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100058,'greenleaf77','叶子青青','$2a$10$JqducY4l7tKjCB7upNzMHuuZNiTxSt4TI5hYTmQVZm6QXnKhOzRDq',3,NULL,NULL,NULL,NULL,NULL,0,0,'2024-12-26 11:55:44','2024-12-26 11:50:08','2024-12-27 09:44:03'),(100059,'happy_goose','快乐鹅','Mk#8pWl@9x',1,'1999-04-30','https://i.pravatar.cc/150?img=21','happy_goose@example.com','天津大学','嘎嘎嘎,开心每一天。',0,0,'2024-12-04 21:09:58','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100060,'happy_penguin','开心企鹅','Mz@l8#Yq0L',2,'1999-08-13','https://i.pravatar.cc/150?img=12','happy_penguin@example.com','吉林大学','快乐每一天,烦恼远离我。',0,0,'2024-12-04 21:09:47','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100061,'happycat77','快乐猫咪','jT!4x@pW7y',2,'1995-05-19','https://i.pravatar.cc/150?img=8','happycat77@example.com','中国人民大学','喵呜,开心每一天。',0,0,'2024-12-04 21:10:31','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100062,'happyfish12','快乐小鱼','P7@vX!l9mR',2,'2001-02-25','https://i.pravatar.cc/150?img=8','happyfish12@example.com','华中科技大学','自由自在,快乐游动。',0,0,'2024-12-04 21:09:09','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100063,'iron_princess','钢铁小公举','Lz@qP84&xT',1,'1999-05-14','https://i.pravatar.cc/150?img=6','iron_princess@example.com','南京大学','外表刚硬,内心公主。',0,0,'2024-12-04 21:09:39','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100064,'jdk','charon','$2a$10$oOfNCe01C.gkaNsndrKka.NkUtQ1621ILtF/OeytwC0wB8NLYFseW',3,NULL,NULL,NULL,NULL,NULL,0,0,NULL,'2024-12-23 15:29:38','2024-12-27 09:44:03'),(100065,'lazy_panda','懒熊猫','Rz#8lP@k2Y',2,'1995-03-05','https://i.pravatar.cc/150?img=17','lazy_panda@example.com','北京师范大学','每天都想躺着晒太阳。',0,0,'2024-12-04 21:09:52','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100066,'lazy_panda77','懒熊猫','B5@tQ!3vP9',1,'1993-08-05','https://i.pravatar.cc/150?img=11','lazy_panda77@example.com','哈尔滨工业大学','除了吃和睡,还有梦想。',0,0,'2024-12-04 21:09:14','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100067,'lemon88','柠檬不萌','P8!wD*93ql',2,'1998-04-22','https://i.pravatar.cc/150?img=2','lemon_tree@example.com','清华大学','酸酸的外表下有颗甜甜的心。',0,0,'2024-12-04 21:09:35','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100068,'little_deer','小鹿斑比','Wk!9xPz@8l',1,'1998-06-22','https://i.pravatar.cc/150?img=26','little_deer@example.com','湖南大学','心中有森林,奔跑不迷失。',0,0,'2024-12-04 21:10:59','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100069,'little_tiger','小老虎','Yz!kWq8@4l',1,'1998-02-19','https://i.pravatar.cc/150?img=14','little_tiger@example.com','华南理工大学','别惹我,小心被咬哦。',0,0,'2024-12-04 21:09:49','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100070,'lonely_wolf','孤狼','Ql@8pWk#9x',1,'1998-07-23','https://i.pravatar.cc/150?img=39','lonely_wolf@example.com','西南大学','孤独是狼的自由。',0,0,'2024-12-04 21:10:49','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100071,'lonelywolf12','孤独狼','sB!5yR@7tZ',3,'1993-09-09','https://i.pravatar.cc/150?img=13','lonelywolf12@example.com','重庆大学','孤独,但不孤单。',0,0,'2024-12-04 21:10:25','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100072,'milk_tea007','奶茶加冰','Kc9!x@wR4l',2,'1997-09-30','https://i.pravatar.cc/150?img=4','milktea_ice@example.com','复旦大学','心凉凉,但奶茶要加冰!',0,0,'2024-12-04 21:09:37','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100073,'misty_dawn','晨曦薄雾','Xp8@9kQ#lw',3,'1999-06-03','https://i.pravatar.cc/150?img=35','misty_dawn@example.com','华中农业大学','晨曦的薄雾,如梦如幻。',0,0,'2024-12-04 21:11:05','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100074,'misty_hills','雾绕山岗','Mk9@8pL#xw',2,'1998-12-22','https://i.pravatar.cc/150?img=43','misty_hills@example.com','广西师范大学','薄雾缭绕,山岗隐约如梦。',0,0,'2024-12-04 21:10:47','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100075,'moon_shadow','月影','Lp9@xQ8z!k',3,'1997-08-25','https://i.pravatar.cc/150?img=23','moon_shadow@example.com','南开大学','在月光下,我与影子共舞。',0,0,'2024-12-04 21:10:54','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100076,'moonlight39','夜色静谧','xA#2bP!9Zy',2,'1993-02-10','https://i.pravatar.cc/150?img=6','moonlight39@example.com','武汉大学','夜色如水,心静如月。',0,0,'2024-12-04 21:10:38','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100077,'moonlight77','月光倾城','F3$xQ!8pZv',3,'1997-10-12','https://i.pravatar.cc/150?img=19','moonlight77@example.com','东南大学','月光下,一切都美好且安静。',0,0,'2024-12-04 21:09:25','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100078,'moonrabbit77','月兔吃胡萝卜','Kq!8#xY3Pm',2,'2001-01-15','https://i.pravatar.cc/150?img=9','moonrabbit77@example.com','华中科技大学','月亮下的胡萝卜最好吃。',0,0,'2024-12-04 21:08:31','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100079,'morning_glory','牵牛花','Pk!8xQ9@lL',1,'1997-03-28','https://i.pravatar.cc/150?img=32','morning_glory@example.com','河北大学','清晨的阳光里,向上攀爬。',0,0,'2024-12-04 21:11:03','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100080,'mysticsky','神秘天空','yW!9c@P7tR',3,'1997-07-07','https://i.pravatar.cc/150?img=20','mysticsky@example.com','华南理工大学','天空下,隐藏着无数秘密。',0,0,'2024-12-04 21:10:14','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100081,'night_owl25','夜猫子','V4!kX@7pRz',1,'1994-09-15','https://i.pravatar.cc/150?img=15','night_owl25@example.com','北京师范大学','白天睡觉,晚上捕猎灵感。',0,0,'2024-12-04 21:09:22','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100082,'nightingale','夜莺','Pk@8xL9!zw',2,'1997-11-06','https://i.pravatar.cc/150?img=27','nightingale_song@example.com','广西大学','夜晚的歌声,献给孤独的人。',0,0,'2024-12-04 21:10:55','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100083,'nightingale45','夜莺歌唱','Z2!qK@9vXw',2,'2001-08-16','https://i.pravatar.cc/150?img=22','nightingale45@example.com','浙江工业大学','每个夜晚都有一曲动听的歌。',0,0,'2024-12-04 21:09:32','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100084,'nightowl21','夜猫子','Pt@6#r3WxL',3,'1998-02-11','https://i.pravatar.cc/150?img=17','nightowl21@example.com','吉林大学','夜深人静,我还在思考。',0,0,'2024-12-04 21:08:53','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100085,'ocean_breeze','海洋微风','Wz@9kL8!px',3,'1999-10-12','https://i.pravatar.cc/150?img=33','ocean_breeze@example.com','海南大学','带着大海的气息,轻轻拂过你。',0,0,'2024-12-04 21:11:02','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100086,'panda_girl20','熊猫少女','W9$k@R3vTx',2,'1999-06-30','https://i.pravatar.cc/150?img=17','panda_girl20@example.com','吉林大学','懒得可爱,爱得懒散。',0,0,'2024-12-04 21:09:23','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100087,'rainbow_fan','星星守护者','Yk#pLq9*8X',1,'1996-01-05','https://i.pravatar.cc/150?img=11','star_guardian@example.com','华中科技大学','守护每一个闪亮的你。',0,0,'2024-12-04 21:09:46','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100088,'rainbow34','彩虹糖','uG$5c#Q7jW',2,'1999-11-01','https://i.pravatar.cc/150?img=10','rainbow34@example.com','中山大学','七彩生活,甜中带酸。',0,0,'2024-12-04 21:10:30','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100089,'rainyday55','雨天思绪','Ko!6#q9MtX',3,'1999-08-18','https://i.pravatar.cc/150?img=14','rainyday55@example.com','厦门大学','雨天的心情总是格外多情。',0,0,'2024-12-04 21:08:37','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100090,'red_rose66','红玫瑰','Q2!yNp@5Wx',2,'1995-03-18','https://i.pravatar.cc/150?img=10','red_rose66@example.com','南开大学','玫瑰有刺,但心中有爱。',0,0,'2024-12-04 21:09:11','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100091,'sakura_blossom','樱花盛开时','Xa@9#pT6oL',2,'1994-04-05','https://i.pravatar.cc/150?img=15','sakura_blossom@example.com','重庆大学','樱花开了,春天来了。',0,0,'2024-12-04 21:08:49','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100092,'silver_breeze','银色微风','Xp8@9kQ#wl',3,'2000-07-17','https://i.pravatar.cc/150?img=45','silver_breeze@example.com','贵州师范大学','轻盈的风,吹散烦恼。',0,0,'2024-12-04 21:10:40','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100093,'silvermoon56','银月的光辉','Pm@4#x8LzQ',1,'1996-12-13','https://i.pravatar.cc/150?img=24','silvermoon56@example.com','华东师范大学','银色月光,照亮前方的路。',0,0,'2024-12-04 21:09:03','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100094,'silverstar','银色星辰','rX!7y@P3tK',1,'1995-12-12','https://i.pravatar.cc/150?img=23','silverstar@example.com','中央财经大学','星辰璀璨,心中有梦。',0,0,'2024-12-04 21:10:03','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100095,'skywalker32','逐梦星空','Ax!4m8L$yB',1,'1992-03-17','https://i.pravatar.cc/150?img=5','skywalker32@example.com','南京大学','追逐梦想,星空是我的家。',0,0,'2024-12-04 21:10:05','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100096,'snowflake','雪花纷飞','Zk9@8xLp!w',2,'2000-01-29','https://i.pravatar.cc/150?img=40','snowflake@example.com','兰州大学','一片片雪花,汇聚冬天的美丽。',0,0,'2024-12-04 21:10:43','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100097,'snowfox','雪地小狐','bR!4z@L9xV',1,'2000-10-10','https://i.pravatar.cc/150?img=17','snowfox@example.com','华中科技大学','雪地中,留下灵动的足迹。',0,0,'2024-12-04 21:10:17','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100098,'soft_bear','小熊软糖','Wq#84Pz&xD',2,'1998-10-10','https://i.pravatar.cc/150?img=9','soft_bear@example.com','中山大学','甜甜的外表,软软的内心。',0,0,'2024-12-04 21:09:44','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100099,'soilgirl2020','吃土少女','xZ@93qlT#p',2,'2000-12-01','https://i.pravatar.cc/150?img=3','soil_girl@example.com','上海交通大学','钱包很瘦,但我心态超胖。',0,0,'2024-12-04 21:09:36','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100100,'star_gazer19','星空守望者','L8$x@2QeW4',3,'1998-12-11','https://i.pravatar.cc/150?img=12','star_gazer19@example.com','厦门大学','抬头看星星,低头看生活。',0,0,'2024-12-04 21:09:16','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100101,'stargazer23','星空下的凝望','pK*7r@Y9vL',1,'1996-08-13','https://i.pravatar.cc/150?img=9','stargazer23@example.com','西安交通大学','星光不问赶路人。',0,0,'2024-12-04 21:10:29','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100102,'starlight_echo','星光回响','Mk9@8lP#xw',2,'1998-08-05','https://i.pravatar.cc/150?img=34','starlight_echo@example.com','江西大学','星光不灭,回响在夜空中。',0,0,'2024-12-04 21:11:04','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100103,'starlight12','星辰闪烁','Rj@7#z9Klw',1,'1993-09-12','https://i.pravatar.cc/150?img=11','starlight12@example.com','天津大学','星辰闪烁,只为追梦的你。',0,0,'2024-12-04 21:08:36','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100104,'sunflower32','向日葵','K6@v!Q3lPt',2,'1995-11-05','https://i.pravatar.cc/150?img=16','sunflower32@example.com','山东大学','永远向着阳光,追逐希望。',0,0,'2024-12-04 21:09:24','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100105,'sunflower77','向日葵的微笑','Kl@8#c5PqV',2,'1999-07-30','https://i.pravatar.cc/150?img=23','sunflower77@example.com','南京航空航天大学','笑对生活,像向日葵一样。',0,0,'2024-12-04 21:09:04','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100106,'sunflower99','向日葵女孩','Hk&xR92l@w',2,'1997-11-25','https://i.pravatar.cc/150?img=13','sunflower_girl@example.com','山东大学','向阳而生,笑对人生。',0,0,'2024-12-04 21:09:48','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100107,'sunnyday72','阳光正好','dR!8@t7QkL',1,'1994-11-21','https://i.pravatar.cc/150?img=5','sunnyday72@example.com','南京大学','生活如阳光,温暖又灿烂。',0,0,'2024-12-04 21:10:35','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100108,'sunshine_boy','阳光男孩','Wl@8kPq#3X',1,'1997-09-12','https://i.pravatar.cc/150?img=18','sunshine_boy@example.com','浙江工业大学','阳光灿烂的每一天。',0,0,'2024-12-04 21:09:54','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100109,'sunshine34','阳光宅男','A9$h4x@P3q',1,'1994-05-20','https://i.pravatar.cc/150?img=5','sunshine34@example.com','浙江大学','阳光下的快乐宅。',0,0,'2024-12-04 21:09:05','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100110,'sunshine45','阳光下的影子','Vg@1pZ#6rJ',1,'1998-05-06','https://i.pravatar.cc/150?img=8','sunshine45@example.com','武汉大学','有阳光的地方,就有我的影子。',0,0,'2024-12-04 21:08:30','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100111,'tea_time99','茶余饭后','U8$x@5pQkR',1,'1993-12-02','https://i.pravatar.cc/150?img=21','tea_time99@example.com','天津大学','茶香四溢,心事淡然。',0,0,'2024-12-04 21:09:29','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100112,'thunderstrike','雷霆一击','Wx@7#z5LpV',1,'1993-08-09','https://i.pravatar.cc/150?img=21','thunderstrike@example.com','哈尔滨工业大学','雷霆的力量,无畏前行。',0,0,'2024-12-04 21:09:02','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100113,'twilight_song','暮光之歌','Pk@8zWl9#x',3,'1996-08-31','https://i.pravatar.cc/150?img=42','twilight_song@example.com','内蒙古大学','在暮光中歌唱,诉说无尽的思念。',0,0,'2024-12-04 21:10:46','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100114,'whisper_willow','柳树细语','Wl@9xPz8#q',2,'1997-04-25','https://i.pravatar.cc/150?img=36','whisper_willow@example.com','安徽大学','轻风拂柳,细语呢喃。',0,0,'2024-12-04 21:11:07','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100115,'whitecloud88','白云悠悠','Nk@3#o9GxP',3,'1995-01-25','https://i.pravatar.cc/150?img=22','whitecloud88@example.com','东南大学','白云飘过,心情也放晴。',0,0,'2024-12-04 21:08:59','2024-12-04 21:07:45','2024-12-27 09:44:03'),(100116,'wildflower','野花盛开','Kp9@8lX!wq',3,'1995-05-15','https://i.pravatar.cc/150?img=38','wildflower@example.com','江西师范大学','野花盛开,自由而无畏。',0,0,'2024-12-04 21:11:08','2024-12-04 20:59:50','2024-12-27 09:44:03'),(100117,'wildlion88','荒野雄狮','tG!7p@K5vL',1,'1998-11-18','https://i.pravatar.cc/150?img=19','wildlion88@example.com','同济大学','心有猛虎,细嗅蔷薇。',0,0,'2024-12-04 21:10:08','2024-12-04 21:06:26','2024-12-27 09:44:03'),(100118,'wind_chaser55','追风少年','N7!vX@2lQw',1,'1996-03-21','https://i.pravatar.cc/150?img=18','wind_chaser55@example.com','重庆大学','追风的少年不怕跌倒。',0,0,'2024-12-04 21:09:26','2024-12-04 21:03:51','2024-12-27 09:44:03'),(100119,'wind_dancer','风之舞者','Qk@9xL8!pY',3,'1998-05-20','https://i.pravatar.cc/150?img=31','wind_dancer@example.com','新疆大学','随风而舞,自由自在。',0,0,'2024-12-04 21:11:01','2024-12-04 20:59:50','2024-12-27 09:44:03'); +/*!40000 ALTER TABLE `user` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2025-01-20 12:08:41 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..81c12cc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "kama-note-tech", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}