├── .gitignore ├── LICENSE ├── README.md ├── task_before.sh ├── xxoo.js └── xxooPoolServer ├── Dockerfile ├── README.md ├── admin └── admin.php ├── config.php ├── data.php ├── db.sql ├── env.sh ├── lib ├── adminer │ ├── adminer-4.8.1-en.php │ ├── plugin-login-password-less.php │ └── plugin.php ├── medoo.php └── util.php ├── nginx.conf ├── www ├── README.md ├── activity.php ├── admin.php ├── index.php └── startup.php └── xxoo.yml /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xxoo助力池 2 | 3 | 本助力池特性 4 | 5 | - 自动解析并上传助力码 6 | - 多账户自助 7 | - 定向助力 8 | - 助力池助力 9 | - 可与其他助力池共存 10 | - 可搭建自己的服务池 11 | 12 | [加入telegram频道点我](https://t.me/xxoo_pool)
13 | https://t.me/xxoo_pool 14 | 15 | 说明:
16 | 17 | 定向互助: 比如你想优先助力某用户,然后再助力池中的其他用户 18 | 所以,当你发现没人给你助力时,邀请几个朋友定向助力你吧!!! 19 | 20 | 21 | 22 | 实现原理: 23 | xxoo.js运行时,会解析jd_get_share_code脚本生成的日志文件,从日志文件中获 24 | 取助力码(所以需要保证jd_get_share_code脚本的正常运行),然后将助力码上传到服务 25 | 池,服务池会根据一定策略下发助力码(上传的是自己的,下发的包含池中其他用户上传的助力码), 26 | 然后定时任务执行时,会自动先执行task_before.sh脚本,此脚本会导入服务器下发的助 27 | 力码到环境变量中 28 | 29 | ## 助力规则 30 | 31 | - 优先助力多账户之间的互助 32 | - 然后助力配置的定向互助 33 | - 然后才助力池中的用户 34 | 35 | 提示:本仓库和JDHelloWorld大佬的助力池可共存,共存时,优先采用本仓库的助力逻辑 36 | 37 | 38 | # 食用方法 39 | 40 | - 1.青龙面板->对比工具 右上角的当前文件选择 task_before.sh, 追加以下内容到脚本中 41 | ``` 42 | ## ======重要提示: 如果是追加到已经存在的 task_before.sh 中,则只拷贝以下内容 43 | xxooLogDir="${dir_log}/raw_main_xxoo" 44 | if [[ $(ls $xxooLogDir) ]]; then 45 | latest_log=$(ls -r $xxooLogDir | head -1) 46 | . $xxooLogDir/$latest_log 47 | echo "## task before $xxooLogDir/$latest_log" 48 | echo "## $GENERATE_INFO"; 49 | fi 50 | ``` 51 | 52 | - 2.青龙面板->环境变量 新增以下环境变量 53 | 54 | 55 | 名称: XXOO_HOST 必填 56 | 值: https://sharec.siber.cn:889 57 | 58 | 当你不想用默认助力池时,填入你要用的xxoo助力池地址,默认为作者提供的地址 59 | 当你要用其他人自建服务池时,请修改XXOO_HOST值 60 | 61 | 62 | 名称: XXOO_TOKEN 63 | 值: dev_token 必填 64 | 65 | 接入服务池的验证token,本人自建的服务池提供一个token 66 | 当你使用其他人的自建服池或者提示数量限制时,请向服务池所有者反馈 67 | 68 | 69 | 名称: XXOO_FOR 选填 70 | 值: pt_pin1@pt_pin2 71 | 72 | 填你想要助力的人,具体请参考定向助力说明 73 | 74 | 75 | 名称: XXOO_CLOSE_SELF 76 | 值: true 选填 77 | 78 | 是否关闭多账号自助,关闭为true,不关为false,不填为不关 79 | 多个小号养大号但是小号本身不需要被助力时使用此开关 80 | 注意:很多脚本默认开启多账户自助,此种情况本脚本无法控制 81 | 82 | 83 | 名称: XXOO_RANDOM 84 | 值: true 选填 85 | 86 | 是否从池中随机获取助力码进行助力,为false时才关闭,其他任 87 | 意值都会随机助力 88 | 89 | 90 | - 3.青龙面板->定时任务->添加定时任务 91 | 92 | 93 | 名称:xxoo更新 94 | 命令:ql raw https://raw.githubusercontent.com/q398044828/xxoo_share_pool/main/xxoo.js 95 | 定时规则:0 0 */1 * * 96 | 97 | 配置好后立即执行一遍,会新增一个定时任务:xxoo互助池,此任务即为助力池脚本 98 | 99 | 100 | - 4.接入是否成功检测: 101 | 102 | 上报成功且下发助力码成功时,xxoo脚本日志内容应该类似于以下内容 103 | 104 |
105 | 点我展开 106 |
107 | ## 开始执行... 2021-11-13 23:55:07
108 | 
109 | ##  task before  /ql/log/xxoo/2021-11-13-23-55-07.log
110 | ##  
111 | :<自动判断 jd_get_share_code 日志所在目录 开始
115 | ========>自动判断get_share_code 日志所在目录 shufflewzc_faker2_jd_get_share_code
116 | 
117 | 从
118 | /ql/log/shufflewzc_faker2_jd_get_share_code
119 | 目录解析日志最新获取的互助码
120 | 
121 | =====以下json数据为从原版的jd_get_share_code脚本的日志中解析获取到的互助码,如果没
122 | 有数据,请尝试先执行jd_get_share_code后再执行xxoo任务
123 | {
124 | jd_654c2078e51f7: {
125 | FRUITSHARECODES: 'd8d67490c41f42348ba589fd18c50edb',
126 | PETSHARECODES: 'MTAxNzIyNTU1NDAwMDAwMDA1MTMxMjIyMw==',
127 | PLANT_BEAN_SHARECODES: 'olmijoxgmjutyeukiu3el2x5tr6uxjor76jutla',
128 | DDFACTORY_SHARECODES: 'T0225KkcRhsdplbXJxKhkfZccwCjVWnYaS5kRrbA',
129 | DREAM_FACTORY_SHARE_CODES: 'cAzv4fnSw852dboodamfKQ==',
130 | JDSGMH_SHARECODES: 'T0225KkcRhsdplbXJxKhkfZccwCjVQmoaT5kRrbA',
131 | JD_CASH_SHARECODES: 'eU9Yaum2Nf4m9maAznJF0Q'
132 | },
133 | jd_76f67073b047f: {
134 | FRUITSHARECODES: 'ead06cd23c884a69b78e90e656da64b0',
135 | PETSHARECODES: 'MTEyNzEzMjc0MDAwMDAwMDYwMTM5MDEz',
136 | PLANT_BEAN_SHARECODES: '4npkonnsy7xi2mrf7ps6m4sy4hcm6ffzcnrmzli',
137 | DDFACTORY_SHARECODES: 'T0225KkcRxhP81PXJxmmlPMNIgCjVWnYaS5kRrbA',
138 | JDSGMH_SHARECODES: 'T0225KkcRxhP81PXJxmmlPMNIgCjVQmoaT5kRrbA',
139 | JD_CASH_SHARECODES: 'eU9Ya-rkYPsm9m2Hy3cUgA'
140 | }
141 | }
142 | EOF
143 | ##====================  xxoo池响应(服务器下发助力码)   ======
144 | export FRUITSHARECODES="99@88"
145 | export PETSHARECODES="fghfgh@456drg"
146 | export PLANT_BEAN_SHARECODES="123123@3245345"
147 | export DDFACTORY_SHARECODES="asdf@dfgsd"
148 | export DREAM_FACTORY_SHARE_CODES="1@2"
149 | export JDSGMH_SHARECODES="aa@bb"
150 | export JD_CASH_SHARECODES="11@22@33"
151 | export GENERATE_INFO="xxoo助力池同步时间===========》 2021年11月13日 23:55:08"
152 | 
153 | 
154 | ## 执行结束... 2021-11-13 23:55:08 耗时 1 秒
155 | 
156 |     
157 |
158 | 159 | # 脚本更新 160 | 161 | - 自动更新:按照食用方法配置,即可每天晚上自动更新 162 | - 手动更新:当运行日志中提示: 请更新xxoo.js版本 时,请手动执行定时任务:xxoo更新 163 | 164 | # 定向互助说明 165 | 166 | 167 | 加了其他助力池,却只是给别人助力,没人给自己助力? 168 | 助力被白嫖? 169 | 170 | 不存在的!!! 171 | 172 | 本助力池支持定向助力,可要求一群好友,你帮我助力,我帮你助力! 173 | 174 | 方式1: 175 | 配置环境变量: XXOO_FOR = pt_pin1@pt_pin2@pt_pin3 176 | 说明:本容器所有账号都助力这三个pt_pin用户,使用@分隔 177 | 方式2: 178 | 配置环境变量: XXOO_FOR = pt_pin1;pt_pin2@pt_pin3;pt_pin4 179 | 说明:第一个账号助力pt_pin1, 180 | 第二个账号助力pt_pin2 和 pt_pin3, 181 | 第三个账号助力pt_pin4 182 | 183 | pt_pin: jd用户的pt_pin参数值 184 | 185 | 提示: 由于每个人可助力别人的次数有限,当你和别人互相定向时,如果对方定向助力数 186 | 太多,且定向助力你的顺序比较靠后,那么就不能保证对方能给你助力, 187 | 且对方还可能是多账号自助,能用来助力别人的次数可能不多 188 | 189 | 190 | 191 | 192 | # 自建服务池:如果你想要自建的话 193 | 194 | 请参考 xxooPoolServer目录内的README 195 | 196 | -------------------------------------------------------------------------------- /task_before.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ## ======重要提示: 如果是追加到已经存在的 task_before.sh 中,则只拷贝以下内容 4 | xxooLogDir="${dir_log}/raw_main_xxoo" 5 | if [[ $(ls $xxooLogDir) ]]; then 6 | latest_log=$(ls -r $xxooLogDir | head -1) 7 | . $xxooLogDir/$latest_log 8 | echo "## task before $xxooLogDir/$latest_log" 9 | echo "## $GENERATE_INFO"; 10 | fi 11 | -------------------------------------------------------------------------------- /xxoo.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | ================Loon============== 5 | [Script] 6 | cron "1,2,3 0 * * *" script-path=xxoo.js, tag=xxoo助力池s 7 | */ 8 | var version='1.0.5'; 9 | var fs = require("fs"); 10 | console.log(":<>16)+(r>>16)+(t>>16)<<16|65535&t}function t(n,r){return n<>>32-r}function u(n,u,e,o,c,f){return r(t(r(r(u,n),r(o,f)),c),e)}function e(n,r,t,e,o,c,f){return u(r&t|~r&e,n,r,o,c,f)}function o(n,r,t,e,o,c,f){return u(r&e|t&~e,n,r,o,c,f)}function c(n,r,t,e,o,c,f){return u(r^t^e,n,r,o,c,f)}function f(n,r,t,e,o,c,f){return u(t^(r|~e),n,r,o,c,f)}function i(n,t){n[t>>5]|=128<>>9<<4)]=t;var u,i,a,h,g,l=1732584193,d=-271733879,v=-1732584194,C=271733878;for(u=0;u>5]>>>r%32&255);return t}function h(n){var r,t=[];for(t[(n.length>>2)-1]=void 0,r=0;r>5]|=(255&n.charCodeAt(r/8))<16&&(e=i(e,8*n.length)),t=0;t<16;t+=1)o[t]=909522486^e[t],c[t]=1549556828^e[t];return u=i(o.concat(h(r)),512+8*r.length),a(i(c.concat(u),640))}function d(n){var r,t,u="";for(t=0;t>>4&15)+"0123456789abcdef".charAt(15&r);return u}function v(n){return unescape(encodeURIComponent(n))}function C(n){return g(v(n))}function A(n){return d(C(n))}function m(n,r){return l(v(n),v(r))}function s(n,r){return d(m(n,r))}function b(n,r,t){return r?t?m(r,n):s(r,n):t?C(n):A(n)}$.md5=b}(); 16 | if ($.isNode()) { 17 | Object.keys(jdCookieNode).forEach((item) => { 18 | cookiesArr.push(jdCookieNode[item]) 19 | }) 20 | } else { 21 | cookiesArr = [$.getdata('CookieJD'), $.getdata('CookieJD2'), ...jsonParse($.getdata('CookiesJD') || "[]").map(item => item.cookie)].filter(item => !!item); 22 | } 23 | var ptPins = []; 24 | for (let i = 0; i < cookiesArr.length; i++) { 25 | ptPins[i] = null; 26 | if (cookiesArr[i]) { 27 | cookie = cookiesArr[i]; 28 | var ptPin = decodeURIComponent(cookie.match(/pt_pin=([^; ]+)(?=;?)/) && cookie.match(/pt_pin=([^; ]+)(?=;?)/)[1]) 29 | ptPins[i] = ptPin; 30 | } 31 | } 32 | 33 | //jd_get_share_code.js脚本的命名映射 34 | var envsGetShareCodeJs = { 35 | "京东农场": "FRUITSHARECODES", 36 | "京东萌宠": "PETSHARECODES", 37 | "种豆得豆": "PLANT_BEAN_SHARECODES", 38 | "东东工厂": "DDFACTORY_SHARECODES", 39 | "京喜农场": "JXNC_SHARECODES", 40 | "闪购盲盒": "JDSGMH_SHARECODES", 41 | "签到领现金": "JD_CASH_SHARECODES", 42 | "京喜工厂": "DREAM_FACTORY_SHARE_CODES" 43 | }; 44 | 45 | //code.sh脚本的命名映射 46 | var envsCodeSh = { 47 | 'MyFruit': 'FRUITSHARECODES', 48 | 'MyPet': 'PETSHARECODES', 49 | 'MyBean': 'PLANT_BEAN_SHARECODES', 50 | 'MyDreamFactory': 'DREAM_FACTORY_SHARE_CODES', 51 | 'MyJdFactory': 'DDFACTORY_SHARECODES', 52 | 'MyJdzz': 'JDZZ_SHARECODES', 53 | 'MyJoy': 'JDJOY_SHARECODES', 54 | 'MyJxnc': 'JXNC_SHARECODES', 55 | 'MyBookShop': 'BOOKSHOP_SHARECODES', 56 | 'MyCash': 'JD_CASH_SHARECODES', 57 | 'MySgmh': 'JDSGMH_SHARECODES', 58 | 'MyCfd': 'JDCFD_SHARECODES', 59 | 'MyHealth': 'JDHEALTH_SHARECODES' 60 | }; 61 | 62 | /** 63 | * 读取互助码 64 | * @returns {null} 65 | */ 66 | 67 | // 从 jd_get_share_code脚本日志中获取助力码 68 | addSource("jd_get_share_code.js", function () { 69 | var getShareCodeRes = null; 70 | if (process.env.XXOO_READ_SHARE_CODE) { 71 | getShareCodeRes = getShareCodeFrom_get_share_code_js_log_ByDir( 72 | process.env.XXOO_READ_SHARE_CODE); 73 | } else { 74 | getShareCodeRes = getShareCodeFrom_get_share_code_js_log_ByAutoJudge(); 75 | } 76 | return getShareCodeRes; 77 | }); 78 | 79 | // 从 code.sh 脚本日志中获取助力码 80 | addSource("code.sh", function () { 81 | return getShareCodeFrom_code(); 82 | }); 83 | 84 | //从所有日志中解析助力码 85 | addSource('log path',function(){ 86 | return parseCodeFromLogPath(); 87 | }); 88 | 89 | 90 | var codes = readFromConfigSources(); 91 | console.log('------ 整合后的助力码------------'); 92 | console.log(codes); 93 | uploadAndGetShareCodes(codes); 94 | 95 | function parseCodeFromLogPath(){ 96 | var res={}; 97 | var logPath=`${process.env.QL_DIR}/log`; 98 | var dirs = fs.readdirSync(logPath); 99 | for (let i = 0; i < dirs.length; i++) { 100 | var logSubPath=dirs[i]; 101 | var data = getLastFileDataFromDir(logSubPath); 102 | if (data == null) { 103 | continue; 104 | } 105 | parseCodeFromString(data,res); 106 | } 107 | return res; 108 | } 109 | 110 | function parseCodeFromString(str, data) { 111 | var ptPinReg = /用户:(\S*)/g; 112 | var envNameReg = /环境变量:(\S*)/g; 113 | var codeReg = /助力码:(\S*)/g; 114 | var ptPin = regexSearch(str, ptPinReg); 115 | var env = regexSearch(str, envNameReg); 116 | var code = regexSearch(str, codeReg); 117 | 118 | if (ptPin.length > 0) { 119 | for (let i = 0; i < ptPin.length; i++) { 120 | if (!isEmpty(data) && !isEmpty(ptPin[i]) && !isEmpty(env[i]) && !isEmpty(code[i])) { 121 | putVal(data, ptPin[i].trim(), env[i].trim(), code[i].trim()); 122 | } 123 | } 124 | } 125 | } 126 | 127 | function regexSearch(str, reg) { 128 | var res = []; 129 | var i = 0; 130 | do { 131 | var r = reg.exec(str); 132 | if (r == null) { 133 | break; 134 | } 135 | res[i] = r[1].trim(); 136 | i++; 137 | } while (true) 138 | return res; 139 | } 140 | 141 | function putVal(data, x, y, val) { 142 | if (!isEmpty(x) && !isEmpty(x) && !isEmpty(y) && !isEmpty(val)) { 143 | val = val.trim(); 144 | if (val !== '') { 145 | if (!data[x]) { 146 | data[x] = {}; 147 | } 148 | data[x][y] = val; 149 | } 150 | } 151 | } 152 | 153 | function isEmpty(str) { 154 | return str == null || false || str.trim() === ''; 155 | } 156 | 157 | /** 158 | * 上传互助码并拉取互助池中的互助码s 159 | */ 160 | function uploadAndGetShareCodes(data) { 161 | if (process.env.XXOO_HOST && process.env.XXOO_TOKEN) { 162 | var host = process.env.XXOO_HOST; 163 | var token = process.env.XXOO_TOKEN; 164 | var askPtPin = process.env.XXOO_FOR; 165 | var closeSelf = process.env.XXOO_CLOSE_SELF; 166 | var random = process.env.XXOO_RANDOM; 167 | var ops = { 168 | 'url': `${host}/index.php?token=${token}&askFor=${askPtPin}&clientVersion=${version}&closeSelf=${closeSelf}&random=${random}`, 169 | 'headers': { 170 | "Content-Type": "application/json", 171 | }, 172 | 'body': JSON.stringify(data) 173 | } 174 | $.post(ops, async (err, resp, data) => { 175 | console.log("EOF"); 176 | console.log("##==================== xxoo池响应 ======") 177 | console.log('%s', data); 178 | } 179 | ); 180 | } 181 | } 182 | 183 | function addSource(desc, func) { 184 | shareCodeSources.push({'desc': desc, 'func': func}); 185 | } 186 | 187 | function readFromConfigSources() { 188 | var res = []; 189 | for (let k in shareCodeSources) { 190 | try{ 191 | var source = shareCodeSources[k]; 192 | var func = source['func']; 193 | var r = func(); 194 | source['response'] = r; 195 | res.push(r); 196 | }catch (e){} 197 | } 198 | var mergedCodes = {}; 199 | console.log("========= 获取助力码来源 ========="); 200 | for (const k in shareCodeSources) { 201 | var source = shareCodeSources[k]; 202 | console.log(` ${source['desc']}`); 203 | if (source['response']) { 204 | mergedCodes = mergeCodes(mergedCodes, source['response']); 205 | } 206 | } 207 | console.log(" "); 208 | return mergedCodes; 209 | } 210 | 211 | function mergeCodes(source1, source2) { 212 | var res = {}; 213 | for (let ptPin of ptPins) { 214 | var a = source1[ptPin]; 215 | var b = source2[ptPin]; 216 | var mergedCodes = {}; 217 | mergedCodes = Object.assign(a == undefined ? {} : a, mergedCodes); 218 | mergedCodes = Object.assign(b == undefined ? {} : b, mergedCodes,); 219 | res[ptPin] = mergedCodes; 220 | } 221 | return res; 222 | } 223 | 224 | /** 225 | * 从code.sh脚本日志中获取助力码 226 | */ 227 | function getShareCodeFrom_code() { 228 | var data = getLastFileDataFromDir('code'); 229 | if (data != null) { 230 | res={}; 231 | for (let key in envsCodeSh) { 232 | var env=envsCodeSh[key]; 233 | for (let i = 0; i < ptPins.length; i++) { 234 | var pt_pin=ptPins[i]; 235 | var code=readFrom(`${key}${i+1}\=\'`,"\'\n",data,0); 236 | if (code.start < 0) { 237 | continue; 238 | } 239 | if (!res[pt_pin]) { 240 | res[pt_pin] = {}; 241 | } 242 | res[pt_pin][env] = code.str; 243 | } 244 | } 245 | return res; 246 | } 247 | return {}; 248 | } 249 | 250 | 251 | function getShareCodeFrom_get_share_code_js_log_ByAutoJudge() { 252 | var pathName = `${process.env.QL_DIR}/log`; 253 | var dirs = fs.readdirSync(pathName); 254 | for (let i = 0; i < dirs.length; i++) { 255 | if (dirs[i].endsWith("get_share_code")) { 256 | console.log(`=>自动判断 jd_get_share_code 日志目录 257 | ${dirs[i]}\r\n`); 258 | return getShareCodeFrom_get_share_code_js_log_ByDir(dirs[i]); 259 | } 260 | } 261 | return {}; 262 | } 263 | 264 | function getShareCodeFrom_get_share_code_js_log_ByDir(dir) { 265 | var data = getLastFileDataFromDir(dir); 266 | if (data == null) { 267 | return {}; 268 | } 269 | return parseFrom_get_share_code_js_log(data); 270 | } 271 | 272 | /** 273 | * 获取给定目录里的最新文件内容 274 | * @param dir 275 | * @returns {null|*} 276 | */ 277 | function getLastFileDataFromDir(dir) { 278 | var pathName = `${process.env.QL_DIR}/log/${dir}`; 279 | if (!fs.existsSync(pathName)) { 280 | return null; 281 | } 282 | var stat = fs.statSync(pathName); 283 | if (!stat.isDirectory()) { 284 | return null; 285 | } 286 | var files = fs.readdirSync(pathName); 287 | if (files.length > 0) { 288 | var lastLog = files[files.length - 1]; 289 | var data = fs.readFileSync(`${pathName}/${lastLog}`, "utf8"); 290 | return data; 291 | } 292 | return null; 293 | } 294 | 295 | function parseFrom_get_share_code_js_log(data) { 296 | 297 | 298 | var res = {}; 299 | 300 | var i = 0; 301 | var idx = 0; 302 | /** 303 | * 看起来很low的解析方式对不对? 304 | * 对,但是我菜啊,正则不太会用!!! 305 | */ 306 | do { 307 | try { 308 | i++; 309 | var line = null; 310 | try { 311 | line = readFrom("【", "\n", data, idx); 312 | } catch (b) { 313 | break; 314 | } 315 | idx = line.end + 1; 316 | var code = readAfter("】", line.str, 0).str; 317 | var pt_pin = readFrom("(", ")", line.str, 0).str; 318 | var name = readFrom(")", "】", line.str).str; 319 | var env = envsGetShareCodeJs[name]; 320 | if (!checkChinese(code) && pt_pin != '') { 321 | if (!res[pt_pin]) { 322 | res[pt_pin] = {}; 323 | } 324 | res[pt_pin][env] = code; 325 | } 326 | } catch (e) { 327 | $.log(e); 328 | } 329 | } while (i < 99999); 330 | return res; 331 | } 332 | 333 | /** 334 | * cookie解析 335 | */ 336 | function jdCookies() { 337 | var CookieJDs = []; 338 | if (process.env.JD_COOKIE) { 339 | if (process.env.JD_COOKIE.indexOf('&') > -1) { 340 | CookieJDs = process.env.JD_COOKIE.split('&'); 341 | } else if (process.env.JD_COOKIE.indexOf('\n') > -1) { 342 | CookieJDs = process.env.JD_COOKIE.split('\n'); 343 | } else { 344 | CookieJDs = [process.env.JD_COOKIE]; 345 | } 346 | } 347 | var res = {}; 348 | CookieJDs = [...new Set(CookieJDs.filter(item => !!item))]; 349 | console.log(`\n=========共${CookieJDs.length}个京东账号Cookie=========\n`); 350 | for (let i = 0; i < CookieJDs.length; i++) { 351 | if (!CookieJDs[i].match(/pt_pin=(.+?);/) 352 | || !CookieJDs[i].match(/pt_key=(.+?);/)) { 353 | console.log( 354 | `\n提示: cookie 【${CookieJDs[i]}】填写不规范,正确格式: pt_key=xxx;pt_pin=xxx;(分号;不可少)\n`); 355 | } 356 | const index = (i + 1 === 1) ? '' : (i + 1); 357 | res['CookieJD' + index] = CookieJDs[i].trim(); 358 | } 359 | return res; 360 | } 361 | 362 | /** 363 | * 从给定的str中截取start和end之间的字符串 364 | * 不包括start和end自己 365 | * @param startIdx: 从哪个位置开始查找 366 | */ 367 | function readFrom(start, end, str, startIdx) { 368 | var s = str.indexOf(start, startIdx); 369 | var e = str.indexOf(end, s+start.length); 370 | var o = { 371 | str: str.substring(s + start.length, e), 372 | start: s, 373 | end: e 374 | } 375 | if (s < 0 || e < 0|| s === e) { 376 | o.str = ""; 377 | o.start = -1; 378 | o.end = -1; 379 | } 380 | return o; 381 | } 382 | 383 | /** 384 | * 从给定的str中截取start到最后的字符串,不包括start 385 | * @param startIdx: 从哪个位置开始查找 386 | */ 387 | function readAfter(start, str, startIdx) { 388 | var s = str.indexOf(start, startIdx); 389 | var o = { 390 | str: str.substring(s + 1, str.length), 391 | start: s 392 | } 393 | 394 | return o; 395 | } 396 | 397 | /** 398 | * 是否包含中文 399 | * @param val 400 | * @returns {boolean} 401 | */ 402 | function checkChinese(val) { 403 | var reg = new RegExp("[\\u4E00-\\u9FFF]+", "g"); 404 | return reg.test(val); 405 | } 406 | 407 | // prettier-ignore 408 | function Env(t,e){"undefined"!=typeof process&&JSON.stringify(process.env).indexOf("GITHUB")>-1&&process.exit(0);class s{constructor(t){this.env=t}send(t,e="GET"){t="string"==typeof t?{url:t}:t;let s=this.get;return"POST"===e&&(s=this.post),new Promise((e,i)=>{s.call(this,t,(t,s,r)=>{t?i(t):e(s)})})}get(t){return this.send.call(this.env,t)}post(t){return this.send.call(this.env,t,"POST")}}return new class{constructor(t,e){this.name=t,this.http=new s(this),this.data=null,this.dataFile="box.dat",this.logs=[],this.isMute=!1,this.isNeedRewrite=!1,this.logSeparator="\n",this.startTime=(new Date).getTime(),Object.assign(this,e),this.log("",`🔔${this.name}, 开始!`)}isNode(){return"undefined"!=typeof module&&!!module.exports}isQuanX(){return"undefined"!=typeof $task}isSurge(){return"undefined"!=typeof $httpClient&&"undefined"==typeof $loon}isLoon(){return"undefined"!=typeof $loon}toObj(t,e=null){try{return JSON.parse(t)}catch{return e}}toStr(t,e=null){try{return JSON.stringify(t)}catch{return e}}getjson(t,e){let s=e;const i=this.getdata(t);if(i)try{s=JSON.parse(this.getdata(t))}catch{}return s}setjson(t,e){try{return this.setdata(JSON.stringify(t),e)}catch{return!1}}getScript(t){return new Promise(e=>{this.get({url:t},(t,s,i)=>e(i))})}runScript(t,e){return new Promise(s=>{let i=this.getdata("@chavy_boxjs_userCfgs.httpapi");i=i?i.replace(/\n/g,"").trim():i;let r=this.getdata("@chavy_boxjs_userCfgs.httpapi_timeout");r=r?1*r:20,r=e&&e.timeout?e.timeout:r;const[o,h]=i.split("@"),n={url:`http://${h}/v1/scripting/evaluate`,body:{script_text:t,mock_type:"cron",timeout:r},headers:{"X-Key":o,Accept:"*/*"}};this.post(n,(t,e,i)=>s(i))}).catch(t=>this.logErr(t))}loaddata(){if(!this.isNode())return{};{this.fs=this.fs?this.fs:require("fs"),this.path=this.path?this.path:require("path");const t=this.path.resolve(this.dataFile),e=this.path.resolve(process.cwd(),this.dataFile),s=this.fs.existsSync(t),i=!s&&this.fs.existsSync(e);if(!s&&!i)return{};{const i=s?t:e;try{return JSON.parse(this.fs.readFileSync(i))}catch(t){return{}}}}}writedata(){if(this.isNode()){this.fs=this.fs?this.fs:require("fs"),this.path=this.path?this.path:require("path");const t=this.path.resolve(this.dataFile),e=this.path.resolve(process.cwd(),this.dataFile),s=this.fs.existsSync(t),i=!s&&this.fs.existsSync(e),r=JSON.stringify(this.data);s?this.fs.writeFileSync(t,r):i?this.fs.writeFileSync(e,r):this.fs.writeFileSync(t,r)}}lodash_get(t,e,s){const i=e.replace(/\[(\d+)\]/g,".$1").split(".");let r=t;for(const t of i)if(r=Object(r)[t],void 0===r)return s;return r}lodash_set(t,e,s){return Object(t)!==t?t:(Array.isArray(e)||(e=e.toString().match(/[^.[\]]+/g)||[]),e.slice(0,-1).reduce((t,s,i)=>Object(t[s])===t[s]?t[s]:t[s]=Math.abs(e[i+1])>>0==+e[i+1]?[]:{},t)[e[e.length-1]]=s,t)}getdata(t){let e=this.getval(t);if(/^@/.test(t)){const[,s,i]=/^@(.*?)\.(.*?)$/.exec(t),r=s?this.getval(s):"";if(r)try{const t=JSON.parse(r);e=t?this.lodash_get(t,i,""):e}catch(t){e=""}}return e}setdata(t,e){let s=!1;if(/^@/.test(e)){const[,i,r]=/^@(.*?)\.(.*?)$/.exec(e),o=this.getval(i),h=i?"null"===o?null:o||"{}":"{}";try{const e=JSON.parse(h);this.lodash_set(e,r,t),s=this.setval(JSON.stringify(e),i)}catch(e){const o={};this.lodash_set(o,r,t),s=this.setval(JSON.stringify(o),i)}}else s=this.setval(t,e);return s}getval(t){return this.isSurge()||this.isLoon()?$persistentStore.read(t):this.isQuanX()?$prefs.valueForKey(t):this.isNode()?(this.data=this.loaddata(),this.data[t]):this.data&&this.data[t]||null}setval(t,e){return this.isSurge()||this.isLoon()?$persistentStore.write(t,e):this.isQuanX()?$prefs.setValueForKey(t,e):this.isNode()?(this.data=this.loaddata(),this.data[e]=t,this.writedata(),!0):this.data&&this.data[e]||null}initGotEnv(t){this.got=this.got?this.got:require("got"),this.cktough=this.cktough?this.cktough:require("tough-cookie"),this.ckjar=this.ckjar?this.ckjar:new this.cktough.CookieJar,t&&(t.headers=t.headers?t.headers:{},void 0===t.headers.Cookie&&void 0===t.cookieJar&&(t.cookieJar=this.ckjar))}get(t,e=(()=>{})){t.headers&&(delete t.headers["Content-Type"],delete t.headers["Content-Length"]),this.isSurge()||this.isLoon()?(this.isSurge()&&this.isNeedRewrite&&(t.headers=t.headers||{},Object.assign(t.headers,{"X-Surge-Skip-Scripting":!1})),$httpClient.get(t,(t,s,i)=>{!t&&s&&(s.body=i,s.statusCode=s.status),e(t,s,i)})):this.isQuanX()?(this.isNeedRewrite&&(t.opts=t.opts||{},Object.assign(t.opts,{hints:!1})),$task.fetch(t).then(t=>{const{statusCode:s,statusCode:i,headers:r,body:o}=t;e(null,{status:s,statusCode:i,headers:r,body:o},o)},t=>e(t))):this.isNode()&&(this.initGotEnv(t),this.got(t).on("redirect",(t,e)=>{try{if(t.headers["set-cookie"]){const s=t.headers["set-cookie"].map(this.cktough.Cookie.parse).toString();s&&this.ckjar.setCookieSync(s,null),e.cookieJar=this.ckjar}}catch(t){this.logErr(t)}}).then(t=>{const{statusCode:s,statusCode:i,headers:r,body:o}=t;e(null,{status:s,statusCode:i,headers:r,body:o},o)},t=>{const{message:s,response:i}=t;e(s,i,i&&i.body)}))}post(t,e=(()=>{})){if(t.body&&t.headers&&!t.headers["Content-Type"]&&(t.headers["Content-Type"]="application/x-www-form-urlencoded"),t.headers&&delete t.headers["Content-Length"],this.isSurge()||this.isLoon())this.isSurge()&&this.isNeedRewrite&&(t.headers=t.headers||{},Object.assign(t.headers,{"X-Surge-Skip-Scripting":!1})),$httpClient.post(t,(t,s,i)=>{!t&&s&&(s.body=i,s.statusCode=s.status),e(t,s,i)});else if(this.isQuanX())t.method="POST",this.isNeedRewrite&&(t.opts=t.opts||{},Object.assign(t.opts,{hints:!1})),$task.fetch(t).then(t=>{const{statusCode:s,statusCode:i,headers:r,body:o}=t;e(null,{status:s,statusCode:i,headers:r,body:o},o)},t=>e(t));else if(this.isNode()){this.initGotEnv(t);const{url:s,...i}=t;this.got.post(s,i).then(t=>{const{statusCode:s,statusCode:i,headers:r,body:o}=t;e(null,{status:s,statusCode:i,headers:r,body:o},o)},t=>{const{message:s,response:i}=t;e(s,i,i&&i.body)})}}time(t,e=null){const s=e?new Date(e):new Date;let i={"M+":s.getMonth()+1,"d+":s.getDate(),"H+":s.getHours(),"m+":s.getMinutes(),"s+":s.getSeconds(),"q+":Math.floor((s.getMonth()+3)/3),S:s.getMilliseconds()};/(y+)/.test(t)&&(t=t.replace(RegExp.$1,(s.getFullYear()+"").substr(4-RegExp.$1.length)));for(let e in i)new RegExp("("+e+")").test(t)&&(t=t.replace(RegExp.$1,1==RegExp.$1.length?i[e]:("00"+i[e]).substr((""+i[e]).length)));return t}msg(e=t,s="",i="",r){const o=t=>{if(!t)return t;if("string"==typeof t)return this.isLoon()?t:this.isQuanX()?{"open-url":t}:this.isSurge()?{url:t}:void 0;if("object"==typeof t){if(this.isLoon()){let e=t.openUrl||t.url||t["open-url"],s=t.mediaUrl||t["media-url"];return{openUrl:e,mediaUrl:s}}if(this.isQuanX()){let e=t["open-url"]||t.url||t.openUrl,s=t["media-url"]||t.mediaUrl;return{"open-url":e,"media-url":s}}if(this.isSurge()){let e=t.url||t.openUrl||t["open-url"];return{url:e}}}};if(this.isMute||(this.isSurge()||this.isLoon()?$notification.post(e,s,i,o(r)):this.isQuanX()&&$notify(e,s,i,o(r))),!this.isMuteLog){let t=["","==============📣系统通知📣=============="];t.push(e),s&&t.push(s),i&&t.push(i),console.log(t.join("\n")),this.logs=this.logs.concat(t)}}log(...t){t.length>0&&(this.logs=[...this.logs,...t]),console.log(t.join(this.logSeparator))}logErr(t,e){const s=!this.isSurge()&&!this.isQuanX()&&!this.isLoon();s?this.log("",`❗️${this.name}, 错误!`,t.stack):this.log("",`❗️${this.name}, 错误!`,t)}wait(t){return new Promise(e=>setTimeout(e,t))}done(t={}){const e=(new Date).getTime(),s=(e-this.startTime)/1e3;this.log("",`🔔${this.name}, 结束! 🕛 ${s} 秒`),this.log(),(this.isSurge()||this.isQuanX()||this.isLoon())&&$done(t)}}(t,e)} 409 | -------------------------------------------------------------------------------- /xxooPoolServer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | 4 | RUN yum install -y epel-release \ 5 | && rpm -ivh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm \ 6 | && yum install -y php72w php72w-pdo php72w-mysql php72w-pecl-redis php72w-opcache php72w-fpm \ 7 | && yum install -y nginx crontabs \ 8 | && mkdir -p /var/www/xxoo 9 | 10 | COPY ./ /var/www/xxoo 11 | COPY ./nginx.conf /etc/nginx/nginx.conf 12 | CMD cd /var/www/xxoo \ 13 | && chmod +x ./env.sh \ 14 | && ./env.sh \ 15 | && php data.php init \ 16 | && /usr/sbin/php-fpm \ 17 | && /usr/sbin/nginx -g "daemon off;" -------------------------------------------------------------------------------- /xxooPoolServer/README.md: -------------------------------------------------------------------------------- 1 | # 使用说明 2 | 3 | 部署方式: 4 | 5 | 6 | docker 7 | docker-compose 8 | 注:目前只支持x86架构 9 | 10 | 部署: 11 | 12 | - 将xxoo.yml文件拷贝到你的服务器,并修改里面的密码 13 | - 执行命令:docker-compose -f xxoo.yml up -d 14 | 15 | 数据管理地址: /admin.php 16 | -------------------------------------------------------------------------------- /xxooPoolServer/admin/admin.php: -------------------------------------------------------------------------------- 1 | DB_TYPE, 40 | * 'database_file' => DB_URL 41 | * ]); 42 | * 43 | * const DB_TYPE = "mysql"; 44 | * $db = new medoo([ 45 | * 'database_type' => DB_TYPE, 46 | * 'database_name' => '', 47 | * 'server' => '192.168.2.10', 48 | * 'port'=>3306, 49 | * 'username'=>'', 50 | * 'password'=>'' 51 | * ]); 52 | */ 53 | const DB_TYPE = "mysql"; 54 | define("DB_DATABASE", getenv('DB_DATABASE')); 55 | define("DB_HOST", getenv('DB_HOST')); 56 | define("DB_PORT", getenv('DB_PORT')); 57 | define("DB_USER", getenv('DB_USER')); 58 | define("DB_PASS", getenv('DB_PASS')); 59 | 60 | 61 | /* 62 | const DB_URL = __DIR__ . '/shareCode.db'; 63 | const DB_TYPE = "sqlite"; 64 | $db = new medoo([ 65 | 'database_type' => DB_TYPE, 66 | 'database_file' => DB_URL 67 | ]);*/ 68 | 69 | /** 70 | * redis配置,如果你的服务器有phpredis扩展的话才打开,否则不要打开 71 | */ 72 | const USE_REDIS = true; 73 | define("REDIS_HOST", getenv('REDIS_HOST')); 74 | define("REDIS_PORT", getenv('REDIS_PORT')); 75 | const REDIS_PASS = ''; 76 | const REDIS_DEFAULT_TIME = 43200; 77 | const REDIS_IDNEX = 0; 78 | 79 | 80 | /** 81 | * 没有环境变量时使用的默认触发异步任务的密码 82 | */ 83 | const TASK_PASS = 'testpassasdf234234.,,sdklfghjdklfg'; 84 | 85 | /** 86 | * 助力次数配置 87 | * [a,b] 88 | * a: 需要助力的次数 89 | * b: 可提供助力次数 90 | */ 91 | const ENVS = [ 92 | 'FRUITSHARECODES' => [5, 3], //京东农场 93 | 'PETSHARECODES' => [5, 5], //京东萌宠 94 | 'PLANT_BEAN_SHARECODES' => [9, 3], //种豆得豆 95 | 'DDFACTORY_SHARECODES' => [5, 3], //东东工厂 96 | 'DREAM_FACTORY_SHARE_CODES' => [15, 3], //京喜工厂 97 | 'JXNC_SHARECODES' => [5, 3], //京喜农场 98 | 'JDZZ_SHARECODES' => [5, 2], //京东赚赚 99 | 'JDJOY_SHARECODES' => [6, 1], //疯狂的JOY 100 | 'BOOKSHOP_SHARECODES' => [10, 1], //京东书店 101 | 'JD_CASH_SHARECODES' => [10, 1], //签到领现金 102 | 'JDSGMH_SHARECODES' => [10, 1], //闪购盲盒 103 | 'JDCFD_SHARECODES' => [5, 5], //京喜财富岛 104 | 'JDHEALTH_SHARECODES' => [5, 5], //东东健康 105 | 'CITY_SHARECODES' => [5, 5] //城城领现金 106 | ]; 107 | 108 | /** 109 | * 助力码非法字符集,用于过滤 110 | */ 111 | const ILLEGAL_CHAR = [ 112 | '{', 113 | '}', 114 | '[', 115 | ']' 116 | ]; 117 | 118 | /** 119 | * pt_pin非法字符集,用于过滤 120 | */ 121 | const ILLEGAL_PT_PIN_CHAR = [ 122 | 'pt_pin' 123 | ]; 124 | -------------------------------------------------------------------------------- /xxooPoolServer/data.php: -------------------------------------------------------------------------------- 1 | exec("CREATE DATABASE $database"); 73 | if ($res == false) { 74 | var_dump("创建数据库失败"); 75 | var_dump($conn->errorInfo()); 76 | return false; 77 | } 78 | var_dump('分配数据库权限'); 79 | $conn->exec("GRANT ALL PRIVILEGES ON *.* TO '${user}'@'%' IDENTIFIED BY '${pass}' WITH GRANT OPTION"); 80 | $conn->exec("FLUSH PRIVILEGES"); 81 | return true; 82 | 83 | } 84 | 85 | function refreshCurrentCodeNum($userId, $token) 86 | { 87 | $db = getDB(); 88 | $count = $db->count('share_code', [ 89 | 'USER_ID' => $userId 90 | ]); 91 | $db->update('user', ['CURRENT_NUM' => $count], [ 92 | 'ID' => $userId 93 | ]); 94 | getRedis()->del(getUserKey($token)); 95 | dlog("refreshCurrentCodeNum_${userId}", "count ${count}"); 96 | } 97 | 98 | function refreshAll() 99 | { 100 | foreach (ENVS as $env => $v) { 101 | refresh($env); 102 | } 103 | } 104 | 105 | function refresh($env) 106 | { 107 | $db = getDB(); 108 | 109 | //刷数据到缓存前清理数据 110 | dataClean(); 111 | 112 | //批量查询出来刷入缓存 113 | $page = 0; 114 | $num = 200; 115 | $loopMax = 999; 116 | $c = 0; 117 | do { 118 | $loopMax--; 119 | $res = $db->select('share_code', ['CODE'], [ 120 | 'ENV' => $env, 121 | 'LIMIT' => [$page * $num, $num] 122 | ]); 123 | if (count($res) < 1) { 124 | break; 125 | } 126 | $codes = []; 127 | foreach ($res as $k => $data) { 128 | $codes[] = $data['CODE']; 129 | } 130 | $c = $c + count($res); 131 | getRedis()->sAddArray(getEnvCodeKey($env), $codes); 132 | $page++; 133 | } while ($loopMax > 0); 134 | dlog("refresh_${env}", " count " . $c); 135 | } 136 | 137 | function runSql($file, $posis) 138 | { 139 | $db = getDB(); 140 | $fileContent = file_get_contents($file); 141 | 142 | if (DB_TYPE == 'mysql') { 143 | //兼容mysql 144 | $fileContent = str_replace("autoincrement", "AUTO_INCREMENT", $fileContent); 145 | $fileContent = str_replace("TIMESTAMP", "BIGINT", $fileContent); 146 | } 147 | 148 | $sqls = explode(";", $fileContent); 149 | $NamedSqls = []; 150 | foreach ($sqls as $sql) { 151 | $sql = trim($sql); 152 | $s = strpos($sql, "["); 153 | $posi = substr($sql, $s + 1, strpos($sql, "]") - $s - 1); 154 | $NamedSqls[$posi . ""] = $sql; 155 | } 156 | if (count($posis) > 0) { 157 | foreach ($posis as $posi) { 158 | $db->query($NamedSqls[$posi]); 159 | var_dump($db->error()); 160 | } 161 | } else { 162 | foreach ($NamedSqls as $name => $sql) { 163 | $db->query($sql); 164 | var_dump($db->error()); 165 | } 166 | } 167 | } 168 | 169 | function query($sql) 170 | { 171 | $db = getDB(); 172 | var_dump($db->query($sql)->fetchAll()); 173 | } 174 | 175 | function sql($sql) 176 | { 177 | $db = getDB(); 178 | $db->query($sql); 179 | var_dump($db->error()); 180 | } 181 | 182 | function dataClean() 183 | { 184 | $db = getDB(); 185 | $ctime = time() - 1209600;//2周前 186 | $count = $db->count('share_code', [ 187 | 'CREATE_TIME[<]' => $ctime 188 | ]); 189 | //为什么要先查询再执行update? 因为sqlite执行修改时会锁定文件导致并发下降,但是可以共享读 190 | if ($count > 100) { 191 | //大于100判断是为了减少进行删除的频率 192 | $db->delete('share_code', [ 193 | 'CREATE_TIME[<]' => $ctime 194 | ]); 195 | echo getDatetime() . ' > 刪除' . $count . "条数据\r\n"; 196 | } 197 | } 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /xxooPoolServer/db.sql: -------------------------------------------------------------------------------- 1 | -- [0]用户助力码 2 | CREATE TABLE share_code 3 | ( 4 | ID INTEGER PRIMARY KEY autoincrement, 5 | USER_ID INTEGER NOT NULL, -- 用户id 6 | PT_PIN VARCHAR(128), -- 用户上传的pt_pin值 7 | ENV VARCHAR(128), -- 环境变量名 8 | CODE VARCHAR(256), -- 助力码 9 | CREATE_TIME TIMESTAMP -- 创建时间,也是更新时间 10 | ); 11 | 12 | -- [1] 13 | CREATE INDEX share_code_user_id 14 | on share_code (USER_ID); 15 | 16 | -- [2] 17 | CREATE INDEX share_code_env 18 | on share_code (ENV); 19 | 20 | -- [3] 21 | CREATE UNIQUE INDEX share_code_code 22 | on share_code (CODE); 23 | 24 | -- [4] 用户表 25 | CREATE TABLE user 26 | ( 27 | ID INTEGER PRIMARY KEY autoincrement, 28 | TOKEN VARCHAR (128) NOT NULL, -- 用户对接服务池的token值,需要开放给用户 29 | ENABLED INT, -- 启用状态 1启用 0禁用 30 | LIMITED int, -- 上传的code数限制 31 | CREATE_TIME TIMESTAMP 32 | ); 33 | 34 | -- [5] 35 | CREATE UNIQUE INDEX user_token 36 | on `user` (TOKEN); 37 | 38 | -- [6] 39 | insert into `user`(`TOKEN`, `ENABLED`, `LIMITED`) 40 | values ('dev_token', 1, 10000); 41 | 42 | -- [7] 43 | ALTER TABLE `user` 44 | ADD `DATA_VERSION` VARCHAR(64); 45 | 46 | -- [8] 47 | ALTER TABLE `user` 48 | ADD `UPDATED_TIME` TIMESTAMP; 49 | 50 | -- [9] 定向助力表 51 | CREATE TABLE user_for 52 | ( 53 | ID INTEGER PRIMARY KEY autoincrement, 54 | USER_ID INTEGER NOT NULL, -- 用户id 55 | PT_PIN VARCHAR(128) NOT NULL, -- 用户的pt_pin值 56 | ASK_FOR VARCHAR(128) NOT NULL, -- 用户要助力的好友pt_pin值 57 | CREATE_TIME TIMESTAMP 58 | ); 59 | -- [10] 60 | CREATE INDEX user_for_ask_for 61 | on `user_for` (ASK_FOR); 62 | 63 | -- [11] 64 | ALTER TABLE `user` 65 | ADD `CURRENT_NUM` INT; -- 用户当前上传的助力数 -------------------------------------------------------------------------------- /xxooPoolServer/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## 不要动这个文件- 3 | CRTDIR=$(pwd) 4 | cd ${CRTDIR} 5 | echo " 6 | env.php -------------------------------------------------------------------------------- /xxooPoolServer/lib/adminer/adminer-4.8.1-en.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/q398044828/xxoo_share_pool/d6591eacca1155c81091222c8a5bfa1a71d41f89/xxooPoolServer/lib/adminer/adminer-4.8.1-en.php -------------------------------------------------------------------------------- /xxooPoolServer/lib/adminer/plugin-login-password-less.php: -------------------------------------------------------------------------------- 1 | password_hash = $password_hash; 18 | } 19 | 20 | function credentials() { 21 | $password = get_password(); 22 | return array(SERVER, $_GET["username"], (password_verify($password, $this->password_hash) ? "" : $password)); 23 | } 24 | 25 | function login($login, $password) { 26 | if ($password != "") { 27 | return true; 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /xxooPoolServer/lib/adminer/plugin.php: -------------------------------------------------------------------------------- 1 | _findRootClass($class), 'Adminer')) { //! can use interface 28 | $plugins[$class] = new $class; 29 | } 30 | } 31 | } 32 | $this->plugins = $plugins; 33 | //! it is possible to use ReflectionObject to find out which plugins defines which methods at once 34 | } 35 | 36 | function _callParent($function, $args) { 37 | return call_user_func_array(array('parent', $function), $args); 38 | } 39 | 40 | function _applyPlugin($function, $args) { 41 | foreach ($this->plugins as $plugin) { 42 | if (method_exists($plugin, $function)) { 43 | switch (count($args)) { // call_user_func_array() doesn't work well with references 44 | case 0: $return = $plugin->$function(); break; 45 | case 1: $return = $plugin->$function($args[0]); break; 46 | case 2: $return = $plugin->$function($args[0], $args[1]); break; 47 | case 3: $return = $plugin->$function($args[0], $args[1], $args[2]); break; 48 | case 4: $return = $plugin->$function($args[0], $args[1], $args[2], $args[3]); break; 49 | case 5: $return = $plugin->$function($args[0], $args[1], $args[2], $args[3], $args[4]); break; 50 | case 6: $return = $plugin->$function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5]); break; 51 | default: trigger_error('Too many parameters.', E_USER_WARNING); 52 | } 53 | if ($return !== null) { 54 | return $return; 55 | } 56 | } 57 | } 58 | return $this->_callParent($function, $args); 59 | } 60 | 61 | function _appendPlugin($function, $args) { 62 | $return = $this->_callParent($function, $args); 63 | foreach ($this->plugins as $plugin) { 64 | if (method_exists($plugin, $function)) { 65 | $value = call_user_func_array(array($plugin, $function), $args); 66 | if ($value) { 67 | $return += $value; 68 | } 69 | } 70 | } 71 | return $return; 72 | } 73 | 74 | // appendPlugin 75 | 76 | function dumpFormat() { 77 | $args = func_get_args(); 78 | return $this->_appendPlugin(__FUNCTION__, $args); 79 | } 80 | 81 | function dumpOutput() { 82 | $args = func_get_args(); 83 | return $this->_appendPlugin(__FUNCTION__, $args); 84 | } 85 | 86 | function editRowPrint($table, $fields, $row, $update) { 87 | $args = func_get_args(); 88 | return $this->_appendPlugin(__FUNCTION__, $args); 89 | } 90 | 91 | function editFunctions($field) { 92 | $args = func_get_args(); 93 | return $this->_appendPlugin(__FUNCTION__, $args); 94 | } 95 | 96 | // applyPlugin 97 | 98 | function name() { 99 | $args = func_get_args(); 100 | return $this->_applyPlugin(__FUNCTION__, $args); 101 | } 102 | 103 | function credentials() { 104 | $args = func_get_args(); 105 | return $this->_applyPlugin(__FUNCTION__, $args); 106 | } 107 | 108 | function connectSsl() { 109 | $args = func_get_args(); 110 | return $this->_applyPlugin(__FUNCTION__, $args); 111 | } 112 | 113 | function permanentLogin($create = false) { 114 | $args = func_get_args(); 115 | return $this->_applyPlugin(__FUNCTION__, $args); 116 | } 117 | 118 | function bruteForceKey() { 119 | $args = func_get_args(); 120 | return $this->_applyPlugin(__FUNCTION__, $args); 121 | } 122 | 123 | function serverName($server) { 124 | $args = func_get_args(); 125 | return $this->_applyPlugin(__FUNCTION__, $args); 126 | } 127 | 128 | function database() { 129 | $args = func_get_args(); 130 | return $this->_applyPlugin(__FUNCTION__, $args); 131 | } 132 | 133 | function schemas() { 134 | $args = func_get_args(); 135 | return $this->_applyPlugin(__FUNCTION__, $args); 136 | } 137 | 138 | function databases($flush = true) { 139 | $args = func_get_args(); 140 | return $this->_applyPlugin(__FUNCTION__, $args); 141 | } 142 | 143 | function queryTimeout() { 144 | $args = func_get_args(); 145 | return $this->_applyPlugin(__FUNCTION__, $args); 146 | } 147 | 148 | function headers() { 149 | $args = func_get_args(); 150 | return $this->_applyPlugin(__FUNCTION__, $args); 151 | } 152 | 153 | function csp() { 154 | $args = func_get_args(); 155 | return $this->_applyPlugin(__FUNCTION__, $args); 156 | } 157 | 158 | function head() { 159 | $args = func_get_args(); 160 | return $this->_applyPlugin(__FUNCTION__, $args); 161 | } 162 | 163 | function css() { 164 | $args = func_get_args(); 165 | return $this->_applyPlugin(__FUNCTION__, $args); 166 | } 167 | 168 | function loginForm() { 169 | $args = func_get_args(); 170 | return $this->_applyPlugin(__FUNCTION__, $args); 171 | } 172 | 173 | function loginFormField($name, $heading, $value) { 174 | $args = func_get_args(); 175 | return $this->_applyPlugin(__FUNCTION__, $args); 176 | } 177 | 178 | function login($login, $password) { 179 | $args = func_get_args(); 180 | return $this->_applyPlugin(__FUNCTION__, $args); 181 | } 182 | 183 | function tableName($tableStatus) { 184 | $args = func_get_args(); 185 | return $this->_applyPlugin(__FUNCTION__, $args); 186 | } 187 | 188 | function fieldName($field, $order = 0) { 189 | $args = func_get_args(); 190 | return $this->_applyPlugin(__FUNCTION__, $args); 191 | } 192 | 193 | function selectLinks($tableStatus, $set = "") { 194 | $args = func_get_args(); 195 | return $this->_applyPlugin(__FUNCTION__, $args); 196 | } 197 | 198 | function foreignKeys($table) { 199 | $args = func_get_args(); 200 | return $this->_applyPlugin(__FUNCTION__, $args); 201 | } 202 | 203 | function backwardKeys($table, $tableName) { 204 | $args = func_get_args(); 205 | return $this->_applyPlugin(__FUNCTION__, $args); 206 | } 207 | 208 | function backwardKeysPrint($backwardKeys, $row) { 209 | $args = func_get_args(); 210 | return $this->_applyPlugin(__FUNCTION__, $args); 211 | } 212 | 213 | function selectQuery($query, $start, $failed = false) { 214 | $args = func_get_args(); 215 | return $this->_applyPlugin(__FUNCTION__, $args); 216 | } 217 | 218 | function sqlCommandQuery($query) { 219 | $args = func_get_args(); 220 | return $this->_applyPlugin(__FUNCTION__, $args); 221 | } 222 | 223 | function rowDescription($table) { 224 | $args = func_get_args(); 225 | return $this->_applyPlugin(__FUNCTION__, $args); 226 | } 227 | 228 | function rowDescriptions($rows, $foreignKeys) { 229 | $args = func_get_args(); 230 | return $this->_applyPlugin(__FUNCTION__, $args); 231 | } 232 | 233 | function selectLink($val, $field) { 234 | $args = func_get_args(); 235 | return $this->_applyPlugin(__FUNCTION__, $args); 236 | } 237 | 238 | function selectVal($val, $link, $field, $original) { 239 | $args = func_get_args(); 240 | return $this->_applyPlugin(__FUNCTION__, $args); 241 | } 242 | 243 | function editVal($val, $field) { 244 | $args = func_get_args(); 245 | return $this->_applyPlugin(__FUNCTION__, $args); 246 | } 247 | 248 | function tableStructurePrint($fields) { 249 | $args = func_get_args(); 250 | return $this->_applyPlugin(__FUNCTION__, $args); 251 | } 252 | 253 | function tableIndexesPrint($indexes) { 254 | $args = func_get_args(); 255 | return $this->_applyPlugin(__FUNCTION__, $args); 256 | } 257 | 258 | function selectColumnsPrint($select, $columns) { 259 | $args = func_get_args(); 260 | return $this->_applyPlugin(__FUNCTION__, $args); 261 | } 262 | 263 | function selectSearchPrint($where, $columns, $indexes) { 264 | $args = func_get_args(); 265 | return $this->_applyPlugin(__FUNCTION__, $args); 266 | } 267 | 268 | function selectOrderPrint($order, $columns, $indexes) { 269 | $args = func_get_args(); 270 | return $this->_applyPlugin(__FUNCTION__, $args); 271 | } 272 | 273 | function selectLimitPrint($limit) { 274 | $args = func_get_args(); 275 | return $this->_applyPlugin(__FUNCTION__, $args); 276 | } 277 | 278 | function selectLengthPrint($text_length) { 279 | $args = func_get_args(); 280 | return $this->_applyPlugin(__FUNCTION__, $args); 281 | } 282 | 283 | function selectActionPrint($indexes) { 284 | $args = func_get_args(); 285 | return $this->_applyPlugin(__FUNCTION__, $args); 286 | } 287 | 288 | function selectCommandPrint() { 289 | $args = func_get_args(); 290 | return $this->_applyPlugin(__FUNCTION__, $args); 291 | } 292 | 293 | function selectImportPrint() { 294 | $args = func_get_args(); 295 | return $this->_applyPlugin(__FUNCTION__, $args); 296 | } 297 | 298 | function selectEmailPrint($emailFields, $columns) { 299 | $args = func_get_args(); 300 | return $this->_applyPlugin(__FUNCTION__, $args); 301 | } 302 | 303 | function selectColumnsProcess($columns, $indexes) { 304 | $args = func_get_args(); 305 | return $this->_applyPlugin(__FUNCTION__, $args); 306 | } 307 | 308 | function selectSearchProcess($fields, $indexes) { 309 | $args = func_get_args(); 310 | return $this->_applyPlugin(__FUNCTION__, $args); 311 | } 312 | 313 | function selectOrderProcess($fields, $indexes) { 314 | $args = func_get_args(); 315 | return $this->_applyPlugin(__FUNCTION__, $args); 316 | } 317 | 318 | function selectLimitProcess() { 319 | $args = func_get_args(); 320 | return $this->_applyPlugin(__FUNCTION__, $args); 321 | } 322 | 323 | function selectLengthProcess() { 324 | $args = func_get_args(); 325 | return $this->_applyPlugin(__FUNCTION__, $args); 326 | } 327 | 328 | function selectEmailProcess($where, $foreignKeys) { 329 | $args = func_get_args(); 330 | return $this->_applyPlugin(__FUNCTION__, $args); 331 | } 332 | 333 | function selectQueryBuild($select, $where, $group, $order, $limit, $page) { 334 | $args = func_get_args(); 335 | return $this->_applyPlugin(__FUNCTION__, $args); 336 | } 337 | 338 | function messageQuery($query, $time, $failed = false) { 339 | $args = func_get_args(); 340 | return $this->_applyPlugin(__FUNCTION__, $args); 341 | } 342 | 343 | function editInput($table, $field, $attrs, $value) { 344 | $args = func_get_args(); 345 | return $this->_applyPlugin(__FUNCTION__, $args); 346 | } 347 | 348 | function editHint($table, $field, $value) { 349 | $args = func_get_args(); 350 | return $this->_applyPlugin(__FUNCTION__, $args); 351 | } 352 | 353 | function processInput($field, $value, $function = "") { 354 | $args = func_get_args(); 355 | return $this->_applyPlugin(__FUNCTION__, $args); 356 | } 357 | 358 | function dumpDatabase($db) { 359 | $args = func_get_args(); 360 | return $this->_applyPlugin(__FUNCTION__, $args); 361 | } 362 | 363 | function dumpTable($table, $style, $is_view = 0) { 364 | $args = func_get_args(); 365 | return $this->_applyPlugin(__FUNCTION__, $args); 366 | } 367 | 368 | function dumpData($table, $style, $query) { 369 | $args = func_get_args(); 370 | return $this->_applyPlugin(__FUNCTION__, $args); 371 | } 372 | 373 | function dumpFilename($identifier) { 374 | $args = func_get_args(); 375 | return $this->_applyPlugin(__FUNCTION__, $args); 376 | } 377 | 378 | function dumpHeaders($identifier, $multi_table = false) { 379 | $args = func_get_args(); 380 | return $this->_applyPlugin(__FUNCTION__, $args); 381 | } 382 | 383 | function importServerPath() { 384 | $args = func_get_args(); 385 | return $this->_applyPlugin(__FUNCTION__, $args); 386 | } 387 | 388 | function homepage() { 389 | $args = func_get_args(); 390 | return $this->_applyPlugin(__FUNCTION__, $args); 391 | } 392 | 393 | function navigation($missing) { 394 | $args = func_get_args(); 395 | return $this->_applyPlugin(__FUNCTION__, $args); 396 | } 397 | 398 | function databasesPrint($missing) { 399 | $args = func_get_args(); 400 | return $this->_applyPlugin(__FUNCTION__, $args); 401 | } 402 | 403 | function tablesPrint($tables) { 404 | $args = func_get_args(); 405 | return $this->_applyPlugin(__FUNCTION__, $args); 406 | } 407 | 408 | } -------------------------------------------------------------------------------- /xxooPoolServer/lib/medoo.php: -------------------------------------------------------------------------------- 1 | $value) 53 | { 54 | $this->$option = $value; 55 | } 56 | } 57 | else 58 | { 59 | return false; 60 | } 61 | 62 | if ( 63 | isset($this->port) && 64 | is_int($this->port * 1) 65 | ) 66 | { 67 | $port = $this->port; 68 | } 69 | 70 | $type = strtolower($this->database_type); 71 | $is_port = isset($port); 72 | 73 | if (isset($options[ 'prefix' ])) 74 | { 75 | $this->prefix = $options[ 'prefix' ]; 76 | } 77 | 78 | switch ($type) 79 | { 80 | case 'mariadb': 81 | $type = 'mysql'; 82 | 83 | case 'mysql': 84 | if ($this->socket) 85 | { 86 | $dsn = $type . ':unix_socket=' . $this->socket . ';dbname=' . $this->database_name; 87 | } 88 | else 89 | { 90 | $dsn = $type . ':host=' . $this->server . ($is_port ? ';port=' . $port : '') . ';dbname=' . $this->database_name; 91 | } 92 | 93 | // Make MySQL using standard quoted identifier 94 | $commands[] = 'SET SQL_MODE=ANSI_QUOTES'; 95 | break; 96 | 97 | case 'pgsql': 98 | $dsn = $type . ':host=' . $this->server . ($is_port ? ';port=' . $port : '') . ';dbname=' . $this->database_name; 99 | break; 100 | 101 | case 'sybase': 102 | $dsn = 'dblib:host=' . $this->server . ($is_port ? ':' . $port : '') . ';dbname=' . $this->database_name; 103 | break; 104 | 105 | case 'oracle': 106 | $dbname = $this->server ? 107 | '//' . $this->server . ($is_port ? ':' . $port : ':1521') . '/' . $this->database_name : 108 | $this->database_name; 109 | 110 | $dsn = 'oci:dbname=' . $dbname . ($this->charset ? ';charset=' . $this->charset : ''); 111 | break; 112 | 113 | case 'mssql': 114 | $dsn = strstr(PHP_OS, 'WIN') ? 115 | 'sqlsrv:server=' . $this->server . ($is_port ? ',' . $port : '') . ';database=' . $this->database_name : 116 | 'dblib:host=' . $this->server . ($is_port ? ':' . $port : '') . ';dbname=' . $this->database_name; 117 | 118 | // Keep MSSQL QUOTED_IDENTIFIER is ON for standard quoting 119 | $commands[] = 'SET QUOTED_IDENTIFIER ON'; 120 | break; 121 | 122 | case 'sqlite': 123 | $dsn = $type . ':' . $this->database_file; 124 | $this->username = null; 125 | $this->password = null; 126 | break; 127 | } 128 | 129 | if ( 130 | in_array($type, array('mariadb', 'mysql', 'pgsql', 'sybase', 'mssql')) && 131 | $this->charset 132 | ) 133 | { 134 | $commands[] = "SET NAMES '" . $this->charset . "'"; 135 | } 136 | 137 | $this->pdo = new PDO( 138 | $dsn, 139 | $this->username, 140 | $this->password, 141 | $this->option 142 | ); 143 | 144 | foreach ($commands as $value) 145 | { 146 | $this->pdo->exec($value); 147 | } 148 | } 149 | catch (PDOException $e) { 150 | throw new Exception($e->getMessage()); 151 | } 152 | } 153 | 154 | public function query($query) 155 | { 156 | if ($this->debug_mode) 157 | { 158 | echo $query; 159 | 160 | $this->debug_mode = false; 161 | 162 | return false; 163 | } 164 | 165 | $this->logs[] = $query; 166 | 167 | return $this->pdo->query($query); 168 | } 169 | 170 | public function exec($query) 171 | { 172 | if ($this->debug_mode) 173 | { 174 | echo $query; 175 | 176 | $this->debug_mode = false; 177 | 178 | return false; 179 | } 180 | 181 | $this->logs[] = $query; 182 | 183 | return $this->pdo->exec($query); 184 | } 185 | 186 | public function quote($string) 187 | { 188 | return $this->pdo->quote($string); 189 | } 190 | 191 | protected function table_quote($table) 192 | { 193 | return '"' . $this->prefix . $table . '"'; 194 | } 195 | 196 | protected function column_quote($string) 197 | { 198 | preg_match('/(\(JSON\)\s*|^#)?([a-zA-Z0-9_]*)\.([a-zA-Z0-9_]*)/', $string, $column_match); 199 | 200 | if (isset($column_match[ 2 ], $column_match[ 3 ])) 201 | { 202 | return '"' . $this->prefix . $column_match[ 2 ] . '"."' . $column_match[ 3 ] . '"'; 203 | } 204 | 205 | return '"' . $string . '"'; 206 | } 207 | 208 | protected function column_push(&$columns) 209 | { 210 | if ($columns == '*') 211 | { 212 | return $columns; 213 | } 214 | 215 | if (is_string($columns)) 216 | { 217 | $columns = array($columns); 218 | } 219 | 220 | $stack = array(); 221 | 222 | foreach ($columns as $key => $value) 223 | { 224 | if (is_array($value)) 225 | { 226 | $stack[] = $this->column_push($value); 227 | } 228 | else 229 | { 230 | preg_match('/([a-zA-Z0-9_\-\.]*)\s*\(([a-zA-Z0-9_\-]*)\)/i', $value, $match); 231 | 232 | if (isset($match[ 1 ], $match[ 2 ])) 233 | { 234 | $stack[] = $this->column_quote( $match[ 1 ] ) . ' AS ' . $this->column_quote( $match[ 2 ] ); 235 | 236 | $columns[ $key ] = $match[ 2 ]; 237 | } 238 | else 239 | { 240 | $stack[] = $this->column_quote( $value ); 241 | } 242 | } 243 | } 244 | 245 | return implode($stack, ','); 246 | } 247 | 248 | protected function array_quote($array) 249 | { 250 | $temp = array(); 251 | 252 | foreach ($array as $value) 253 | { 254 | $temp[] = is_int($value) ? $value : $this->pdo->quote($value); 255 | } 256 | 257 | return implode($temp, ','); 258 | } 259 | 260 | protected function inner_conjunct($data, $conjunctor, $outer_conjunctor) 261 | { 262 | $haystack = array(); 263 | 264 | foreach ($data as $value) 265 | { 266 | $haystack[] = '(' . $this->data_implode($value, $conjunctor) . ')'; 267 | } 268 | 269 | return implode($outer_conjunctor . ' ', $haystack); 270 | } 271 | 272 | protected function fn_quote($column, $string) 273 | { 274 | return (strpos($column, '#') === 0 && preg_match('/^[A-Z0-9\_]*\([^)]*\)$/', $string)) ? 275 | 276 | $string : 277 | 278 | $this->quote($string); 279 | } 280 | 281 | protected function data_implode($data, $conjunctor, $outer_conjunctor = null) 282 | { 283 | $wheres = array(); 284 | 285 | foreach ($data as $key => $value) 286 | { 287 | $type = gettype($value); 288 | 289 | if ( 290 | preg_match("/^(AND|OR)(\s+#.*)?$/i", $key, $relation_match) && 291 | $type == 'array' 292 | ) 293 | { 294 | $wheres[] = 0 !== count(array_diff_key($value, array_keys(array_keys($value)))) ? 295 | '(' . $this->data_implode($value, ' ' . $relation_match[ 1 ]) . ')' : 296 | '(' . $this->inner_conjunct($value, ' ' . $relation_match[ 1 ], $conjunctor) . ')'; 297 | } 298 | else 299 | { 300 | preg_match('/(#?)([\w\.\-]+)(\[(\>|\>\=|\<|\<\=|\!|\<\>|\>\<|\!?~)\])?/i', $key, $match); 301 | $column = $this->column_quote($match[ 2 ]); 302 | 303 | if (isset($match[ 4 ])) 304 | { 305 | $operator = $match[ 4 ]; 306 | 307 | if ($operator == '!') 308 | { 309 | switch ($type) 310 | { 311 | case 'NULL': 312 | $wheres[] = $column . ' IS NOT NULL'; 313 | break; 314 | 315 | case 'array': 316 | $wheres[] = $column . ' NOT IN (' . $this->array_quote($value) . ')'; 317 | break; 318 | 319 | case 'integer': 320 | case 'double': 321 | $wheres[] = $column . ' != ' . $value; 322 | break; 323 | 324 | case 'boolean': 325 | $wheres[] = $column . ' != ' . ($value ? '1' : '0'); 326 | break; 327 | 328 | case 'string': 329 | $wheres[] = $column . ' != ' . $this->fn_quote($key, $value); 330 | break; 331 | } 332 | } 333 | 334 | if ($operator == '<>' || $operator == '><') 335 | { 336 | if ($type == 'array') 337 | { 338 | if ($operator == '><') 339 | { 340 | $column .= ' NOT'; 341 | } 342 | 343 | if (is_numeric($value[ 0 ]) && is_numeric($value[ 1 ])) 344 | { 345 | $wheres[] = '(' . $column . ' BETWEEN ' . $value[ 0 ] . ' AND ' . $value[ 1 ] . ')'; 346 | } 347 | else 348 | { 349 | $wheres[] = '(' . $column . ' BETWEEN ' . $this->quote($value[ 0 ]) . ' AND ' . $this->quote($value[ 1 ]) . ')'; 350 | } 351 | } 352 | } 353 | 354 | if ($operator == '~' || $operator == '!~') 355 | { 356 | if ($type != 'array') 357 | { 358 | $value = array($value); 359 | } 360 | 361 | $like_clauses = array(); 362 | 363 | foreach ($value as $item) 364 | { 365 | $item = strval($item); 366 | $suffix = mb_substr($item, -1, 1); 367 | 368 | if (preg_match('/^(?!(%|\[|_])).+(?fn_quote($key, $item); 374 | } 375 | 376 | $wheres[] = implode(' OR ', $like_clauses); 377 | } 378 | 379 | if (in_array($operator, array('>', '>=', '<', '<='))) 380 | { 381 | if (is_numeric($value)) 382 | { 383 | $wheres[] = $column . ' ' . $operator . ' ' . $value; 384 | } 385 | elseif (strpos($key, '#') === 0) 386 | { 387 | $wheres[] = $column . ' ' . $operator . ' ' . $this->fn_quote($key, $value); 388 | } 389 | else 390 | { 391 | $wheres[] = $column . ' ' . $operator . ' ' . $this->quote($value); 392 | } 393 | } 394 | } 395 | else 396 | { 397 | switch ($type) 398 | { 399 | case 'NULL': 400 | $wheres[] = $column . ' IS NULL'; 401 | break; 402 | 403 | case 'array': 404 | $wheres[] = $column . ' IN (' . $this->array_quote($value) . ')'; 405 | break; 406 | 407 | case 'integer': 408 | case 'double': 409 | $wheres[] = $column . ' = ' . $value; 410 | break; 411 | 412 | case 'boolean': 413 | $wheres[] = $column . ' = ' . ($value ? '1' : '0'); 414 | break; 415 | 416 | case 'string': 417 | $wheres[] = $column . ' = ' . $this->fn_quote($key, $value); 418 | break; 419 | } 420 | } 421 | } 422 | } 423 | 424 | return implode($conjunctor . ' ', $wheres); 425 | } 426 | 427 | protected function where_clause($where) 428 | { 429 | $where_clause = ''; 430 | 431 | if (is_array($where)) 432 | { 433 | $where_keys = array_keys($where); 434 | $where_AND = preg_grep("/^AND\s*#?$/i", $where_keys); 435 | $where_OR = preg_grep("/^OR\s*#?$/i", $where_keys); 436 | 437 | $single_condition = array_diff_key($where, array_flip( 438 | array('AND', 'OR', 'GROUP', 'ORDER', 'HAVING', 'LIMIT', 'LIKE', 'MATCH') 439 | )); 440 | 441 | if ($single_condition != array()) 442 | { 443 | $condition = $this->data_implode($single_condition, ''); 444 | 445 | if ($condition != '') 446 | { 447 | $where_clause = ' WHERE ' . $condition; 448 | } 449 | } 450 | 451 | if (!empty($where_AND)) 452 | { 453 | $value = array_values($where_AND); 454 | $where_clause = ' WHERE ' . $this->data_implode($where[ $value[ 0 ] ], ' AND'); 455 | } 456 | 457 | if (!empty($where_OR)) 458 | { 459 | $value = array_values($where_OR); 460 | $where_clause = ' WHERE ' . $this->data_implode($where[ $value[ 0 ] ], ' OR'); 461 | } 462 | 463 | if (isset($where[ 'MATCH' ])) 464 | { 465 | $MATCH = $where[ 'MATCH' ]; 466 | 467 | if (is_array($MATCH) && isset($MATCH[ 'columns' ], $MATCH[ 'keyword' ])) 468 | { 469 | $where_clause .= ($where_clause != '' ? ' AND ' : ' WHERE ') . ' MATCH ("' . str_replace('.', '"."', implode($MATCH[ 'columns' ], '", "')) . '") AGAINST (' . $this->quote($MATCH[ 'keyword' ]) . ')'; 470 | } 471 | } 472 | 473 | if (isset($where[ 'GROUP' ])) 474 | { 475 | $where_clause .= ' GROUP BY ' . $this->column_quote($where[ 'GROUP' ]); 476 | 477 | if (isset($where[ 'HAVING' ])) 478 | { 479 | $where_clause .= ' HAVING ' . $this->data_implode($where[ 'HAVING' ], ' AND'); 480 | } 481 | } 482 | 483 | if (isset($where[ 'ORDER' ])) 484 | { 485 | $ORDER = $where[ 'ORDER' ]; 486 | 487 | if (is_array($ORDER)) 488 | { 489 | $stack = array(); 490 | 491 | foreach ($ORDER as $column => $value) 492 | { 493 | if (is_array($value)) 494 | { 495 | $stack[] = 'FIELD(' . $this->column_quote($column) . ', ' . $this->array_quote($value) . ')'; 496 | } 497 | else if ($value === 'ASC' || $value === 'DESC') 498 | { 499 | $stack[] = $this->column_quote($column) . ' ' . $value; 500 | } 501 | else if (is_int($column)) 502 | { 503 | $stack[] = $this->column_quote($value); 504 | } 505 | } 506 | 507 | $where_clause .= ' ORDER BY ' . implode($stack, ','); 508 | } 509 | else 510 | { 511 | $where_clause .= ' ORDER BY ' . $this->column_quote($ORDER); 512 | } 513 | } 514 | 515 | if (isset($where[ 'LIMIT' ])) 516 | { 517 | $LIMIT = $where[ 'LIMIT' ]; 518 | 519 | if (is_numeric($LIMIT)) 520 | { 521 | $where_clause .= ' LIMIT ' . $LIMIT; 522 | } 523 | 524 | if ( 525 | is_array($LIMIT) && 526 | is_numeric($LIMIT[ 0 ]) && 527 | is_numeric($LIMIT[ 1 ]) 528 | ) 529 | { 530 | if ($this->database_type === 'pgsql') 531 | { 532 | $where_clause .= ' OFFSET ' . $LIMIT[ 0 ] . ' LIMIT ' . $LIMIT[ 1 ]; 533 | } 534 | else 535 | { 536 | $where_clause .= ' LIMIT ' . $LIMIT[ 0 ] . ',' . $LIMIT[ 1 ]; 537 | } 538 | } 539 | } 540 | } 541 | else 542 | { 543 | if ($where != null) 544 | { 545 | $where_clause .= ' ' . $where; 546 | } 547 | } 548 | 549 | return $where_clause; 550 | } 551 | 552 | protected function select_context($table, $join, &$columns = null, $where = null, $column_fn = null) 553 | { 554 | preg_match('/([a-zA-Z0-9_\-]*)\s*\(([a-zA-Z0-9_\-]*)\)/i', $table, $table_match); 555 | 556 | if (isset($table_match[ 1 ], $table_match[ 2 ])) 557 | { 558 | $table = $this->table_quote($table_match[ 1 ]); 559 | 560 | $table_query = $this->table_quote($table_match[ 1 ]) . ' AS ' . $this->table_quote($table_match[ 2 ]); 561 | } 562 | else 563 | { 564 | $table = $this->table_quote($table); 565 | 566 | $table_query = $table; 567 | } 568 | 569 | $join_key = is_array($join) ? array_keys($join) : null; 570 | 571 | if ( 572 | isset($join_key[ 0 ]) && 573 | strpos($join_key[ 0 ], '[') === 0 574 | ) 575 | { 576 | $table_join = array(); 577 | 578 | $join_array = array( 579 | '>' => 'LEFT', 580 | '<' => 'RIGHT', 581 | '<>' => 'FULL', 582 | '><' => 'INNER' 583 | ); 584 | 585 | foreach($join as $sub_table => $relation) 586 | { 587 | preg_match('/(\[(\<|\>|\>\<|\<\>)\])?([a-zA-Z0-9_\-]*)\s?(\(([a-zA-Z0-9_\-]*)\))?/', $sub_table, $match); 588 | 589 | if ($match[ 2 ] != '' && $match[ 3 ] != '') 590 | { 591 | if (is_string($relation)) 592 | { 593 | $relation = 'USING ("' . $relation . '")'; 594 | } 595 | 596 | if (is_array($relation)) 597 | { 598 | // For ['column1', 'column2'] 599 | if (isset($relation[ 0 ])) 600 | { 601 | $relation = 'USING ("' . implode($relation, '", "') . '")'; 602 | } 603 | else 604 | { 605 | $joins = array(); 606 | 607 | foreach ($relation as $key => $value) 608 | { 609 | $joins[] = ( 610 | strpos($key, '.') > 0 ? 611 | // For ['tableB.column' => 'column'] 612 | $this->column_quote($key) : 613 | 614 | // For ['column1' => 'column2'] 615 | $table . '."' . $key . '"' 616 | ) . 617 | ' = ' . 618 | $this->table_quote(isset($match[ 5 ]) ? $match[ 5 ] : $match[ 3 ]) . '."' . $value . '"'; 619 | } 620 | 621 | $relation = 'ON ' . implode($joins, ' AND '); 622 | } 623 | } 624 | 625 | $table_name = $this->table_quote($match[ 3 ]) . ' '; 626 | 627 | if (isset($match[ 5 ])) 628 | { 629 | $table_name .= 'AS ' . $this->table_quote($match[ 5 ]) . ' '; 630 | } 631 | 632 | $table_join[] = $join_array[ $match[ 2 ] ] . ' JOIN ' . $table_name . $relation; 633 | } 634 | } 635 | 636 | $table_query .= ' ' . implode($table_join, ' '); 637 | } 638 | else 639 | { 640 | if (is_null($columns)) 641 | { 642 | if (is_null($where)) 643 | { 644 | if ( 645 | is_array($join) && 646 | isset($column_fn) 647 | ) 648 | { 649 | $where = $join; 650 | $columns = null; 651 | } 652 | else 653 | { 654 | $where = null; 655 | $columns = $join; 656 | } 657 | } 658 | else 659 | { 660 | $where = $join; 661 | $columns = null; 662 | } 663 | } 664 | else 665 | { 666 | $where = $columns; 667 | $columns = $join; 668 | } 669 | } 670 | 671 | if (isset($column_fn)) 672 | { 673 | if ($column_fn == 1) 674 | { 675 | $column = '1'; 676 | 677 | if (is_null($where)) 678 | { 679 | $where = $columns; 680 | } 681 | } 682 | else 683 | { 684 | if (empty($columns)) 685 | { 686 | $columns = '*'; 687 | $where = $join; 688 | } 689 | 690 | $column = $column_fn . '(' . $this->column_push($columns) . ')'; 691 | } 692 | } 693 | else 694 | { 695 | $column = $this->column_push($columns); 696 | } 697 | 698 | return 'SELECT ' . $column . ' FROM ' . $table_query . $this->where_clause($where); 699 | } 700 | 701 | protected function data_map($index, $key, $value, $data, &$stack) 702 | { 703 | if (is_array($value)) 704 | { 705 | $sub_stack = array(); 706 | 707 | foreach ($value as $sub_key => $sub_value) 708 | { 709 | if (is_array($sub_value)) 710 | { 711 | $current_stack = $stack[ $index ][ $key ]; 712 | 713 | $this->data_map(false, $sub_key, $sub_value, $data, $current_stack); 714 | 715 | $stack[ $index ][ $key ][ $sub_key ] = $current_stack[ 0 ][ $sub_key ]; 716 | } 717 | else 718 | { 719 | $this->data_map(false, preg_replace('/^[\w]*\./i', "", $sub_value), $sub_key, $data, $sub_stack); 720 | 721 | $stack[ $index ][ $key ] = $sub_stack; 722 | } 723 | } 724 | } 725 | else 726 | { 727 | if ($index !== false) 728 | { 729 | $stack[ $index ][ $value ] = $data[ $value ]; 730 | } 731 | else 732 | { 733 | $stack[ $key ] = $data[ $key ]; 734 | } 735 | } 736 | } 737 | 738 | public function select($table, $join, $columns = null, $where = null) 739 | { 740 | $column = $where == null ? $join : $columns; 741 | 742 | $is_single_column = (is_string($column) && $column !== '*'); 743 | 744 | $query = $this->query($this->select_context($table, $join, $columns, $where)); 745 | 746 | $stack = array(); 747 | 748 | $index = 0; 749 | 750 | if (!$query) 751 | { 752 | return false; 753 | } 754 | 755 | if ($columns === '*') 756 | { 757 | return $query->fetchAll(PDO::FETCH_ASSOC); 758 | } 759 | 760 | if ($is_single_column) 761 | { 762 | return $query->fetchAll(PDO::FETCH_COLUMN); 763 | } 764 | 765 | while ($row = $query->fetch(PDO::FETCH_ASSOC)) 766 | { 767 | foreach ($columns as $key => $value) 768 | { 769 | if (is_array($value)) 770 | { 771 | $this->data_map($index, $key, $value, $row, $stack); 772 | } 773 | else 774 | { 775 | $this->data_map($index, $key, preg_replace('/^[\w]*\./i', "", $value), $row, $stack); 776 | } 777 | } 778 | 779 | $index++; 780 | } 781 | 782 | return $stack; 783 | } 784 | 785 | public function insert($table, $datas) 786 | { 787 | $lastId = array(); 788 | 789 | // Check indexed or associative array 790 | if (!isset($datas[ 0 ])) 791 | { 792 | $datas = array($datas); 793 | } 794 | 795 | foreach ($datas as $data) 796 | { 797 | $values = array(); 798 | $columns = array(); 799 | 800 | foreach ($data as $key => $value) 801 | { 802 | $columns[] = preg_replace("/^(\(JSON\)\s*|#)/i", "", $key); 803 | 804 | switch (gettype($value)) 805 | { 806 | case 'NULL': 807 | $values[] = 'NULL'; 808 | break; 809 | 810 | case 'array': 811 | preg_match("/\(JSON\)\s*([\w]+)/i", $key, $column_match); 812 | 813 | $values[] = isset($column_match[ 0 ]) ? 814 | $this->quote(json_encode($value)) : 815 | $this->quote(serialize($value)); 816 | break; 817 | 818 | case 'boolean': 819 | $values[] = ($value ? '1' : '0'); 820 | break; 821 | 822 | case 'integer': 823 | case 'double': 824 | case 'string': 825 | $values[] = $this->fn_quote($key, $value); 826 | break; 827 | } 828 | } 829 | 830 | $this->exec('INSERT INTO ' . $this->table_quote($table) . ' (' . implode(', ', $columns) . ') VALUES (' . implode($values, ', ') . ')'); 831 | 832 | $lastId[] = $this->pdo->lastInsertId(); 833 | } 834 | 835 | return count($lastId) > 1 ? $lastId : $lastId[ 0 ]; 836 | } 837 | 838 | public function update($table, $data, $where = null) 839 | { 840 | $fields = array(); 841 | 842 | foreach ($data as $key => $value) 843 | { 844 | preg_match('/([\w]+)(\[(\+|\-|\*|\/)\])?/i', $key, $match); 845 | 846 | if (isset($match[ 3 ])) 847 | { 848 | if (is_numeric($value)) 849 | { 850 | $fields[] = $this->column_quote($match[ 1 ]) . ' = ' . $this->column_quote($match[ 1 ]) . ' ' . $match[ 3 ] . ' ' . $value; 851 | } 852 | } 853 | else 854 | { 855 | $column = $this->column_quote(preg_replace("/^(\(JSON\)\s*|#)/i", "", $key)); 856 | 857 | switch (gettype($value)) 858 | { 859 | case 'NULL': 860 | $fields[] = $column . ' = NULL'; 861 | break; 862 | 863 | case 'array': 864 | preg_match("/\(JSON\)\s*([\w]+)/i", $key, $column_match); 865 | 866 | $fields[] = $column . ' = ' . $this->quote( 867 | isset($column_match[ 0 ]) ? json_encode($value) : serialize($value) 868 | ); 869 | break; 870 | 871 | case 'boolean': 872 | $fields[] = $column . ' = ' . ($value ? '1' : '0'); 873 | break; 874 | 875 | case 'integer': 876 | case 'double': 877 | case 'string': 878 | $fields[] = $column . ' = ' . $this->fn_quote($key, $value); 879 | break; 880 | } 881 | } 882 | } 883 | 884 | return $this->exec('UPDATE ' . $this->table_quote($table) . ' SET ' . implode(', ', $fields) . $this->where_clause($where)); 885 | } 886 | 887 | public function delete($table, $where) 888 | { 889 | return $this->exec('DELETE FROM ' . $this->table_quote($table) . $this->where_clause($where)); 890 | } 891 | 892 | public function replace($table, $columns, $search = null, $replace = null, $where = null) 893 | { 894 | if (is_array($columns)) 895 | { 896 | $replace_query = array(); 897 | 898 | foreach ($columns as $column => $replacements) 899 | { 900 | foreach ($replacements as $replace_search => $replace_replacement) 901 | { 902 | $replace_query[] = $column . ' = REPLACE(' . $this->column_quote($column) . ', ' . $this->quote($replace_search) . ', ' . $this->quote($replace_replacement) . ')'; 903 | } 904 | } 905 | 906 | $replace_query = implode(', ', $replace_query); 907 | $where = $search; 908 | } 909 | else 910 | { 911 | if (is_array($search)) 912 | { 913 | $replace_query = array(); 914 | 915 | foreach ($search as $replace_search => $replace_replacement) 916 | { 917 | $replace_query[] = $columns . ' = REPLACE(' . $this->column_quote($columns) . ', ' . $this->quote($replace_search) . ', ' . $this->quote($replace_replacement) . ')'; 918 | } 919 | 920 | $replace_query = implode(', ', $replace_query); 921 | $where = $replace; 922 | } 923 | else 924 | { 925 | $replace_query = $columns . ' = REPLACE(' . $this->column_quote($columns) . ', ' . $this->quote($search) . ', ' . $this->quote($replace) . ')'; 926 | } 927 | } 928 | 929 | return $this->exec('UPDATE ' . $this->table_quote($table) . ' SET ' . $replace_query . $this->where_clause($where)); 930 | } 931 | 932 | public function get($table, $join = null, $columns = null, $where = null) 933 | { 934 | $column = $where == null ? $join : $columns; 935 | 936 | $is_single_column = (is_string($column) && $column !== '*'); 937 | 938 | $query = $this->query($this->select_context($table, $join, $columns, $where) . ' LIMIT 1'); 939 | 940 | if ($query) 941 | { 942 | $data = $query->fetchAll(PDO::FETCH_ASSOC); 943 | 944 | if (isset($data[ 0 ])) 945 | { 946 | if ($is_single_column) 947 | { 948 | return $data[ 0 ][ preg_replace('/^[\w]*\./i', "", $column) ]; 949 | } 950 | 951 | if ($column === '*') 952 | { 953 | return $data[ 0 ]; 954 | } 955 | 956 | $stack = array(); 957 | 958 | foreach ($columns as $key => $value) 959 | { 960 | if (is_array($value)) 961 | { 962 | $this->data_map(0, $key, $value, $data[ 0 ], $stack); 963 | } 964 | else 965 | { 966 | $this->data_map(0, $key, preg_replace('/^[\w]*\./i', "", $value), $data[ 0 ], $stack); 967 | } 968 | } 969 | 970 | return $stack[ 0 ]; 971 | } 972 | else 973 | { 974 | return false; 975 | } 976 | } 977 | else 978 | { 979 | return false; 980 | } 981 | } 982 | 983 | public function has($table, $join, $where = null) 984 | { 985 | $column = null; 986 | 987 | $query = $this->query('SELECT EXISTS(' . $this->select_context($table, $join, $column, $where, 1) . ')'); 988 | 989 | if ($query) 990 | { 991 | return $query->fetchColumn() === '1'; 992 | } 993 | else 994 | { 995 | return false; 996 | } 997 | } 998 | 999 | public function count($table, $join = null, $column = null, $where = null) 1000 | { 1001 | $query = $this->query($this->select_context($table, $join, $column, $where, 'COUNT')); 1002 | 1003 | return $query ? 0 + $query->fetchColumn() : false; 1004 | } 1005 | 1006 | public function max($table, $join, $column = null, $where = null) 1007 | { 1008 | $query = $this->query($this->select_context($table, $join, $column, $where, 'MAX')); 1009 | 1010 | if ($query) 1011 | { 1012 | $max = $query->fetchColumn(); 1013 | 1014 | return is_numeric($max) ? $max + 0 : $max; 1015 | } 1016 | else 1017 | { 1018 | return false; 1019 | } 1020 | } 1021 | 1022 | public function min($table, $join, $column = null, $where = null) 1023 | { 1024 | $query = $this->query($this->select_context($table, $join, $column, $where, 'MIN')); 1025 | 1026 | if ($query) 1027 | { 1028 | $min = $query->fetchColumn(); 1029 | 1030 | return is_numeric($min) ? $min + 0 : $min; 1031 | } 1032 | else 1033 | { 1034 | return false; 1035 | } 1036 | } 1037 | 1038 | public function avg($table, $join, $column = null, $where = null) 1039 | { 1040 | $query = $this->query($this->select_context($table, $join, $column, $where, 'AVG')); 1041 | 1042 | return $query ? 0 + $query->fetchColumn() : false; 1043 | } 1044 | 1045 | public function sum($table, $join, $column = null, $where = null) 1046 | { 1047 | $query = $this->query($this->select_context($table, $join, $column, $where, 'SUM')); 1048 | 1049 | return $query ? 0 + $query->fetchColumn() : false; 1050 | } 1051 | 1052 | public function action($actions) 1053 | { 1054 | if (is_callable($actions)) 1055 | { 1056 | $this->pdo->beginTransaction(); 1057 | 1058 | $result = $actions($this); 1059 | 1060 | if ($result === false) 1061 | { 1062 | $this->pdo->rollBack(); 1063 | } 1064 | else 1065 | { 1066 | $this->pdo->commit(); 1067 | } 1068 | } 1069 | else 1070 | { 1071 | return false; 1072 | } 1073 | } 1074 | 1075 | public function debug() 1076 | { 1077 | $this->debug_mode = true; 1078 | 1079 | return $this; 1080 | } 1081 | 1082 | public function error() 1083 | { 1084 | return $this->pdo->errorInfo(); 1085 | } 1086 | 1087 | public function last_query() 1088 | { 1089 | return end($this->logs); 1090 | } 1091 | 1092 | public function log() 1093 | { 1094 | return $this->logs; 1095 | } 1096 | 1097 | public function info() 1098 | { 1099 | $output = array( 1100 | 'server' => 'SERVER_INFO', 1101 | 'driver' => 'DRIVER_NAME', 1102 | 'client' => 'CLIENT_VERSION', 1103 | 'version' => 'SERVER_VERSION', 1104 | 'connection' => 'CONNECTION_STATUS' 1105 | ); 1106 | 1107 | foreach ($output as $key => $value) 1108 | { 1109 | $output[ $key ] = $this->pdo->getAttribute(constant('PDO::ATTR_' . $value)); 1110 | } 1111 | 1112 | return $output; 1113 | } 1114 | } 1115 | ?> -------------------------------------------------------------------------------- /xxooPoolServer/lib/util.php: -------------------------------------------------------------------------------- 1 | $code, 'msg' => $msg, 'data' => $data])); 7 | } 8 | 9 | function dieJson($data) 10 | { 11 | header('Content-Type:application/json; charset=utf-8'); 12 | return die(json_encode($data)); 13 | } 14 | 15 | /** 16 | * 敏感信息隐藏 17 | * @param $str 18 | */ 19 | function sensitive($str) 20 | { 21 | $length = strlen($str); 22 | $reLen = 6; 23 | if ($length < 10) { 24 | $reLen = 3; 25 | } 26 | $s = ($length / 2) - $reLen / 2; 27 | return substr_replace($str, "******", $s, $reLen); 28 | } 29 | 30 | function test(&$testData, $key, $data) 31 | { 32 | if ($_GET['devTest']) { 33 | $testData[$key] = $data; 34 | } 35 | } 36 | 37 | function slog(&$res, $str) 38 | { 39 | $res['data'] = $res['data'] . "# log=>${str} 40 | "; 41 | } 42 | 43 | function resAppend(&$res, $appendStr) 44 | { 45 | $res['data'] = $res['data'] . $appendStr; 46 | } 47 | 48 | 49 | function resRaw($data) 50 | { 51 | die($data); 52 | } 53 | 54 | 55 | function updData($key, $func) 56 | { 57 | $func(); 58 | if (USE_REDIS) { 59 | getRedis()->del($key); 60 | } 61 | } 62 | 63 | 64 | /* 65 | * key 缓存key, 66 | * val Cache对象 67 | */ 68 | function getDataByArr(array $arr) 69 | { 70 | $keys = array_keys($arr); 71 | $res = []; 72 | if (USE_REDIS) { 73 | $cacheRes = getRedis()->mget($keys); 74 | foreach ($cacheRes as $i => $r) { 75 | if ($r == false) { 76 | $cache = $arr[$keys[$i]]; 77 | $call = $cache->call; 78 | $r = $call(); 79 | $putCache = $r; 80 | if ($cache->isArray) { 81 | $putCache = json_encode($putCache); 82 | } 83 | getRedis()->set($keys[$i], $putCache, $cache->timeout); 84 | $res[$keys[$i]] = $r; 85 | } else { 86 | $res[$keys[$i]] = json_decode($r, true); 87 | } 88 | } 89 | } else { 90 | foreach ($arr as $key => $c) { 91 | $call = $c->call; 92 | $res[$key] = $call(); 93 | } 94 | } 95 | 96 | return $res; 97 | } 98 | 99 | $redis = null; 100 | function getRedis() 101 | { 102 | global $redis; 103 | if ($redis == null) { 104 | $redis = new Redis(); 105 | $redis->pconnect(REDIS_HOST, REDIS_PORT, REDIS_DEFAULT_TIME);//serverip port 106 | if (REDIS_PASS != '') { 107 | $redis->auth(REDIS_PASS); 108 | } 109 | $redis->select(REDIS_IDNEX); 110 | } 111 | return $redis; 112 | } 113 | 114 | $db = null; 115 | function getDB() 116 | { 117 | global $db; 118 | if ($db == null) { 119 | $db = new medoo([ 120 | 'database_type' => DB_TYPE, 121 | 'database_name' => DB_DATABASE, 122 | 'server' => DB_HOST, 123 | 'port' => DB_PORT, 124 | 'username' => DB_USER, 125 | 'password' => DB_PASS 126 | ]); 127 | } 128 | return $db; 129 | } 130 | 131 | function getUserKey($token) 132 | { 133 | return "user:" . $token; 134 | } 135 | 136 | function getAskForMeKey($str) 137 | { 138 | return "getAskForMe:" . $str; 139 | } 140 | 141 | function getReqDataVersion($reqMd5) 142 | { 143 | return "reqDataVersion:" . $reqMd5; 144 | } 145 | 146 | function getEnvCodeKey($env) 147 | { 148 | return "envCodeCache:${env}"; 149 | } 150 | 151 | function getPtPinCodeKey($ptPin) 152 | { 153 | return "ptPinCodeCache:${ptPin}"; 154 | } 155 | 156 | function getPtPinCoeKeyArr(array $ptPins) 157 | { 158 | $keys = []; 159 | foreach ($ptPins as $ptPin) { 160 | $keys[] = getPtPinCodeKey($ptPin); 161 | } 162 | return $keys; 163 | } 164 | 165 | function getDatetime() 166 | { 167 | return date("Y-m-d H:i:s", time()); 168 | } 169 | 170 | function dlog($key, $val) 171 | { 172 | if (USE_REDIS) { 173 | getRedis()->set($key, getDatetime() . " ${val}"); 174 | } 175 | echo getDatetime() . " ${key} ${val}\r\n"; 176 | } 177 | 178 | function getenvl($key, $default) 179 | { 180 | $res = getenv($key); 181 | return empty($res) ? $default : $res; 182 | } 183 | 184 | function asyncShell($cmd) 185 | { 186 | 187 | $url = "http://localhost:80/index.php?taskPass=" . getenvl('TASK_PASS', TASK_PASS);//接受curl请求的地址 188 | $ch = curl_init(); 189 | curl_setopt($ch, CURLOPT_URL, $url); 190 | curl_setopt($ch, CURLOPT_POST, 1); 191 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 192 | curl_setopt($ch, CURLOPT_POSTFIELDS, $cmd);//post方式数据为json格式 193 | curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5);//设置超时时间为1s 194 | $result = curl_exec($ch); 195 | curl_close($ch); 196 | return $result; 197 | } 198 | 199 | -------------------------------------------------------------------------------- /xxooPoolServer/nginx.conf: -------------------------------------------------------------------------------- 1 | # For more information on configuration, see: 2 | # * Official English Documentation: http://nginx.org/en/docs/ 3 | # * Official Russian Documentation: http://nginx.org/ru/docs/ 4 | 5 | user nginx; 6 | worker_processes auto; 7 | error_log /var/log/nginx/error.log; 8 | pid /run/nginx.pid; 9 | 10 | # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. 11 | include /usr/share/nginx/modules/*.conf; 12 | 13 | events { 14 | worker_connections 1024; 15 | } 16 | 17 | http { 18 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 19 | '$status $body_bytes_sent "$http_referer" ' 20 | '"$http_user_agent" "$http_x_forwarded_for"'; 21 | 22 | access_log /var/log/nginx/access.log main; 23 | 24 | sendfile on; 25 | tcp_nopush on; 26 | tcp_nodelay on; 27 | keepalive_timeout 65; 28 | types_hash_max_size 4096; 29 | 30 | include /etc/nginx/mime.types; 31 | default_type application/octet-stream; 32 | 33 | # Load modular configuration files from the /etc/nginx/conf.d directory. 34 | # See http://nginx.org/en/docs/ngx_core_module.html#include 35 | # for more information. 36 | include /etc/nginx/conf.d/*.conf; 37 | 38 | server { 39 | listen 80; 40 | listen [::]:80; 41 | server_name _; 42 | root /var/www/xxoo/www; 43 | 44 | # Load configuration files for the default server block. 45 | include /etc/nginx/default.d/*.conf; 46 | 47 | try_files $uri $uri/ @rewrite; 48 | location @rewrite { 49 | rewrite ^/(.*)$ /index.php?_url=/$1; 50 | } 51 | 52 | location ~ \.php$ { 53 | try_files $uri =404; 54 | fastcgi_pass 127.0.0.1:9000; 55 | fastcgi_index index.php; 56 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 57 | include fastcgi_params; 58 | ###Save user landing page to cookie: srcid for PHP files 59 | ##add_header Set-Cookie $srcid; 60 | } 61 | 62 | error_page 404 /404.html; 63 | location = /404.html { 64 | } 65 | 66 | error_page 500 502 503 504 /50x.html; 67 | location = /50x.html { 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /xxooPoolServer/www/README.md: -------------------------------------------------------------------------------- 1 | 2 | # 重要说明: 3 | 4 | 对外开放的服务请使用,nginx或者apache的 document root请使用此目录 -------------------------------------------------------------------------------- /xxooPoolServer/www/activity.php: -------------------------------------------------------------------------------- 1 | 20 ? 20 : $num; 10 | 11 | $env = $env[1]; 12 | $res = getRedis()->sRandMember(getEnvCodeKey($env), $num); 13 | dieJson(['code' => 200, 'data' => $res]); -------------------------------------------------------------------------------- /xxooPoolServer/www/admin.php: -------------------------------------------------------------------------------- 1 | '']; 7 | try { 8 | $req = getReq(); 9 | 10 | //客户端版本检测 11 | clientVersionChekc($req->reqClientVersion); 12 | //一次性拿到所有需要的数据,减少io次数 13 | getAllNeedByOnce($req); 14 | // 用户校验和获取 15 | $user = $req->user; 16 | 17 | 18 | $res = ""; 19 | $testData = []; 20 | 21 | 22 | $res = uploadAndGetCodes($req); 23 | 24 | slog($response, " ↓↓↓↓↓↓↓↓↓↓↓↓ 以下为下发的助力码 ↓↓↓↓↓↓↓↓↓↓↓↓"); 25 | resAppend($response, $res); 26 | resRaw($response['data']); 27 | } catch (Exception $e) { 28 | var_dump($e); 29 | } 30 | 31 | function clientVersionChekc($clientVersion) 32 | { 33 | global $response; 34 | if ($clientVersion !== CLIENT_VERSION) { 35 | slog($response, "================== 更新提示 ============"); 36 | slog($response, ""); 37 | slog($response, " 请更新xxoo.js版本"); 38 | slog($response, ""); 39 | slog($response, "======================================="); 40 | slog($response, ""); 41 | } 42 | } 43 | 44 | 45 | /** 46 | * 用户助力码数量校验 47 | * @param $userId 48 | * @param $limited 49 | */ 50 | function recordLimitCheck(Req $req) 51 | { 52 | $currentNum = $req->user['CURRENT_NUM']; 53 | $limited = $req->user['LIMITED']; 54 | $newAdd = 0; 55 | if ($currentNum == null) { 56 | $currentNum = 0; 57 | $command = "refreshCurrentCodeNum " . $req->user['ID'] . " " . $req->user['TOKEN']; 58 | triggerTask($command, "async.log"); 59 | } 60 | foreach ($req as $env => $d) { 61 | $newAdd = $newAdd + count($d); 62 | } 63 | if ($currentNum + $newAdd > $limited) { 64 | res(400, '当前用户token上传的助力码数量已超标'); 65 | } 66 | } 67 | 68 | /** 69 | * @param $userId 70 | * @param $data 71 | */ 72 | function uploadAndGetCodes(Req $req) 73 | { 74 | 75 | global $response; 76 | $userId = $req->user['ID']; 77 | $data = $req->reqData; 78 | 79 | //根据请求数据的版本判断是否需要更新数据库,如果需要, 80 | $newVersion = getNewVersionIfNeedUpdate($req); 81 | if ($newVersion != null) { 82 | //用户上传的助力码数量校验 83 | recordLimitCheck($req); 84 | slog($response, " 更新助力码版本 ${newVersion}"); 85 | uploadjson($userId, $data); 86 | updateAskFor($req); 87 | updateUserDataVersion($req->user, $newVersion); 88 | } 89 | 90 | //获取助力码返回给客户端 91 | $r = getCodes($req); 92 | 93 | //获取定向信息 94 | $askForMe = getAskForMe($req); 95 | 96 | slog($response, ""); 97 | if (count($askForMe) > 0) { 98 | slog($response, " ================== 定向您的用户 ============"); 99 | slog($response, ""); 100 | foreach ($askForMe as $me) { 101 | slog($response, " $me"); 102 | } 103 | } else { 104 | slog($response, " ==================== 定向您的用户 ============"); 105 | slog($response, ""); 106 | slog($response, " 当前没有用户定向你,快去邀请几个朋友定向你!!!"); 107 | } 108 | slog($response, ""); 109 | return $r; 110 | } 111 | 112 | /** 113 | * 根据传入的请求参数,判断是否有新数据版本号 114 | * @param $user 115 | * @param $req 116 | * @param $askFor 117 | * @return string|null 118 | */ 119 | function getNewVersionIfNeedUpdate(Req $req) 120 | { 121 | 122 | $res = null; 123 | 124 | if (USE_REDIS) { 125 | if ($req->areadyRequestDataVersion != null) { 126 | return null; 127 | } 128 | } 129 | $user = $req->user; 130 | $reqMd5 = $req->reqMd5; 131 | $oldMd5 = $user['DATA_VERSION']; 132 | 133 | if ($reqMd5 == $oldMd5) { 134 | $dbUpdateTime = $user['UPDATED_TIME']; 135 | if ((time() - $dbUpdateTime) > MAX_NO_UPDATE_DAY) { 136 | $res = $reqMd5; 137 | } 138 | } else { 139 | $res = $reqMd5; 140 | } 141 | if (USE_REDIS) { 142 | getRedis()->set(getReqDataVersion($reqMd5), true, 7200); 143 | } 144 | return $res; 145 | } 146 | 147 | /** 148 | * 一次性拿到所有需要的数据,减少io次数 149 | */ 150 | function getAllNeedByOnce(Req $req) 151 | { 152 | $reqDataVersionKey = getReqDataVersion($req->reqMd5); 153 | $userKey = getUserKey($req->reqToken); 154 | $getAskForMeKey = getAskForMeKey($req->reqMd5); 155 | $call = [ 156 | $reqDataVersionKey => new Cache($reqDataVersionKey, 7200, false, function () { 157 | return null; 158 | }), 159 | $userKey => new Cache($userKey, 3600, true, function () use ($req) { 160 | return getUserFromDB($req->reqToken); 161 | }), 162 | $getAskForMeKey => new Cache($getAskForMeKey, 3600, true, function () use ($req) { 163 | return getAskForMeFromDB($req); 164 | }) 165 | ]; 166 | 167 | $resArr = getDataByArr($call); 168 | $req->user = $resArr[$userKey]; 169 | $req->areadyRequestDataVersion = $resArr[$reqDataVersionKey]; 170 | $req->getAskForMe = $resArr[$getAskForMeKey]; 171 | 172 | return $req; 173 | } 174 | 175 | /** 176 | * 获取定向自己的用户 177 | */ 178 | function getAskForMe(Req $req) 179 | { 180 | return $req->getAskForMe; 181 | } 182 | 183 | function getAskForMeFromDB(Req $req) 184 | { 185 | $ptPins = array_keys($req->reqData); 186 | if (count($ptPins) > 0) { 187 | $db = getDB(); 188 | $askForMe = $db->select('user_for', ['PT_PIN', 'ASK_FOR'], ['ASK_FOR' => $ptPins]); 189 | $res = []; 190 | foreach ($askForMe as $k => $v) { 191 | $res[] = sensitive($v['PT_PIN']) . ' 助力=> ' . sensitive($v['ASK_FOR']); 192 | } 193 | return $res; 194 | } 195 | return null; 196 | } 197 | 198 | /** 199 | * 保存定向助力 200 | * @param $userId 201 | * @param $reqData 202 | * @param $askFor 203 | */ 204 | function updateAskFor(Req $req) 205 | { 206 | 207 | $userId = $req->user['ID']; 208 | updateAskForArr($userId, $req->reqData, $req->reqAskFor); 209 | } 210 | 211 | 212 | function updateAskForArr($userId, $reqData, array $askForArr) 213 | { 214 | $i = 0; 215 | foreach ($reqData as $ptPin => $codes) { 216 | if ($askForArr[$i] != null) { 217 | updateAskForByPtPin($userId, $ptPin, $askForArr[$i]); 218 | $i++; 219 | } 220 | } 221 | } 222 | 223 | 224 | function updateAskForByPtPin($userId, $ptPin, $askForPins) 225 | { 226 | $db = getDB(); 227 | //删除旧的定向 228 | $res = $db->delete('user_for', [ 229 | 'AND' => [ 230 | 'USER_ID' => $userId, 231 | 'PT_PIN' => $ptPin 232 | ] 233 | ]); 234 | //保存新定向 235 | foreach ($askForPins as $askForPin) { 236 | if ($askForPin != null 237 | && $askForPin != '' 238 | && $askForPin != 'undefined' 239 | && $askForPin != $ptPin 240 | && (!isIllegal(ILLEGAL_PT_PIN_CHAR, $askForPin))) { 241 | $db->insert('user_for', [ 242 | 'USER_ID' => $userId, 243 | 'PT_PIN' => $ptPin, 244 | 'ASK_FOR' => $askForPin, 245 | 'CREATE_TIME' => time() 246 | ]); 247 | } 248 | } 249 | } 250 | 251 | function uploadjson($userId, $data) 252 | { 253 | 254 | foreach ($data as $ptPin => $envs) { 255 | foreach ($envs as $env => $code) { 256 | if (!isIllegalCode($code)) { 257 | saveShareCode($userId, $ptPin, $env, $code); 258 | } 259 | } 260 | } 261 | return $userId; 262 | } 263 | 264 | function isIllegalCode($code) 265 | { 266 | return isIllegal(ILLEGAL_CHAR, $code); 267 | } 268 | 269 | function isIllegal(array $vars, $str) 270 | { 271 | foreach ($vars as $word) { 272 | if (strpos($str, $word) !== false) { 273 | return true; 274 | } 275 | } 276 | return false; 277 | } 278 | 279 | function saveShareCode($userId, $ptPin, $env, $code) 280 | { 281 | if ($code == '' || $code == null) { 282 | return false; 283 | } 284 | $db = getDB(); 285 | //先尝试修改,修改成功则表示已经存在 286 | $data = [ 287 | 'USER_ID' => $userId, 288 | 'PT_PIN' => $ptPin, 289 | 'ENV' => $env, 290 | 'CODE' => $code, 291 | 'CREATE_TIME' => time() 292 | ]; 293 | $n = $db->update('share_code', $data, [ 294 | 'CODE' => $code, 295 | 'ENV' => $env, 296 | 'USER_ID' => $userId 297 | ]); 298 | if ($n < 1) { 299 | $res = $db->insert('share_code', $data); 300 | $res = $res > 0 ? true : false; 301 | } else { 302 | $res = true; 303 | } 304 | 305 | if (USE_REDIS) { 306 | getRedis()->sAdd(getEnvCodeKey($env), $code); 307 | } 308 | return $res; 309 | } 310 | 311 | 312 | function getCodes(Req $req) 313 | { 314 | 315 | /** 316 | * 助力码分3大块, 317 | * 1 $reqCodes 请求里带的助力码,用户多账号自助 318 | * 2 $askForCodes 定向助力别人的助力码 319 | * 3 $dbEnvs 数据库随机获取的助力码,用户给别人随机助力 320 | * 以上三种数据,都需要是以下格式:才方便合并 321 | * [ 322 | * "env1":[ 323 | * ["askFor1Code","askFor2Code","askFor3Code"],[第二个账号的askFor],[第三个账号的askFor] 324 | * ] 325 | * ] 326 | */ 327 | 328 | //请求里的互助码 329 | $envNames = parseReqCodes($req); 330 | $reqCodes = $envNames; 331 | 332 | // 数据库的互助码 随机获取 333 | $dbEnvs = []; 334 | if ($req->random === true) { 335 | foreach ($envNames as $env => $v) { 336 | $dbEnvCodes = getCodesByEnv($env, count($req->reqData)); 337 | $dbEnvs[$env] = $dbEnvCodes; 338 | } 339 | } 340 | 341 | 342 | //请求要求的互助码 343 | $askForCodes = askFor($req, $envNames); 344 | 345 | $finalEnvCodes = mergeCodesByEnv($reqCodes, $askForCodes, $dbEnvs); 346 | 347 | $res = mergeDbEnvAndReqEnv($req, $finalEnvCodes); 348 | return $res; 349 | } 350 | 351 | function parseReqCodes(Req $req) 352 | { 353 | 354 | $res = []; 355 | if ($_GET['closeSelf'] !== 'true') { 356 | $envNames = []; 357 | foreach ($req->reqData as $ptPin => $envs) { 358 | foreach ($envs as $env => $code) { 359 | if (!isset($envNames[$env])) { 360 | $envNames[$env] = []; 361 | } 362 | if (isIllegalCode($code)) { 363 | continue; 364 | } 365 | $envNames[$env][] = $code; 366 | } 367 | } 368 | 369 | $ptPinNum = count($req->reqData); 370 | foreach ($envNames as $env => $codes) { 371 | if ($res[$env] == null) { 372 | $res[$env] = []; 373 | } 374 | for ($i = 0; $i < $ptPinNum; $i++) { 375 | $res[$env][] = $codes; 376 | } 377 | } 378 | } 379 | return $res; 380 | } 381 | 382 | /** 383 | * 384 | * @param $finalEnvCodes 385 | * [ 386 | * "env1":[ 387 | * ["askFor1Code","askFor2Code","askFor3Code"],[第二个账号的askFor],[第三个账号的askFor] 388 | * ] 389 | * ] 390 | * @param $req 391 | * @return string 392 | */ 393 | function mergeDbEnvAndReqEnv(Req $req, $finalEnvCodes) 394 | { 395 | 396 | $reqPtPins = array_keys($req->reqData); 397 | $shell = ""; 398 | foreach ($finalEnvCodes as $env => $accounts) { 399 | $sh = []; 400 | foreach ($accounts as $i => $ptpinFors) { 401 | if (count($ptpinFors) > 0) { 402 | $ptpinFors = array_diff($ptpinFors, [$req->reqData[$reqPtPins[$i]][$env], '']); 403 | $ptpinFors = array_unique($ptpinFors); 404 | $sh[] = implode("@", $ptpinFors); 405 | } 406 | } 407 | if (count($sh) > 0) { 408 | $shell = "${shell}export ${env}=\"" . implode("&", $sh) . "\"\r\n"; 409 | } 410 | } 411 | $date = date('Y年m月d日 H:i:s'); 412 | $shell = "${shell}export GENERATE_INFO=\"xxoo助力池同步时间: ${date}\"\r\n"; 413 | return $shell; 414 | } 415 | 416 | /** 417 | * 按照传入的顺序根据env合并code 418 | * 419 | * b数组参数的每个元素数据结构: 420 | * [ 421 | * "env1":[ 422 | * ["code1","code2","code3"],[第二个账号要助力的码],[第三个账号要助力的码] 423 | * ], 424 | * "env2":[ [],[],[] ] 425 | * ] 426 | * @param ...$b 427 | */ 428 | function mergeCodesByEnv(...$b) 429 | { 430 | $merges = []; 431 | foreach ($b as $el) { 432 | foreach ($el as $env => $mulitAccount) { 433 | foreach ($mulitAccount as $i => $ptPinFors) { 434 | tool3DarrayAppend($env, $i, $ptPinFors, $merges); 435 | } 436 | } 437 | } 438 | return $merges; 439 | } 440 | 441 | /** 442 | * 三位数组 追加数组值 443 | * @param $env 444 | * @param $ptPinLocation 445 | * @param array $appendArr 要追加的 446 | * @param array $arr 447 | */ 448 | function tool3DarrayAppend($env, $ptPinLocation, array $appendArr, array &$arr) 449 | { 450 | if (!isset($arr[$env])) { 451 | $arr[$env] = []; 452 | } 453 | if (!isset($arr[$env][$ptPinLocation])) { 454 | $arr[$env][$ptPinLocation] = []; 455 | } 456 | $old = $arr[$env][$ptPinLocation]; 457 | $arr[$env][$ptPinLocation] = array_merge($old, $appendArr); 458 | } 459 | 460 | /** 461 | * 定向助力 462 | * @param $askFor 463 | * @return array 464 | * [ 465 | * "env1":[ 466 | * ["askFor1Code","askFor2Code","askFor3Code"],[第二个账号的askFor],[第三个账号的askFor] 467 | * ] 468 | * ] 469 | */ 470 | function askFor(Req $req, array $allEnvName) 471 | { 472 | 473 | $ptPins = getAskForPtPins($req->reqAskFor); 474 | $codes = getAskForCodes($req, $ptPins); 475 | 476 | return askForDataConvert($req, $allEnvName, $codes); 477 | } 478 | 479 | /** 480 | * @param Req $req 481 | * @param $askPtPins 482 | * ["ptpin1","ptpin2",...] 483 | * @return 无序数据 484 | * [ 485 | * "${pt_pin}__${env}":[ 486 | * 'PT_PIN'=>'ptpin1', 487 | * 'CODE'=>'code1', 488 | * 'ENV'=>'env1' 489 | * ] 490 | * ] 491 | */ 492 | function getAskForCodes(Req $req, $askPtPins) 493 | { 494 | $db = getDB(); 495 | $dbCodes = []; 496 | $cacheCodes = []; 497 | $needFromDb = []; 498 | if (USE_REDIS) { 499 | $cacheRes = getRedis()->mget(getPtPinCoeKeyArr($askPtPins)); 500 | $size = count($askPtPins); 501 | for ($i = 0; $i < $size; $i++) { 502 | $v = $cacheRes[$i]; 503 | if ($v != false) { 504 | $v = json_decode($v, true); 505 | $v['PT_PIN'] = $askPtPins[$i]; 506 | $cacheCodes[] = $v; 507 | } else { 508 | $needFromDb[] = $askPtPins[$i]; 509 | } 510 | } 511 | } 512 | if (count($needFromDb) > 0) { 513 | $dbCodes = $db->select('share_code', ['ENV', 'CODE', 'PT_PIN'], [ 514 | 'PT_PIN' => $needFromDb 515 | ]); 516 | } 517 | $res = array_merge($dbCodes, $cacheCodes); 518 | 519 | $codes = []; 520 | foreach ($res as $i => $v) { 521 | $codes["${v['PT_PIN']}__${v['ENV']}"] = $v['CODE']; 522 | } 523 | 524 | return $codes; 525 | } 526 | 527 | function getAskForPtPins(array $reqAskFor) 528 | { 529 | $res = []; 530 | if ($reqAskFor != null) { 531 | foreach ($reqAskFor as $i => $ptPins) { 532 | $res = array_merge($res, $ptPins); 533 | } 534 | } 535 | return $res; 536 | } 537 | 538 | 539 | /** 540 | * 转换数据格式,并根据请求的askFor排序 541 | * @param codes 542 | * [ 543 | * "${pt_pin}__${env}":[ 544 | * 'PT_PIN'=>'ptpin1', 545 | * 'CODE'=>'code1', 546 | * 'ENV'=>'env1' 547 | * ] 548 | * ] 549 | * 转成: 550 | * [ 551 | * "env1":[ 552 | * ["askFor1Code","askFor2Code","askFor3Code"],[第二个账号的askFor],[第三个账号的askFor] 553 | * ] 554 | * ] 555 | */ 556 | function askForDataConvert(Req $req, $allEnvName, array $codes) 557 | { 558 | 559 | $res = []; 560 | if (count($req->reqAskFor) < 1) { 561 | return $res; 562 | } 563 | 564 | foreach ($allEnvName as $env => $v) { 565 | $i = 0; 566 | foreach ($req->reqData as $ptPin => $v2) { 567 | $askOrder = $req->reqAskFor[$i]; 568 | $accountAskCodes = []; 569 | foreach ($askOrder as $askPtPin) { 570 | $accountAskCodes[] = $codes["${askPtPin}__${env}"]; 571 | 572 | } 573 | tool3DarrayAppend($env, $i, $accountAskCodes, $res); 574 | $i++; 575 | } 576 | } 577 | 578 | return $res; 579 | } 580 | 581 | 582 | /** 583 | * 从数据库/缓存 随机获取对应env下的互助码 584 | * @param $ptPinNum 账户个数,用于动态判断随机获取多少个助力码 585 | */ 586 | function getCodesByEnv($env, $ptPinNum) 587 | { 588 | 589 | $codes = []; 590 | $canHelpNum = DEFAULT_GET_CODE_NUM * $ptPinNum; 591 | $canHelpNum = $canHelpNum > 50 ? 50 : $canHelpNum;//部分人可能会使用大量账户,获取太多助力码影响性能,所以,限制下 592 | if (USE_REDIS) { 593 | $res = getRedis()->sRandMember(getEnvCodeKey($env), $canHelpNum); 594 | foreach ($res as $k => $code) { 595 | $codes[] = $code; 596 | } 597 | } else { 598 | $db = getDB(); 599 | $argEnv = $db->quote($env); 600 | $sql = <<query($sql . " ORDER BY ${randFunc} limit " . $canHelpNum)->fetchAll(); 605 | foreach ($res as $r) { 606 | $codes[] = $r['CODE']; 607 | } 608 | } 609 | triggerRefershEnvCodeCache($env); 610 | $codes = array_unique($codes); 611 | return array_chunk($codes, $canHelpNum / $ptPinNum); 612 | } 613 | 614 | /** 615 | * 触发输入根据环境变量刷入助力码到缓存中 616 | * @param $env 617 | */ 618 | function triggerRefershEnvCodeCache($env) 619 | { 620 | $needUpdateCodeCaches = getRedis()->get("envCodeNeedUpdate:${env}"); 621 | if ($needUpdateCodeCaches == null) { 622 | triggerTask("refresh ${env}", "refresh.log"); 623 | getRedis()->set("envCodeNeedUpdate:${env}", true, 7200 + rand(0, 30) * 120); 624 | } 625 | } 626 | 627 | function triggerTask($command, $log) 628 | { 629 | asyncShell("php ../data.php ${command}"); 630 | //pclose(popen("php ../data.php ${command} >> ../${log} 2>&1 &", 'r')); 631 | } 632 | 633 | //-------------------------------------------------- user 操作相关 ---------------------------------------- 634 | function getUserFromDB($token) 635 | { 636 | 637 | $db = getDB(); 638 | $user = $db->select('user', ['ID', 'LIMITED', 'TOKEN', 'DATA_VERSION', 'UPDATED_TIME', 'CURRENT_NUM'], [ 639 | 'TOKEN' => $token 640 | ]); 641 | if (count($user) < 1) { 642 | res(400, 'token不存在'); 643 | } 644 | return $user[0]; 645 | 646 | } 647 | 648 | 649 | function updateUserDataVersion($user, $dataVersion) 650 | { 651 | updData("user:${user['TOKEN']}", function () use ($user, $dataVersion) { 652 | global $db, $response; 653 | $res = $db->update('user', 654 | [ 655 | 'DATA_VERSION' => $dataVersion, 656 | 'UPDATED_TIME' => time() 657 | ], 658 | [ 659 | 'ID' => $user['ID'] 660 | ]); 661 | slog($response, " 数据更新状态:" . json_encode($res)); 662 | }); 663 | } 664 | 665 | /** 666 | * 解析请求参数 667 | * @return Req 668 | */ 669 | function getReq() 670 | { 671 | 672 | $req = new Req( 673 | $_GET[TOKEN_PARAMETER_NAME], 674 | file_get_contents("php://input"), 675 | $_GET['clientVersion'] 676 | ); 677 | //是否随机获取助力码 678 | $req->random = $_GET['random'] !== 'false'; 679 | 680 | //定向助力解析 681 | $askFor = $_GET['askFor']; 682 | $askForMulit = explode(";", $askFor); 683 | if (count($askForMulit) > 1) { 684 | $askForMulitArr = []; 685 | foreach ($askForMulit as $k => $askForArr) { 686 | $askForMulitArr[] = explode("@", $askForArr); 687 | } 688 | $req->reqAskFor = $askForMulitArr; 689 | } else { 690 | //旧的定向助力转新定向助力 691 | $askForPins = explode("@", $askFor); 692 | $oldReqAskFor = []; 693 | foreach ($req->reqData as $reqPtPin => $v) { 694 | $oldReqAskFor[] = $askForPins; 695 | } 696 | $req->reqAskFor = $oldReqAskFor; 697 | } 698 | $req->reqAskForRaw = $askFor; 699 | 700 | //请求参数的md5值 701 | $reqMd5 = md5($req->reqBody . $req->reqAskForRaw); 702 | $req->reqMd5 = $reqMd5; 703 | return $req; 704 | } 705 | 706 | function reqCheck() 707 | { 708 | if (!empty($_GET['taskPass']) && $_GET['taskPass'] == getenvl('TASK_PASS', TASK_PASS)) { 709 | ignore_user_abort(true); 710 | set_time_limit(0); 711 | $task = file_get_contents("php://input"); 712 | $f = popen($task, 'r'); 713 | do { 714 | $r = fgets($f); 715 | echo $r . "\r\n"; 716 | if (empty($r)) { 717 | break; 718 | } 719 | } while (true); 720 | die(); 721 | } 722 | 723 | if ($_REQUEST['_url'] != null) { 724 | require_once __DIR__ . '/activity.php'; 725 | die(); 726 | } 727 | } 728 | 729 | 730 | class Req 731 | { 732 | 733 | /** 734 | * 不同账号定向助力不同的用户,根据索引和reqData的索引对应 735 | * @var null 736 | * [ 737 | * ["ptpin11","ptpin2"], 738 | * ["ptpin33","ptpin44"] 739 | * ] 740 | */ 741 | public $reqAskFor = null; 742 | 743 | /** 744 | * @var null 745 | * [ 746 | * "ptpin1":[ 747 | * "env1"=>"xxx", 748 | * "env2"=>"xxx" 749 | * ], 750 | * "ptpin2":[ 751 | * "env1"=>"xxx", 752 | * "env2"=>"xxx" 753 | * ] 754 | * ] 755 | */ 756 | public $reqData = null; 757 | /** 758 | * @var null 普通字符串 759 | */ 760 | public $reqToken = null; 761 | public $reqClientVersion = null; 762 | 763 | /** 764 | * @var null 请求体里的原始数据 765 | */ 766 | public $reqBody = null; 767 | /** 768 | * 769 | */ 770 | public $reqAskForRaw = null; 771 | 772 | /** 773 | * @var null 用户表对象 774 | */ 775 | public $user = null; 776 | 777 | /** 778 | * @var null 根据请求参数生成的md5值,如果客户端请求的此参数一直没变,那么定向数据 779 | */ 780 | public $reqMd5 = null; 781 | 782 | public $areadyRequestDataVersion = null; 783 | 784 | public $getAskForMe = null; 785 | 786 | public $random = null; 787 | 788 | 789 | function __construct($reqToken, $reqData, $clientVersion) 790 | { 791 | $this->reqToken = $reqToken; 792 | $this->reqData = json_decode($reqData, true); 793 | $this->reqClientVersion = $clientVersion; 794 | $this->reqBody = $reqData; 795 | } 796 | } 797 | 798 | class Cache 799 | { 800 | public $key; 801 | public $timeout; 802 | public $call; 803 | public $isArray; 804 | 805 | function __construct($key, $timeout, $isArray, $call) 806 | { 807 | $this->key = $key; 808 | $this->timeout = $timeout; 809 | $this->call = $call; 810 | $this->isArray = $isArray; 811 | } 812 | } -------------------------------------------------------------------------------- /xxooPoolServer/www/startup.php: -------------------------------------------------------------------------------- 1 | count('share_code'); 10 | echo "数据库:正常\r\n"; 11 | } catch (Exception $e) { 12 | $err[] = $e; 13 | echo "数据库:异常\r\n"; 14 | $res = true; 15 | } 16 | try { 17 | $redistest = getRedis()->set("test", "test"); 18 | echo "缓存:正常\r\n"; 19 | } catch (Exception $e) { 20 | echo "缓存:异常\r\n"; 21 | $err[] = $e; 22 | $res = false; 23 | } 24 | 25 | if ($res == false) { 26 | echo "异常\r\n"; 27 | var_dump($err); 28 | }else{ 29 | "已正常启动"; 30 | } -------------------------------------------------------------------------------- /xxooPoolServer/xxoo.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | xxoo: 4 | container_name: 'xxoo' 5 | image: "kkqqhh/xxoo:latest" 6 | ports: 7 | - "889:80" 8 | stdin_open: true 9 | tty: true 10 | environment: 11 | DB_HOST: 'mariadb' 12 | DB_PORT: '3306' 13 | DB_USER: 'xxoo' # 需要和 MARIADB_USER 参数一致 14 | DB_PASS: 'testpass' # 需要 和 MARIADB_PASSWORD 一致 15 | DB_ROOT_PASS: 'testrootpass' # 需要和 MARIADB_ROOT_PASSWORD 参数一致 16 | DB_DATABASE: 'xxoo_pool' 17 | REDIS_HOST: 'redis' 18 | REDIS_PORT: '6379' 19 | TASK_PASS: 'testpass' # 异步触发任务密码,请勿泄露,运行前修改 20 | depends_on: 21 | - redis 22 | - mariadb 23 | redis: 24 | container_name: 'redis' 25 | image: "redis:latest" 26 | mariadb: 27 | container_name: 'mariadb' 28 | image: "mariadb:latest" 29 | environment: 30 | MARIADB_USER: 'xxoo' 31 | MARIADB_PASSWORD: 'testpass' 32 | MARIADB_ROOT_PASSWORD: 'testrootpass' 33 | volumes: 34 | - ./xxoo-mariadb-data:/var/lib/mysql --------------------------------------------------------------------------------