├── .github
└── dependabot.yml
├── .gitignore
├── LICENSE
├── README.md
├── client
├── README.md
├── aischedule
│ ├── aischedule.go
│ ├── model.go
│ ├── table.go
│ └── util.go
├── go.mod
├── go.sum
├── health
│ ├── health.go
│ ├── info.go
│ ├── login.go
│ ├── model.go
│ ├── record.go
│ ├── status.go
│ └── util.go
├── jw
│ ├── common.go
│ ├── jw.go
│ ├── login.go
│ ├── model.go
│ ├── parser.go
│ ├── pub_lookxl.go
│ ├── pub_znpk.go
│ ├── stu_jxjh.go
│ ├── stu_wsxk.go
│ ├── stu_xscj.go
│ ├── stu_xsxj.go
│ ├── sys_banner.go
│ ├── sys_xscj.go
│ ├── sys_znpk.go
│ └── util.go
├── jwc
│ └── jwc.go
├── main.go
├── sec
│ ├── home.go
│ ├── login.go
│ ├── model.go
│ ├── portal.go
│ ├── sec.go
│ └── util.go
├── util
│ └── util.go
└── zhyd
│ ├── electricity.go
│ ├── login.go
│ ├── model.go
│ ├── util.go
│ └── zhyd.go
├── go.mod
└── go.sum
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "gomod"
9 | directory: "/"
10 | schedule:
11 | interval: "daily"
12 | open-pull-requests-limit: 10
13 |
14 | - package-ecosystem: "github-actions"
15 | directory: "/"
16 | schedule:
17 | interval: "daily"
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | .env
3 | .idea
4 |
5 | # log
6 | *.log
7 |
8 | # for docs
9 | node_modules
10 | .temp
11 | .cache
12 | dist
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 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 Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published by
637 | the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Oh My LIT (吾之洛理)
2 |
3 | > just for "All In One"...
4 |
5 | [](https://goreportcard.com/badge/github.com/icepie/oh-my-lit)
6 | [](https://github.com/icepie/oh-my-lit/blob/dev/LICENSE)
7 | [](https://jq.qq.com/?_wv=1027&k=lz0XyN86)
8 | [](https://t.me/lit_edu)
9 |
10 | ## Document
11 |
12 | [godoc](https://pkg.go.dev/github.com/icepie/oh-my-lit)
13 |
14 | ## Link
15 |
16 | [oh-my-lit/client](https://github.com/icepie/oh-my-lit/tree/main/client) - The core library
17 |
18 | [lit-edu-go](https://github.com/icepie/oh-my-lit/tree/main-outdate) - The project's predecessor
19 |
20 | ## License
21 |
22 | [AGPLv3](https://github.com/icepie/lit-edu-go/blob/main/LICENSE)
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # Oh My LIT Client
2 |
3 | > The core library for Oh My LIT :)
4 |
5 | ## Installation
6 |
7 | ```bash
8 | $ go get -u github.com/icepie/oh-my-lit/client
9 | ```
10 |
11 | ## Todo
12 |
13 | - [x] sec
14 |
15 | - [x] jw
16 |
17 | - [x] zhyd
18 |
19 | - [x] health
20 |
--------------------------------------------------------------------------------
/client/aischedule/aischedule.go:
--------------------------------------------------------------------------------
1 | package aischedule
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/go-resty/resty/v2"
7 | )
8 |
9 | var (
10 | AISchedulParserURL = "https://open-schedule.ai.xiaomi.com/api/schedule/parser"
11 | // AIScheduleTBID = "45625"
12 | AIScheduleTableURL = "https://i.ai.mi.com/course-multi/table"
13 | AIScheduleTablesURL = "https://i.ai.mi.com/course-multi/tables"
14 | AIScheduleFeedbackURL = "https://open-schedule.ai.xiaomi.com/api/feedback"
15 | AIScheduleCourseURL = "https://i.ai.mi.com/course-multi/courseInfos"
16 | AIScheduleShareURL = "https://i.ai.mi.com/course-multi/shareToken"
17 | AIScheduleImportURL = "https://i.ai.mi.com/h5/precache/ai-schedule"
18 |
19 | // 默认课表风格
20 | StyleList = []ScheduleStyle{{Color: "#00A6F2", Background: "#E5F4FF"},
21 | {Color: "#FC6B50", Background: "#FDEBDE"},
22 | {Color: "#3CB3C8", Background: "#DEFBF8"},
23 | {Color: "#7D7AEA", Background: "#EDEDFF"},
24 | {Color: "#FF9900", Background: "#FCEBCD"},
25 | {Color: "#EF5B75", Background: "#FFEFF0"},
26 | {Color: "#5B8EFF", Background: "#EAF1FF"},
27 | {Color: "#F067BB", Background: "#FFEDF8"},
28 | {Color: "#29BBAA", Background: "#E2F8F3"},
29 | {Color: "#CBA713", Background: "#FFF8C8"},
30 | {Color: "#B967E3", Background: "#F9EDFF"},
31 | {Color: "#6E8ADA", Background: "#F3F2FD"},
32 | {Color: "#00A6F2", Background: "#E5F4FF"},
33 | {Color: "#FC6B50", Background: "#FDEBDE"},
34 | {Color: "#3CB3C8", Background: "#DEFBF8"}}
35 | )
36 |
37 | type AIScheduleUser struct {
38 | UserID int64
39 | DeviceID string // 最重要
40 | Client *resty.Client
41 | }
42 |
43 | // NewHealthUser 新建健康平台用户
44 | func NewAISchedule() *AIScheduleUser {
45 |
46 | var u AIScheduleUser
47 |
48 | u.Client = resty.New()
49 |
50 | // u.Client.SetDebug(true)
51 | // u.Client.SetHeaders(MainHeaders)
52 | u.Client.SetTimeout(5 * time.Second)
53 |
54 | return &u
55 | }
56 |
57 | // SetUserID 设置用户ID
58 | func (u *AIScheduleUser) SetUserID(userID int64) *AIScheduleUser {
59 | u.UserID = userID
60 | return u
61 | }
62 |
63 | // SetDeviceID 设置密码
64 | func (u *AIScheduleUser) SetDeviceID(deviceID string) *AIScheduleUser {
65 | u.DeviceID = deviceID
66 | return u
67 | }
68 |
--------------------------------------------------------------------------------
/client/aischedule/model.go:
--------------------------------------------------------------------------------
1 | package aischedule
2 |
3 | // ScheduleStyle 课表风格
4 | type ScheduleStyle struct {
5 | Color string `json:"color"`
6 | Background string `json:"background"`
7 | }
8 |
9 | // Ret 返回结构
10 | type Ret struct {
11 | Code int64 `json:"code"`
12 | Data interface{} `json:"data"`
13 | Desc string `json:"desc"`
14 | }
15 |
16 | type CreateRet struct {
17 | Code int64 `json:"code"`
18 | Data int64 `json:"data"`
19 | Desc string `json:"desc"`
20 | }
21 |
22 | type ChangeRet struct {
23 | Code int64 `json:"code"`
24 | Data bool `json:"data"`
25 | Desc string `json:"desc"`
26 | }
27 |
28 | type AddRet struct {
29 | Code int64 `json:"code"`
30 | Data []int64 `json:"data"`
31 | Desc string `json:"desc"`
32 | }
33 |
34 | type ShareRet struct {
35 | Code int64 `json:"code"`
36 | Data struct {
37 | DueTime int64 `json:"dueTime"`
38 | Token string `json:"token"`
39 | } `json:"data"`
40 | Desc string `json:"desc"`
41 | }
42 |
43 | type ParseRet struct {
44 | TBID string `json:"tb_id"`
45 | ParserRet string `json:"parserRet"`
46 | }
47 |
48 | type GetCourseRet struct {
49 | Code int64 `json:"code"`
50 | Data struct {
51 | Courses []AppCourse `json:"courses"`
52 | } `json:"data"`
53 | Desc string `json:"desc"`
54 | }
55 |
56 | type TableSetting struct {
57 | AfternoonNum int64 `json:"afternoonNum"`
58 | Extend string `json:"extend"`
59 | ID int64 `json:"id"`
60 | IsWeekend int64 `json:"isWeekend"`
61 | MorningNum int64 `json:"morningNum"`
62 | NightNum int64 `json:"nightNum"`
63 | PresentWeek int64 `json:"presentWeek"`
64 | School string `json:"school"`
65 | Sections string `json:"sections"`
66 | Speak int64 `json:"speak"`
67 | StartSemester string `json:"startSemester"`
68 | TotalWeek int64 `json:"totalWeek"`
69 | WeekStart int64 `json:"weekStart"`
70 | }
71 |
72 | type AppCourse struct {
73 | AttendTime int64 `json:"attendTime"`
74 | CreateTime int64 `json:"createTime"`
75 | CtID int64 `json:"ctId"`
76 | Day int64 `json:"day"`
77 | Extend string `json:"extend"`
78 | ID int64 `json:"id"`
79 | Name string `json:"name"`
80 | Position string `json:"position"`
81 | SectionList []interface{} `json:"sectionList"`
82 | Sections string `json:"sections"`
83 | Style string `json:"style"`
84 | Teacher string `json:"teacher"`
85 | UpdateTime int64 `json:"updateTime"`
86 | Weeks string `json:"weeks"`
87 | }
88 |
89 | type Table struct {
90 | Courses []interface{} `json:"courses"`
91 | Current int64 `json:"current"`
92 | ID int64 `json:"id"`
93 | Name string `json:"name"`
94 | Setting struct {
95 | AfternoonNum int64 `json:"afternoonNum"`
96 | Confirm int64 `json:"confirm"`
97 | CreateTime int64 `json:"createTime"`
98 | Extend string `json:"extend"`
99 | ID int64 `json:"id"`
100 | IsWeekend int64 `json:"isWeekend"`
101 | MorningNum int64 `json:"morningNum"`
102 | NightNum int64 `json:"nightNum"`
103 | PresentWeek int64 `json:"presentWeek"`
104 | School string `json:"school"`
105 | SectionTimes string `json:"sectionTimes"`
106 | Speak int64 `json:"speak"`
107 | StartSemester string `json:"startSemester"`
108 | TotalWeek int64 `json:"totalWeek"`
109 | UpdateTime int64 `json:"updateTime"`
110 | WeekStart int64 `json:"weekStart"`
111 | } `json:"setting"`
112 | }
113 |
114 | type Schedule struct {
115 | CourseInfos []struct {
116 | Day int `json:"day"`
117 | Name string `json:"name"`
118 | Position string `json:"position"`
119 | Sections []Section `json:"sections"`
120 | Teacher string `json:"teacher"`
121 | Weeks []int `json:"weeks"`
122 | } `json:"courseInfos"`
123 | SectionTimes []struct {
124 | EndTime string `json:"endTime"`
125 | Section int `json:"section"`
126 | StartTime string `json:"startTime"`
127 | } `json:"sectionTimes"`
128 | }
129 |
130 | type Section struct {
131 | Section int `json:"section"`
132 | }
133 |
134 | type Course struct {
135 | Day int64 `json:"day"`
136 | Name string `json:"name"`
137 | Position string `json:"position"`
138 | Sections string `json:"sections"`
139 | Style string `json:"style"`
140 | Teacher string `json:"teacher"`
141 | Weeks string `json:"weeks"`
142 | }
143 |
--------------------------------------------------------------------------------
/client/aischedule/table.go:
--------------------------------------------------------------------------------
1 | package aischedule
2 |
3 | import (
4 | "encoding/base64"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "strconv"
9 | )
10 |
11 | // GetTables 获取课程列表
12 | func (u *AIScheduleUser) GetTables() (tables []Table, err error) {
13 |
14 | var result Ret
15 |
16 | _, err = u.Client.R().
17 | SetQueryParams(map[string]string{
18 | "userId": strconv.FormatInt(u.UserID, 10),
19 | "deviceId": u.DeviceID,
20 | }).
21 | SetResult(&result).
22 | Get(AIScheduleTablesURL)
23 |
24 | if err != nil {
25 | return
26 | }
27 |
28 | if result.Code != 0 {
29 | err = errors.New(result.Desc)
30 | return
31 | }
32 |
33 | byteData, _ := json.Marshal(result.Data)
34 | err = json.Unmarshal(byteData, &tables)
35 | if err != nil {
36 | return
37 | }
38 |
39 | return
40 | }
41 |
42 | // GetTables 获取课程列表
43 | func (u *AIScheduleUser) GetTable(id int64) (courses []AppCourse, err error) {
44 |
45 | var result GetCourseRet
46 |
47 | _, err = u.Client.R().
48 | SetQueryParams(map[string]string{
49 | "userId": strconv.FormatInt(u.UserID, 10),
50 | "deviceId": u.DeviceID,
51 | "ctId": strconv.FormatInt(id, 10),
52 | }).
53 | SetResult(&result).
54 | Get(AIScheduleTableURL)
55 |
56 | if err != nil {
57 | return
58 | }
59 |
60 | if result.Code != 0 {
61 | err = errors.New(result.Desc)
62 | return
63 | }
64 |
65 | courses = result.Data.Courses
66 |
67 | return
68 | }
69 |
70 | // DeleteTable 删除课表
71 | func (u *AIScheduleUser) DeleteTable(id int64) (err error) {
72 |
73 | type deleteReq struct {
74 | SID int64 `json:"sId"`
75 | DeviceID string `json:"deviceId"`
76 | CtID int64 `json:"ctId"`
77 | UserID int64 `json:"userId"`
78 | }
79 |
80 | req := deleteReq{SID: id, CtID: id, DeviceID: u.DeviceID, UserID: u.UserID}
81 |
82 | var result ChangeRet
83 |
84 | _, err = u.Client.R().
85 | SetBody(req).
86 | SetResult(&result).
87 | Delete(AIScheduleTableURL)
88 |
89 | if err != nil {
90 | return
91 | }
92 |
93 | if result.Code != 0 {
94 | err = errors.New(result.Desc)
95 | }
96 |
97 | return
98 |
99 | }
100 |
101 | // CreateTable 创建课表
102 | func (u *AIScheduleUser) CreateTable(name string, isCurrent bool) (id int64, err error) {
103 |
104 | var result CreateRet
105 |
106 | type createReq struct {
107 | Current int64 `json:"current"`
108 | DeviceID string `json:"deviceId"`
109 | Name string `json:"name"`
110 | UserID int64 `json:"userId"`
111 | }
112 |
113 | req := createReq{Current: 0, DeviceID: u.DeviceID, Name: name, UserID: u.UserID}
114 |
115 | if isCurrent {
116 | req.Current = 1
117 | }
118 |
119 | _, err = u.Client.R().
120 | SetBody(req).
121 | SetResult(&result).
122 | Post(AIScheduleTableURL)
123 |
124 | if err != nil {
125 | return
126 | }
127 |
128 | if result.Code != 0 {
129 | err = errors.New(result.Desc)
130 | return
131 | }
132 |
133 | id = result.Data
134 |
135 | return
136 | }
137 |
138 | // EditTable 修改课表
139 | func (u *AIScheduleUser) EditTable(name string, id int64, setting TableSetting) (err error) {
140 |
141 | type editReq struct {
142 | CtID int64 `json:"ctId"`
143 | DeviceID string `json:"deviceId"`
144 | Name string `json:"name"`
145 | Setting TableSetting `json:"setting"`
146 | UserID int64 `json:"userId"`
147 | }
148 |
149 | req := editReq{Setting: setting, DeviceID: u.DeviceID, Name: name, UserID: u.UserID, CtID: id}
150 | req.Setting.ID = id
151 |
152 | var ret ChangeRet
153 |
154 | _, err = u.Client.R().
155 | SetBody(&req).
156 | SetResult(&ret).
157 | Put(AIScheduleTableURL)
158 |
159 | if err != nil {
160 | return
161 | }
162 |
163 | if !ret.Data {
164 | err = errors.New(ret.Desc)
165 | }
166 |
167 | return
168 | }
169 |
170 | // AddCourses 课表添加课程
171 | func (u *AIScheduleUser) AddCourses(id int64, courses []AppCourse) (err error) {
172 |
173 | var ret AddRet
174 |
175 | type addCoursesReq struct {
176 | Courses []AppCourse `json:"courses"`
177 | CtID int64 `json:"ctId"`
178 | DeviceID string `json:"deviceId"`
179 | UserID int64 `json:"userId"`
180 | }
181 |
182 | req := addCoursesReq{Courses: courses, CtID: id, DeviceID: u.DeviceID, UserID: u.UserID}
183 |
184 | _, err = u.Client.R().
185 | SetBody(&req).
186 | SetResult(&ret).
187 | Post(AIScheduleCourseURL)
188 |
189 | if err != nil {
190 | return
191 | }
192 |
193 | if ret.Code != 0 {
194 | err = errors.New(ret.Desc)
195 | return
196 | }
197 |
198 | return
199 |
200 | }
201 |
202 | // ParseTable 解析课表
203 | func (u *AIScheduleUser) ParseTable(tbID int64, html string) (schedule Schedule, err error) {
204 |
205 | type parseReq struct {
206 | Html string `json:"html"`
207 | TBID string `json:"tb_id"`
208 | }
209 |
210 | req := parseReq{Html: strconv.Quote(html), TBID: strconv.FormatInt(tbID, 10)}
211 |
212 | var ret ParseRet
213 |
214 | _, err = u.Client.R().
215 | SetBody(&req).
216 | SetResult(&ret).
217 | Post(AISchedulParserURL)
218 |
219 | if err != nil {
220 | return
221 | }
222 |
223 | err = json.Unmarshal([]byte(ret.ParserRet), &schedule)
224 | if err != nil {
225 | return
226 | }
227 |
228 | return schedule, nil
229 |
230 | }
231 |
232 | // ShareTable 分享课表
233 | func (u *AIScheduleUser) ShareTable(ctId int64) (url string, duetime int64, err error) {
234 |
235 | var ret ShareRet
236 |
237 | _, err = u.Client.R().
238 | SetQueryParams(map[string]string{
239 | "ctId": strconv.FormatInt(ctId, 10),
240 | "userId": strconv.FormatInt(u.UserID, 10),
241 | "deviceId": u.DeviceID,
242 | }).
243 | SetResult(&ret).
244 | Get(AIScheduleShareURL)
245 |
246 | if err != nil {
247 | return
248 | }
249 |
250 | if ret.Code != 0 {
251 | err = errors.New(ret.Desc)
252 | return
253 | }
254 |
255 | rawStr := fmt.Sprintf("%d&%s&%s&%d&%d", u.UserID, u.DeviceID, ret.Data.Token, ret.Data.DueTime, ctId)
256 |
257 | url = fmt.Sprintf("%s/#/import_schedule?linkToken=%s", AIScheduleImportURL, base64.StdEncoding.EncodeToString([]byte(rawStr)))
258 |
259 | duetime = ret.Data.DueTime
260 |
261 | return
262 |
263 | }
264 |
265 | // FeedbackTable 分享课表导入功能 theType {1: "完美", 2: "部分错误", 3: "不可用"}
266 | func (u *AIScheduleUser) FeedbackTable(feedbackType int64, tbID int64) (err error) {
267 |
268 | type feedbackReq struct {
269 | DeviceID string `json:"deviceId"`
270 | TbID int64 `json:"tb_id"`
271 | Type int64 `json:"type"`
272 | UserID int64 `json:"userId"`
273 | }
274 |
275 | if feedbackType != 1 && feedbackType != 2 && feedbackType != 3 {
276 | err = errors.New("feedback type error")
277 | return
278 | }
279 |
280 | req := feedbackReq{DeviceID: u.DeviceID, UserID: u.UserID, Type: feedbackType, TbID: tbID}
281 |
282 | _, err = u.Client.R().
283 | SetBody(&req).
284 | Post(AIScheduleFeedbackURL)
285 |
286 | return
287 |
288 | }
289 |
--------------------------------------------------------------------------------
/client/aischedule/util.go:
--------------------------------------------------------------------------------
1 | package aischedule
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "strconv"
7 | "time"
8 | )
9 |
10 | func BuildSectionsStr(s []Section) string {
11 | var sections string
12 | for _, section := range s {
13 | sections += strconv.Itoa(section.Section) + ","
14 | }
15 | return sections[:len(sections)-1]
16 |
17 | }
18 |
19 | func BuildStyle() string {
20 | rand.Seed(time.Now().UnixNano())
21 | style := StyleList[rand.Intn(len(StyleList))]
22 | return fmt.Sprintf("{\"color\":\"%s\",\"background\":\"%s\"}", style.Color, style.Background)
23 |
24 | }
25 |
26 | func BuildWeeksStr(w []int) string {
27 | var weeks string
28 | for _, week := range w {
29 | weeks += strconv.Itoa(week) + ","
30 | }
31 | return weeks[:len(weeks)-1]
32 | }
33 |
34 | // ScheduleConvertAppCourse 解析出的课表格式转换为导入的格式
35 | func ScheduleConvertAppCourse(schedule Schedule) (courses []AppCourse) {
36 | for _, course := range schedule.CourseInfos {
37 | courses = append(courses, AppCourse{
38 | Day: int64(course.Day),
39 | Name: course.Name,
40 | Position: course.Position,
41 | Sections: BuildSectionsStr(course.Sections),
42 | Style: BuildStyle(),
43 | Teacher: course.Teacher,
44 | Weeks: BuildWeeksStr(course.Weeks),
45 | })
46 | }
47 | return
48 | }
49 |
--------------------------------------------------------------------------------
/client/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/icepie/oh-my-lit/client
2 |
3 | go 1.17
4 |
5 | require (
6 | github.com/JohannesKaufmann/html-to-markdown v1.3.6
7 | github.com/PuerkitoBio/goquery v1.8.0
8 | github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394
9 | github.com/go-resty/resty/v2 v2.6.0
10 | github.com/thinkeridea/go-extend v1.3.2
11 | github.com/wumansgy/goEncrypt v0.0.0-20210730092718-e359121aa81e
12 | )
13 |
14 | require (
15 | github.com/andybalholm/cascadia v1.3.1 // indirect
16 | golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
17 | )
18 |
--------------------------------------------------------------------------------
/client/go.sum:
--------------------------------------------------------------------------------
1 | github.com/JohannesKaufmann/html-to-markdown v1.3.6 h1:i3Ma4RmIU97gqArbxZXbFqbWKm7XtImlMwVNUouQ7Is=
2 | github.com/JohannesKaufmann/html-to-markdown v1.3.6/go.mod h1:Ol3Jv/xw8jt8qsaLeSh/6DBBw4ZBJrTqrOu3wbbUUg8=
3 | github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
4 | github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
5 | github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
6 | github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
7 | github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ=
8 | github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg=
9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11 | github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=
12 | github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
13 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
14 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
15 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
16 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
17 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
19 | github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
20 | github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
21 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
22 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
23 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
24 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
25 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
26 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
27 | github.com/thinkeridea/go-extend v1.3.2 h1:0ZImRXpJc+wBNIrNEMbTuKwIvJ6eFoeuNAewvzONrI0=
28 | github.com/thinkeridea/go-extend v1.3.2/go.mod h1:xqN1e3y1PdVSij1VZp6iPKlO8I4jLbS8CUuTySj981g=
29 | github.com/wumansgy/goEncrypt v0.0.0-20210730092718-e359121aa81e h1:W01vYoGpMHQplbaoahP0XVJdw/2KpJBh6jLWaWo8+Yw=
30 | github.com/wumansgy/goEncrypt v0.0.0-20210730092718-e359121aa81e/go.mod h1:d0Tq90dl4xqZEiphQ7dAVEPxcefmY2hSiATq3BfgsVY=
31 | github.com/yuin/goldmark v1.4.14 h1:jwww1XQfhJN7Zm+/a1ZA/3WUiEBEroYFNTiV3dKwM8U=
32 | github.com/yuin/goldmark v1.4.14/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
33 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
34 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
35 | golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
36 | golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
37 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
38 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
39 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
40 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
41 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
42 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
43 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
44 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
45 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
46 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
47 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
48 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
49 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
50 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
51 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
52 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
53 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
54 |
--------------------------------------------------------------------------------
/client/health/health.go:
--------------------------------------------------------------------------------
1 | package health
2 |
3 | import (
4 | "errors"
5 | "time"
6 |
7 | "github.com/go-resty/resty/v2"
8 | )
9 |
10 | var (
11 | // MianHost
12 | MianHost = "hmgr.sec.lit.edu.cn"
13 | // MianUrl 主网址
14 | MianUrl = "http://" + MianHost + "/wms"
15 | // MuyunUrl 木云反代
16 | MuyunUrl = MianUrl + "/web/"
17 | // LoginUrl 登录接口
18 | LoginUrl = MianUrl + "/healthyLogin"
19 | // GetTokenUrl 获取 Token
20 | GetTokenUrl = MianUrl + "/getToken"
21 | // SecLoginUrl 门户统一登录接口
22 | SecLoginUrl = "http://ids.lit.edu.cn/authserver/login?service=" + GetTokenUrl
23 | // LastRecordUrl 获取上次记录
24 | LastRecordUrl = MianUrl + "/lastHealthyRecord"
25 | // FirstRecordUrl 第一次上报
26 | FirstRecordUrl = MianUrl + "/addHealthyRecord"
27 | // SecondRecordUrl 第二次上报
28 | SecondRecordUrl = MianUrl + "/addTwoHealthyRecord"
29 | // ThirdRecordUrl 第三次上报
30 | ThirdRecordUrl = MianUrl + "/addThreeHealthyRecord"
31 | // HealthyReportCount 上报统计
32 | HealthyReportCountUrl = MianUrl + "/healthyReportCount"
33 | // MainHeaders 主请求头
34 | MainHeaders = map[string]string{
35 | "Proxy-Connection": "keep-alive",
36 | "Accept": "application/json, text/plain, */*",
37 | "DNT": "1",
38 | "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36",
39 | "Content-Type": "application/json;charset=UTF-8;Access-Control-Allow-Headers",
40 | "Origin": MianUrl,
41 | "Referer": MuyunUrl,
42 | "Accept-Language": "zh-CN,zh;q=0.9",
43 | }
44 |
45 | // IdentityNameList 身份
46 | IdentityNameList = map[string]int{
47 | "student": 1000401,
48 | "teacher": 1000402,
49 | "other": 1000405,
50 | }
51 |
52 | // IdentityCodeList 身份
53 | IdentityList = map[int]string{
54 | 1000401: "student",
55 | 1000402: "teacher",
56 | 1000405: "other",
57 | }
58 |
59 | // TimeLayout 时间格式
60 | TimeLayout = "2006-01-02 15:04:05"
61 | // TimeLayoutLite 时间格式
62 | TimeLayoutLite = "2006-01-02"
63 | )
64 |
65 | // HealthUser 健康平台用户结构体
66 | type HealthUser struct {
67 | Username string
68 | Password string
69 | SecPassword string
70 | UserInfo UserInfo
71 | Client *resty.Client // 测试用
72 | }
73 |
74 | // NewHealthUser 新建健康平台用户
75 | func NewHealthUser() *HealthUser {
76 |
77 | var u HealthUser
78 |
79 | u.Client = resty.New()
80 | // u.Username = Username
81 | // u.Password = Password
82 |
83 | // u.Client.SetDebug(true)
84 | u.Client.SetHeaders(MainHeaders)
85 | u.Client.SetTimeout(5 * time.Second)
86 |
87 | return &u
88 | }
89 |
90 | // SetPassword 设置用户名
91 | func (u *HealthUser) SetUsername(username string) *HealthUser {
92 | u.Username = username
93 | return u
94 | }
95 |
96 | // SetPassword 设置密码
97 | func (u *HealthUser) SetPassword(password string) *HealthUser {
98 | u.Password = password
99 | return u
100 | }
101 |
102 | // SetSecPassword 设置门户密码
103 | func (u *HealthUser) SetSecPassword(secPassword string) *HealthUser {
104 | u.SecPassword = secPassword
105 | return u
106 | }
107 |
108 | // PerSetCooikes 访问一下反代页面
109 | func (u *HealthUser) PerSetCooikes() (err error) {
110 |
111 | // 先访问一下反代页面,获取cookie
112 | resp, err := u.Client.R().
113 | Get(MuyunUrl)
114 |
115 | if err != nil {
116 | return
117 | }
118 |
119 | if resp.IsError() {
120 | err = errors.New("fail to new the object")
121 | return
122 | }
123 |
124 | return
125 | }
126 |
--------------------------------------------------------------------------------
/client/health/info.go:
--------------------------------------------------------------------------------
1 | package health
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "time"
8 | )
9 |
10 | // GetLastRecord 获取上次上报记录
11 | func (u *HealthUser) GetLastRecord() (lastRecord LastRecord, err error) {
12 |
13 | resp, err := u.Client.R().
14 | SetQueryParams(map[string]string{
15 | "teamId": fmt.Sprint(u.UserInfo.TeamID),
16 | "userId": fmt.Sprint(u.UserInfo.UserID),
17 | }).SetResult(Result{}).
18 | Get(LastRecordUrl)
19 |
20 | if err != nil {
21 | return
22 | }
23 |
24 | r := resp.Result().(*Result)
25 |
26 | // 登陆失败
27 | if r.Code != 200 {
28 | err = errors.New(r.Msg)
29 | return
30 | }
31 |
32 | byteData, _ := json.Marshal(r.Data)
33 | err = json.Unmarshal(byteData, &lastRecord)
34 | if err != nil {
35 | return
36 | }
37 |
38 | return
39 | }
40 |
41 | // IsReportedToday 今日是否上报
42 | // rtime: 次数 [0,1,2,3] 0: 为三次是否全上报 1: 第一次 2: 第二次 3: 第三次
43 | func (u *HealthUser) IsReportedToday(rtime uint) (isReported bool, err error) {
44 |
45 | lastRecord, err := u.GetLastRecord()
46 | if err != nil {
47 | return IsReportedTodayByRaw(lastRecord, rtime)
48 | }
49 |
50 | return
51 | }
52 |
53 | // IsReportedTodayByRaw 今日是否上报
54 | // rtime: 次数 [0,1,2,3] 0: 为三次是否全上报 1: 第一次 2: 第二次 3: 第三次
55 | func IsReportedTodayByRaw(lastRecord LastRecord, rtime uint) (isReported bool, err error) {
56 |
57 | isReported = false
58 |
59 | if rtime > 3 {
60 | err = errors.New("rtime error")
61 | return
62 | }
63 |
64 | // 查询最近上报时间是否为今天 (北京时间东八区)
65 | var cstZone = time.FixedZone("CST", 8*3600) // 东八区
66 | nowTime := time.Now().In(cstZone)
67 |
68 | createTime, err := time.Parse(TimeLayout, lastRecord.CreateTime)
69 | if err != nil {
70 | return
71 | }
72 |
73 | if createTime.Format(TimeLayoutLite) == nowTime.Format(TimeLayoutLite) {
74 |
75 | if rtime == 0 {
76 | if len(lastRecord.Temperature) != 0 && len(lastRecord.TemperatureTwo) != 0 && len(lastRecord.TemperatureThree) != 0 {
77 | isReported = true
78 | }
79 |
80 | } else if rtime == 1 {
81 | if len(lastRecord.Temperature) != 0 {
82 | isReported = true
83 | }
84 | } else if rtime == 2 {
85 | if len(lastRecord.TemperatureTwo) != 0 {
86 | isReported = true
87 | }
88 | } else if len(lastRecord.TemperatureThree) != 0 {
89 | isReported = true
90 | }
91 |
92 | return
93 | }
94 |
95 | return
96 | }
97 |
--------------------------------------------------------------------------------
/client/health/login.go:
--------------------------------------------------------------------------------
1 | package health
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "net/url"
7 | "strings"
8 |
9 | "github.com/icepie/oh-my-lit/client/util"
10 | )
11 |
12 | // GetToken 根据 Cookies 获取Token信息
13 | func (u *HealthUser) GetToken() (ret UserInfo, err error) {
14 |
15 | resp, err := u.Client.R().
16 | SetResult(Result{}).
17 | Get(GetTokenUrl)
18 | if err != nil {
19 | return
20 | }
21 |
22 | r := resp.Result().(*Result)
23 |
24 | // 登陆失败
25 | if r.Code != 200 {
26 | err = errors.New(r.Msg)
27 | return
28 | }
29 |
30 | byteData, _ := json.Marshal(r.Data)
31 | err = json.Unmarshal(byteData, &u.UserInfo)
32 | if err != nil {
33 | return
34 | }
35 |
36 | ret = u.UserInfo
37 | return
38 | }
39 |
40 | // Login 登录健康平台
41 | func (u *HealthUser) Login() (err error) {
42 |
43 | if len(u.Username) == 0 {
44 | err = errors.New("empty username")
45 | return
46 | }
47 |
48 | if len(u.Password) == 0 {
49 | err = errors.New("empty password")
50 | return
51 | }
52 |
53 | // 预先设置cookies
54 | u.PerSetCooikes()
55 |
56 | resp, err := u.Client.R().
57 | SetBody(LoginParam{CardNo: u.Username, Password: getSha256(u.Password)}).
58 | SetResult(Result{}).
59 | Post(LoginUrl)
60 |
61 | if err != nil {
62 | return
63 | }
64 |
65 | r := resp.Result().(*Result)
66 |
67 | // 登陆失败
68 | if r.Code != 200 {
69 | err = errors.New(r.Msg)
70 | return
71 | }
72 |
73 | byteData, _ := json.Marshal(r.Data)
74 | err = json.Unmarshal(byteData, &u.UserInfo)
75 | if err != nil {
76 | return
77 | }
78 |
79 | u.Client.SetHeader("token", u.UserInfo.Token)
80 |
81 | return
82 | }
83 |
84 | // LoginWithSec 健康平台使用统一登陆
85 | func (u *HealthUser) LoginWithSec() (err error) {
86 |
87 | if len(u.Username) == 0 {
88 | err = errors.New("empty username")
89 | return
90 | }
91 |
92 | if len(u.SecPassword) == 0 {
93 | err = errors.New("empty sec password")
94 | return
95 | }
96 |
97 | // 禁止重定向
98 | // tmpClient := u.Client.SetRedirectPolicy(resty.NoRedirectPolicy())
99 | // u.Client.SetRedirectPolicy(resty.NoRedirectPolicy())
100 |
101 | resp, err := u.Client.R().
102 | Get(SecLoginUrl)
103 | if err != nil {
104 | return
105 | }
106 |
107 | body := resp.String()
108 |
109 | // 获取所有可需参数
110 | actionUrl, err := util.GetSubstringBetweenStringsByRE(body, `id="form" action="`, `"`)
111 | if err != nil {
112 | return
113 | }
114 |
115 | // lt, err := util.GetSubstringBetweenStringsByRE(body, `name="lt" value="`, `"`)
116 | // if err != nil {
117 | // return
118 | // }
119 |
120 | execution, err := util.GetSubstringBetweenStringsByRE(body, `name="execution" value="`, `"`)
121 | if err != nil {
122 | return
123 | }
124 |
125 | eventId, err := util.GetSubstringBetweenStringsByRE(body, `name="_eventId" value="`, `"`)
126 | if err != nil {
127 | return
128 | }
129 |
130 | salt, err := util.GetSubstringBetweenStringsByRE(body, `id="salt" value="`, `"`)
131 | if err != nil {
132 | return
133 | }
134 |
135 | // 这个地址需要html解码
136 | // decodeurl := html.UnescapeString(actionUrl)
137 |
138 | // var data = strings.NewReader("username=" + u.Username + "&password=" + u.Password + captchaParam + "<=" + lt + "&execution=" + execution + "&_eventId=" + eventId + "&rmShown=" + rmShown)
139 |
140 | dealPassword, err := loginCrypto(u.SecPassword, salt, "1234567890abcdef")
141 | if err != nil {
142 | return
143 | }
144 |
145 | req := u.Client.R().
146 | SetHeader("authority", actionUrl).
147 | SetFormData(map[string]string{
148 | "username": u.Username,
149 | "password": dealPassword,
150 | // "lt": lt,
151 | "execution": execution,
152 | "_eventId": eventId,
153 | // "salt": salt,
154 | "rememberMe": "true", // 一周内免登录 on/off
155 | "_rememberMe": "on",
156 | })
157 |
158 | // // 预定.....
159 | // if len(captcha) > 0 {
160 | // req.SetFormData(map[string]string{
161 | // "captchaResponse": captcha,
162 | // })
163 | // }
164 |
165 | resp, err = req.Post(SecLoginUrl)
166 | if err != nil {
167 | return
168 | }
169 |
170 | body = resp.String()
171 |
172 | // 判断是否有错误
173 | if strings.Contains(resp.String(), "credential.errors") {
174 | loginErrStr, _ := util.GetSubstringBetweenStringsByRE(body, `credential.errors">`, ``)
175 | err = errors.New(loginErrStr)
176 | return
177 | }
178 |
179 | mainUrl, _ := url.Parse(MianUrl)
180 |
181 | // 从Cookies获取token
182 | Cookies := u.Client.GetClient().Jar.Cookies(mainUrl)
183 |
184 | for _, cookie := range Cookies {
185 | if cookie.Name == "token" {
186 | u.Client.SetHeader("token", cookie.Value)
187 | break
188 | }
189 | }
190 |
191 | // 补全登陆信息
192 | ui, err := u.GetToken()
193 | if err != nil {
194 | return
195 | }
196 |
197 | u.UserInfo = ui
198 |
199 | return
200 | }
201 |
202 | // IsLogged 判断是否登录
203 | func (u *HealthUser) IsLogged() bool {
204 | _, err := u.GetLastRecord()
205 | return err == nil
206 | }
207 |
--------------------------------------------------------------------------------
/client/health/model.go:
--------------------------------------------------------------------------------
1 | package health
2 |
3 | // LoginParam 登陆参数
4 | type LoginParam struct {
5 | CardNo string `json:"cardNo"`
6 | Password string `json:"password"`
7 | }
8 |
9 | // Result 响应结果
10 | type Result struct {
11 | Success bool `json:"success"`
12 | Code int64 `json:"code"`
13 | Msg string `json:"msg"`
14 | Data interface{} `json:"data"`
15 | }
16 |
17 | // UserInfo 用户信息
18 | type UserInfo struct {
19 | Age int64 `json:"age"`
20 | CardNo interface{} `json:"cardNo"`
21 | ExpireTime string `json:"expireTime"`
22 | Identity int64 `json:"identity"`
23 | IsAdmin int64 `json:"isAdmin"`
24 | IsApprover interface{} `json:"isApprover"`
25 | IsGeneralAdmin int64 `json:"isGeneralAdmin"`
26 | IsReportAdmin int64 `json:"isReportAdmin"`
27 | IsReturnSchoolApprover int64 `json:"isReturnSchoolApprover"`
28 | IsTwoTemperature int64 `json:"isTwoTemperature"`
29 | LastUpdateTime string `json:"lastUpdateTime"`
30 | LocalAddress string `json:"localAddress"`
31 | LogoURL string `json:"logoUrl"`
32 | Mobile string `json:"mobile"`
33 | Name string `json:"name"`
34 | NativePlaceAddress string `json:"nativePlaceAddress"`
35 | NativePlaceCity string `json:"nativePlaceCity"`
36 | NativePlaceDistrict string `json:"nativePlaceDistrict"`
37 | NativePlaceProvince string `json:"nativePlaceProvince"`
38 | OrganizationName string `json:"organizationName"`
39 | Sex int64 `json:"sex"`
40 | TeamCity string `json:"teamCity"`
41 | TeamID int64 `json:"teamId"`
42 | TeamName string `json:"teamName"`
43 | TeamNo string `json:"teamNo"`
44 | TeamProvince string `json:"teamProvince"`
45 | Token string `json:"token"`
46 | UserID int64 `json:"userId"`
47 | UserOrganizationID int64 `json:"userOrganizationId"`
48 | }
49 |
50 | // LastRecord 上次上报结构
51 | type LastRecord struct {
52 | AbroadInfo string `json:"abroadInfo"`
53 | CaseAddress interface{} `json:"caseAddress"`
54 | ContactAddress string `json:"contactAddress"`
55 | ContactCity string `json:"contactCity"`
56 | ContactDistrict string `json:"contactDistrict"`
57 | ContactPatient string `json:"contactPatient"`
58 | ContactProvince string `json:"contactProvince"`
59 | ContactTime interface{} `json:"contactTime"`
60 | CreateTime string `json:"createTime"`
61 | CureTime interface{} `json:"cureTime"`
62 | CurrentAddress string `json:"currentAddress"`
63 | CurrentCity string `json:"currentCity"`
64 | CurrentDistrict string `json:"currentDistrict"`
65 | CurrentProvince string `json:"currentProvince"`
66 | CurrentStatus string `json:"currentStatus"`
67 | DiagnosisTime interface{} `json:"diagnosisTime"`
68 | ExceptionalCase int64 `json:"exceptionalCase"`
69 | ExceptionalCaseInfo string `json:"exceptionalCaseInfo"`
70 | FriendHealthy int64 `json:"friendHealthy"`
71 | GoHuBeiCity string `json:"goHuBeiCity"`
72 | GoHuBeiTime interface{} `json:"goHuBeiTime"`
73 | HealthyStatus int64 `json:"healthyStatus"`
74 | ID int64 `json:"id"`
75 | IsAbroad int64 `json:"isAbroad"`
76 | IsInTeamCity int64 `json:"isInTeamCity"`
77 | Isolation int64 `json:"isolation"`
78 | PeerAddress interface{} `json:"peerAddress"`
79 | PeerIsCase int64 `json:"peerIsCase"`
80 | ReportDate string `json:"reportDate"`
81 | SeekMedical int64 `json:"seekMedical"`
82 | SeekMedicalInfo string `json:"seekMedicalInfo"`
83 | SelfHealthy int64 `json:"selfHealthy"`
84 | SelfHealthyInfo string `json:"selfHealthyInfo"`
85 | SelfHealthyTime interface{} `json:"selfHealthyTime"`
86 | TeamID int64 `json:"teamId"`
87 | Temperature string `json:"temperature"`
88 | TemperatureNormal int64 `json:"temperatureNormal"`
89 | TemperatureThree string `json:"temperatureThree"`
90 | TemperatureTwo string `json:"temperatureTwo"`
91 | TravelPatient string `json:"travelPatient"`
92 | TreatmentHospitalAddress string `json:"treatmentHospitalAddress"`
93 | UserID int64 `json:"userId"`
94 | VillageIsCase int64 `json:"villageIsCase"`
95 | }
96 |
97 | // FirstRecordParam 首次上报参数结构
98 | type FirstRecordParam struct {
99 | AbroadInfo string `json:"abroadInfo"`
100 | CaseAddress interface{} `json:"caseAddress"`
101 | ContactAddress string `json:"contactAddress"`
102 | ContactCity string `json:"contactCity"`
103 | ContactDistrict string `json:"contactDistrict"`
104 | ContactLocation string `json:"contactLocation"`
105 | ContactPatient string `json:"contactPatient"`
106 | ContactProvince string `json:"contactProvince"`
107 | ContactTime interface{} `json:"contactTime"`
108 | CureTime interface{} `json:"cureTime"`
109 | CurrentAddress string `json:"currentAddress"`
110 | CurrentCity string `json:"currentCity"`
111 | CurrentDistrict string `json:"currentDistrict"`
112 | CurrentLocation string `json:"currentLocation"`
113 | CurrentProvince string `json:"currentProvince"`
114 | CurrentStatus string `json:"currentStatus"`
115 | DiagnosisTime interface{} `json:"diagnosisTime"`
116 | ExceptionalCase int64 `json:"exceptionalCase"`
117 | ExceptionalCaseInfo string `json:"exceptionalCaseInfo"`
118 | FriendHealthy int64 `json:"friendHealthy"`
119 | GoHuBeiCity string `json:"goHuBeiCity"`
120 | GoHuBeiTime interface{} `json:"goHuBeiTime"`
121 | HealthyStatus int64 `json:"healthyStatus"`
122 | IsAbroad int64 `json:"isAbroad"`
123 | IsInTeamCity int64 `json:"isInTeamCity"`
124 | IsTrip int64 `json:"isTrip"`
125 | Isolation int64 `json:"isolation"`
126 | LocalAddress string `json:"localAddress"`
127 | Mobile string `json:"mobile"`
128 | NativePlaceAddress string `json:"nativePlaceAddress"`
129 | NativePlaceCity string `json:"nativePlaceCity"`
130 | NativePlaceDistrict string `json:"nativePlaceDistrict"`
131 | NativePlaceProvince string `json:"nativePlaceProvince"`
132 | PeerAddress interface{} `json:"peerAddress"`
133 | PeerIsCase int64 `json:"peerIsCase"`
134 | ReportDate string `json:"reportDate"`
135 | SeekMedical int64 `json:"seekMedical"`
136 | SeekMedicalInfo string `json:"seekMedicalInfo"`
137 | SelfHealthy int64 `json:"selfHealthy"`
138 | SelfHealthyInfo string `json:"selfHealthyInfo"`
139 | SelfHealthyTime interface{} `json:"selfHealthyTime"`
140 | TeamID int64 `json:"teamId"`
141 | Temperature string `json:"temperature"`
142 | TemperatureNormal int64 `json:"temperatureNormal"`
143 | TemperatureThree string `json:"temperatureThree"`
144 | TemperatureTwo string `json:"temperatureTwo"`
145 | TravelPatient string `json:"travelPatient"`
146 | TreatmentHospitalAddress string `json:"treatmentHospitalAddress"`
147 | UserID int64 `json:"userId"`
148 | VillageIsCase int64 `json:"villageIsCase"`
149 | }
150 |
151 | // ExtraRecordParam 补充上报参数 (第二, 三次)
152 | type ExtraRecordParam struct {
153 | HealthyRecordID int64 `json:"healthyRecordId"`
154 | Temperature string `json:"temperature"`
155 | TemperatureNormal int64 `json:"temperatureNormal"`
156 | }
157 |
158 | // HealthyReportCount 上报统计结构
159 | type HealthyReportCount struct {
160 | NotReported int64 `json:"notReported"`
161 | RecordTotal int64 `json:"recordTotal"`
162 | Reported int64 `json:"reported"`
163 | }
164 |
--------------------------------------------------------------------------------
/client/health/record.go:
--------------------------------------------------------------------------------
1 | package health
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "time"
8 | )
9 |
10 | // FirstReportByRaw 使用原始参数进行今日第一次上报
11 | func (u *HealthUser) FirstReportByRaw(param FirstRecordParam) (err error) {
12 |
13 | body, err := json.Marshal(param)
14 | if err != nil {
15 | return
16 | }
17 |
18 | resp, err := u.Client.R().
19 | SetBody(string(body)).
20 | SetResult(Result{}).
21 | Post(FirstRecordUrl)
22 |
23 | if err != nil {
24 | return
25 | }
26 |
27 | r := resp.Result().(*Result)
28 |
29 | // 登陆失败
30 | if r.Code != 200 {
31 | err = errors.New(r.Msg)
32 | }
33 |
34 | return
35 | }
36 |
37 | // FirstReport 进行今日第一次上报
38 | func (u *HealthUser) FirstReport(firstTemp float64, secondTemp float64, thirdTemp float64) (err error) {
39 |
40 | lr, err := u.GetLastRecord()
41 | if err != nil {
42 | return
43 | }
44 |
45 | // 构建上报时间 (北京时间东八区)
46 | var cstZone = time.FixedZone("CST", 8*3600) // 东八区
47 | nowTime := time.Now().In(cstZone)
48 |
49 | param := FirstRecordParam{
50 | AbroadInfo: lr.AbroadInfo,
51 | CaseAddress: lr.CaseAddress,
52 | ContactAddress: lr.ContactAddress,
53 | ContactCity: lr.ContactCity,
54 | ContactDistrict: lr.ContactDistrict,
55 | ContactLocation: "",
56 | ContactPatient: lr.ContactPatient,
57 | ContactProvince: lr.ContactProvince,
58 | ContactTime: lr.ContactTime,
59 | CureTime: lr.CureTime,
60 | CurrentAddress: lr.CurrentAddress,
61 | CurrentCity: lr.CurrentCity,
62 | CurrentDistrict: lr.CurrentDistrict,
63 | CurrentLocation: "",
64 | CurrentProvince: lr.CurrentProvince,
65 | CurrentStatus: lr.CurrentStatus,
66 | DiagnosisTime: lr.DiagnosisTime,
67 | ExceptionalCase: lr.ExceptionalCase,
68 | ExceptionalCaseInfo: lr.ExceptionalCaseInfo,
69 | FriendHealthy: lr.FriendHealthy,
70 | GoHuBeiCity: lr.GoHuBeiCity,
71 | GoHuBeiTime: lr.GoHuBeiTime,
72 | HealthyStatus: lr.HealthyStatus,
73 | IsAbroad: lr.IsAbroad,
74 | IsInTeamCity: lr.IsInTeamCity,
75 | IsTrip: lr.IsAbroad,
76 | Isolation: lr.Isolation,
77 | LocalAddress: u.UserInfo.LocalAddress,
78 | Mobile: u.UserInfo.Mobile,
79 | NativePlaceAddress: u.UserInfo.NativePlaceAddress,
80 | NativePlaceCity: u.UserInfo.NativePlaceCity,
81 | NativePlaceDistrict: u.UserInfo.NativePlaceDistrict,
82 | NativePlaceProvince: u.UserInfo.NativePlaceProvince,
83 | PeerAddress: lr.PeerAddress,
84 | PeerIsCase: lr.PeerIsCase,
85 | ReportDate: nowTime.Format("2006-01-02"),
86 | SeekMedical: lr.SeekMedical,
87 | SeekMedicalInfo: lr.SeekMedicalInfo,
88 | SelfHealthy: lr.SelfHealthy,
89 | SelfHealthyInfo: lr.SelfHealthyInfo,
90 | SelfHealthyTime: lr.SelfHealthyTime,
91 | TeamID: lr.TeamID,
92 | Temperature: fmt.Sprint(secondTemp),
93 | TemperatureNormal: lr.TemperatureNormal,
94 | TemperatureThree: fmt.Sprint(secondTemp),
95 | TemperatureTwo: fmt.Sprint(thirdTemp),
96 | TravelPatient: lr.TravelPatient,
97 | TreatmentHospitalAddress: lr.TreatmentHospitalAddress,
98 | UserID: u.UserInfo.UserID,
99 | VillageIsCase: lr.VillageIsCase,
100 | }
101 |
102 | if secondTemp == 0 {
103 | param.TemperatureTwo = ""
104 | }
105 |
106 | if thirdTemp == 0 {
107 | param.TemperatureThree = ""
108 | }
109 |
110 | err = u.FirstReportByRaw(param)
111 |
112 | return
113 | }
114 |
115 | // SecondReportByRaw 使用原始参数进行今日第二次上报
116 | func (u *HealthUser) SecondReportByRaw(param ExtraRecordParam) (err error) {
117 |
118 | body, err := json.Marshal(param)
119 | if err != nil {
120 | return
121 | }
122 |
123 | resp, err := u.Client.R().
124 | SetBody(string(body)).
125 | SetResult(Result{}).
126 | Put(SecondRecordUrl)
127 |
128 | if err != nil {
129 | return
130 | }
131 |
132 | r := resp.Result().(*Result)
133 |
134 | // 登陆失败
135 | if r.Code != 200 {
136 | err = errors.New(r.Msg)
137 | }
138 |
139 | return
140 | }
141 |
142 | // SecondReport 进行今日第二次上报
143 | func (u *HealthUser) SecondReport(temp float64) (err error) {
144 |
145 | lr, err := u.GetLastRecord()
146 | if err != nil {
147 | return
148 | }
149 |
150 | param := ExtraRecordParam{
151 | HealthyRecordID: lr.ID,
152 | Temperature: fmt.Sprint(temp),
153 | TemperatureNormal: lr.TemperatureNormal,
154 | }
155 |
156 | err = u.SecondReportByRaw(param)
157 |
158 | return
159 |
160 | }
161 |
162 | // ThirdReportByRaw 使用原始参数进行今日第三次上报
163 | func (u *HealthUser) ThirdReportByRaw(param ExtraRecordParam) (err error) {
164 |
165 | body, err := json.Marshal(param)
166 | if err != nil {
167 | return
168 | }
169 |
170 | resp, err := u.Client.R().
171 | SetBody(string(body)).
172 | SetResult(Result{}).
173 | Put(ThirdRecordUrl)
174 |
175 | if err != nil {
176 | return
177 | }
178 |
179 | r := resp.Result().(*Result)
180 |
181 | // 登陆失败
182 | if r.Code != 200 {
183 | err = errors.New(r.Msg)
184 | }
185 |
186 | return
187 | }
188 |
189 | // ThirdReport 进行今日第三次上报
190 | func (u *HealthUser) ThirdReport(temp float64) (err error) {
191 |
192 | lr, err := u.GetLastRecord()
193 | if err != nil {
194 | return
195 | }
196 |
197 | param := ExtraRecordParam{
198 | HealthyRecordID: lr.ID,
199 | Temperature: fmt.Sprint(temp),
200 | TemperatureNormal: lr.TemperatureNormal,
201 | }
202 |
203 | err = u.ThirdReportByRaw(param)
204 |
205 | return
206 |
207 | }
208 |
--------------------------------------------------------------------------------
/client/health/status.go:
--------------------------------------------------------------------------------
1 | package health
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | )
8 |
9 | // GetHealthyReportCount 获取上报统计
10 | // identity 身份 (student, teacher, other) 如果为空字符串则为全部 , orgIDs 部门列表, createTime 时间 2021-10-27
11 | func (u *HealthUser) GetHealthyReportCount(identity string, orgIDs []uint, createTime string) (healthyReportCount HealthyReportCount, err error) {
12 |
13 | //
14 | orgIDsStr := ""
15 |
16 | for i, orgID := range orgIDsStr {
17 | orgIDsStr += fmt.Sprint(orgID)
18 | if i != len(orgIDsStr)-1 {
19 | orgIDsStr += ","
20 | }
21 | }
22 |
23 | resp, err := u.Client.R().
24 | SetQueryParams(map[string]string{
25 | "teamId": fmt.Sprint(u.UserInfo.TeamID),
26 | "userId": fmt.Sprint(u.UserInfo.UserID),
27 | "identity": fmt.Sprint(IdentityNameList[identity]),
28 | "organizationIds": orgIDsStr,
29 | "createTime": createTime,
30 | }).SetResult(Result{}).
31 | Get(HealthyReportCountUrl)
32 |
33 | if err != nil {
34 | return
35 | }
36 |
37 | r := resp.Result().(*Result)
38 |
39 | if r.Code != 200 {
40 | err = errors.New(r.Msg)
41 | return
42 | }
43 |
44 | byteData, _ := json.Marshal(r.Data)
45 | err = json.Unmarshal(byteData, &healthyReportCount)
46 | if err != nil {
47 | return
48 | }
49 |
50 | return
51 | }
52 |
--------------------------------------------------------------------------------
/client/health/util.go:
--------------------------------------------------------------------------------
1 | package health
2 |
3 | import (
4 | "crypto/sha256"
5 | "encoding/base64"
6 | "encoding/hex"
7 | "math"
8 | "math/rand"
9 | "time"
10 |
11 | "github.com/wumansgy/goEncrypt"
12 | )
13 |
14 | // 登录参数加密
15 | func loginCrypto(plain string, key string, iv string) (data string, err error) {
16 | cryptText, err := goEncrypt.AesCbcEncrypt([]byte(plain), []byte(key), []byte(iv))
17 | if err != nil {
18 | return
19 | }
20 | data = base64.StdEncoding.EncodeToString(cryptText)
21 | return
22 | }
23 |
24 | func getSha256(str string) string {
25 | h := sha256.New()
26 | h.Write([]byte(str))
27 | return hex.EncodeToString(h.Sum(nil))
28 | }
29 |
30 | // GenRandomSafeTemp 随机生成安全体温
31 | func GenRandomSafeTemp() float64 {
32 | rand.Seed(time.Now().Unix())
33 | min, max := 36.0, 37.0
34 | x := math.Pow10(1)
35 | return math.Trunc((min+rand.Float64()*(max-min))*x) / x
36 | }
37 |
--------------------------------------------------------------------------------
/client/jw/common.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | import (
4 | "errors"
5 |
6 | "strconv"
7 | "strings"
8 |
9 | "github.com/PuerkitoBio/goquery"
10 | "github.com/thinkeridea/go-extend/exunicode/exutf8"
11 | )
12 |
13 | // GetJwTime 获取当前教务时间
14 | func (u *JwUser) GetJwTime() (jwTime JwTime, err error) {
15 | body, err := u.GetBannerRpt()
16 | if err != nil {
17 | return
18 | }
19 |
20 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(body))
21 | if err != nil {
22 | return
23 | }
24 |
25 | // 在线人数
26 | onstrFull := doc.Find("span#onTimeNum").First().Text()
27 |
28 | // 去掉缩进和空格
29 | onstrq := strings.Replace(strings.Replace(strings.Replace(onstrFull, "\\t", "", -1), "\n", "", -1), "\t", "", -1)
30 | // 去掉特殊的符号
31 | onstrq = strings.Replace(onstrq, "\u00a0", "", -1)
32 | // 截取一下
33 | onlineNumstr := exutf8.RuneSubString(onstrq, 5, 10)
34 | // 尝试转为整形
35 | onlineNum, _ := strconv.Atoi(onlineNumstr)
36 |
37 | jwTime.OnlineNum = uint(onlineNum)
38 |
39 | rawData := doc.Find("span").Eq(1).Text()
40 |
41 | // 对教务时间进行处理
42 | // [2021年08月06日 星期五 2021-2022学年第一学期 [假期]]
43 | jwtimeData := strings.Fields(rawData)
44 |
45 | if len(jwtimeData) < 2 {
46 | err = errors.New("fatl to get jwtime")
47 | return
48 | }
49 |
50 | for i, subStr := range jwtimeData {
51 | if i != len(jwTime.RawData)-1 {
52 | jwTime.RawData += subStr + " "
53 | } else {
54 | jwTime.RawData += subStr
55 | }
56 | }
57 |
58 | YearStr := exutf8.RuneSubString(jwtimeData[2], 0, 4)
59 | YearNum, _ := strconv.Atoi(YearStr)
60 | jwTime.Year = uint(YearNum)
61 |
62 | if strings.Contains(jwtimeData[2], "一") {
63 | jwTime.Term = 0
64 | }
65 |
66 | if strings.Contains(jwtimeData[2], "二") {
67 | jwTime.Term = 1
68 | }
69 |
70 | if strings.Contains(jwtimeData[len(jwtimeData)-1], "假期") {
71 | jwTime.IsVacation = true
72 | } else {
73 |
74 | // 周数处理
75 | weekStr := strings.ReplaceAll(jwtimeData[3], "第", "")
76 | weekStr = strings.ReplaceAll(weekStr, "周", "")
77 |
78 | // 尝试转为整形
79 | weekNum, _ := strconv.Atoi(weekStr)
80 | jwTime.Week = uint(weekNum)
81 | }
82 |
83 | return
84 | }
85 |
86 | // GetClassID 获取班级ID
87 | func (u *JwUser) GetClassID(name string) (id string, err error) {
88 |
89 | idMap := make(map[string]string)
90 |
91 | body, err := u.GetClassSelPage()
92 | if err != nil {
93 | return
94 | }
95 |
96 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(body))
97 | if err != nil {
98 | return
99 | }
100 |
101 | sel := doc.Find("td#theXZBJ>select")
102 |
103 | sel.Find("option").Each(func(i int, option *goquery.Selection) {
104 | idMap[option.Text()] = option.AttrOr("value", "")
105 | })
106 |
107 | id = idMap[name]
108 |
109 | if len(id) == 0 {
110 | err = errors.New("not found class id")
111 | }
112 |
113 | return
114 | }
115 |
116 | // GetClassID 获取学生ID
117 | func (u *JwUser) GetStuID(name string) (id string, err error) {
118 |
119 | body, err := u.GetListXSRpt(name)
120 | if err != nil {
121 | return
122 | }
123 |
124 | if strings.Contains(body, "@") || !strings.Contains(body, "|") {
125 | err = errors.New("not found student id")
126 | return
127 | }
128 |
129 | id = body[0:12]
130 | if len(id) == 0 {
131 | err = errors.New("not found student id")
132 | }
133 |
134 | return
135 | }
136 |
137 | // 成绩排名参数构造
138 | func (u *JwUser) XscjbmLeftRptToHidKC(body string) string {
139 |
140 | doc, _ := goquery.NewDocumentFromReader(strings.NewReader(body))
141 |
142 | hid_kc := `'TTTTTTTTTTTTTTTT'`
143 |
144 | doc.Find("table#ID_Table>tbody>tr").Each(func(i int, tr *goquery.Selection) {
145 |
146 | v, isExist := tr.Find("td>input").First().Attr("value")
147 |
148 | if isExist {
149 |
150 | hid_kc = hid_kc + "," + `'` + v + `'`
151 | }
152 |
153 | })
154 |
155 | return hid_kc
156 | }
157 |
--------------------------------------------------------------------------------
/client/jw/jw.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | import (
4 | "errors"
5 | "net/url"
6 |
7 | "github.com/go-resty/resty/v2"
8 | "github.com/icepie/oh-my-lit/client/sec"
9 | )
10 |
11 | var (
12 | // MianHost
13 | MianHost = "jw.sec.lit.edu.cn"
14 | // MianUrl 主网址
15 | MianUrl = "http://" + MianHost // 120.194.42.205:9001
16 | // DefaultPath 首页
17 | DefaultPath = "/default.aspx"
18 | // LoginPath 登陆接口
19 | LoginPath = "/_data/index_LOGIN.aspx"
20 | // MenuPath 菜单接口
21 | MenuPath = "/frame/menu.aspx"
22 | // SysBannerPath 管理员菜单接口(通用)
23 | SysBannerPath = "/SYS/Main_banner.aspx"
24 | // StuMyInfoPath 学生学籍信息接口
25 | StuMyInfoPath = "/xsxj/Stu_MyInfo_RPT.aspx"
26 | // StuZXJGPath 学生正选结果
27 | StuZXJGPath = "/wsxk/stu_zxjg_rpt.aspx"
28 | // StuBYFAKCPath 学生理论课程结果
29 | StuBYFAKCPath = "/jxjh/Stu_byfakc_rpt.aspx"
30 | // StuBYFAHJPath 学生实践环节结果
31 | StuBYFAHJPath = "/jxjh/Stu_byfahj_rpt.aspx"
32 | // StuDJKSCJPath 学生等级成绩
33 | StuDJKSCJPath = "/xscj/Stu_djkscj_rpt.aspx"
34 | // SysListXSPath 管理员查询学生姓名结果
35 | SysListXSPath = "/XSCJ/Private/list_XS.aspx"
36 | // SysXSGRCJPath 管理员查询学生成绩结果
37 | SysXSGRCJPath = "/XSCJ/f_xsgrcj_rpt.aspx"
38 | // SysZNPKDayfreeSelPath 管理员查询空教室结果
39 | SysZNPKDayfreeSelPath = "/ZNPK/DayfreeSel_Rpt.aspx"
40 | // SysClassSelPath 管理员查询班级课表
41 | SysClassSelPath = "/ZNPK/ClassSel_rpt.aspx"
42 | // ClassSelPath 公用课表接口
43 | ClassSelPath = "/ZNPK/KBFB_ClassSel.aspx"
44 | // LoginBySecPath 智慧门户快速登陆
45 | LoginBySecPath = "/cas_njjz.aspx"
46 | // XscjbmLeft 有效成绩排名 left
47 | SysXscjbmLeftPath = "/XSCJ/g_xscjbm_left.aspx"
48 | // SysXscjbmRightPath 有效成绩排名 right
49 | SysXscjbmRightPath = "/XSCJ/g_xscjbm_right.aspx"
50 | // LookxlRptPath 查看校历
51 | LookxlRptPath = "/_data/index_Lookxl.aspx"
52 | // MAINFRMPath 主页
53 | MAINFRMPath = "/MAINFRM.aspx"
54 | // UA
55 | UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36"
56 | // SchoolCode 院校代号
57 | SchoolCode = "11070"
58 |
59 | // MainHeaders 主请求头
60 | MainHeaders = map[string]string{
61 | "dnt": "1",
62 | "x-requested-with": "XMLHttpRequest",
63 | "sec-ch-ua-mobile": "1",
64 | "User-Agent": UA,
65 | "sec-fetch-site": "same-origin",
66 | "sec-fetch-mode": "cors",
67 | "sec-fetch-dest": "empty",
68 | "Accept-Language": "zh-CN,zh;q=0.9",
69 | }
70 |
71 | /* 身份
72 |
78 | */
79 |
80 | // StuType 学生
81 | StuType = "STU"
82 | // TeaType 教师教辅人员
83 | TeaType = "TEA"
84 | // SysType 管理人员
85 | SysType = "SYS"
86 | // AdmType 门户维护员
87 | AdmType = "ADM"
88 | )
89 |
90 | // JwUser 教务在线结构体
91 | type JwUser struct {
92 | Username string
93 | Password string
94 | SelType string
95 | Url *url.URL
96 | IsBoundSec bool
97 | Client *resty.Client
98 | }
99 |
100 | // NewJwUser 新建教务用户
101 | func NewJwUser() *JwUser {
102 | var u JwUser
103 | u.Client = resty.New()
104 | u.Client.SetHeaders(MainHeaders)
105 | u.Url, _ = url.Parse(MianUrl)
106 | return &u
107 | }
108 |
109 | // SetUsername 设置用户名
110 | func (u *JwUser) SetUsername(username string) *JwUser {
111 | u.Username = username
112 | return u
113 | }
114 |
115 | // SetPassword 设置密码
116 | func (u *JwUser) SetPassword(password string) *JwUser {
117 | u.Password = password
118 | return u
119 | }
120 |
121 | // SetSelType 设置登陆类型
122 | /*
123 | // StuType 学生
124 | StuType = "STU"
125 | // TeaType 教师教辅人员
126 | TeaType = "TEA"
127 | // SysType 管理人员
128 | SysType = "SYS"
129 | // AdmType 门户维护员
130 | AdmType = "ADM"
131 | */
132 | func (u *JwUser) SetSelType(selType string) *JwUser {
133 | if selType != StuType && selType != TeaType && selType != SysType && selType != AdmType {
134 | // log.Println("error: your selType not support!")
135 | return u
136 | }
137 | u.SelType = selType
138 | return u
139 | }
140 |
141 | // SetUrl JwUser reset main URL
142 | func (u *JwUser) SetUrl(url *url.URL) *JwUser {
143 | u.Url = url
144 | return u
145 | }
146 |
147 | // BindSecUser 绑定智慧门户帐号
148 | func (u *JwUser) BindSecUser(secUser *sec.SecUser) (err error) {
149 |
150 | portalIsLogged := secUser.IsPortalLogged()
151 | if !portalIsLogged {
152 | err = errors.New("secUser is not portal logged")
153 | return
154 | }
155 |
156 | // https://sec.lit.edu.cn/webvpn/LjIwNi4xNzAuMjE4LjE2Mg==/LjIwOC4xNzMuMTQ4LjE1OC4xNTguMTcwLjk0LjE1Mi4xNTAuMjE2LjEwMi4xOTcuMjA5/cas_njjz.aspx?vpn-0
157 |
158 | secJWUrl, _ := url.Parse(sec.JWUrlPerfix)
159 |
160 | // 更新Url
161 | u.SetUrl(secJWUrl)
162 |
163 | // // 覆盖Client
164 | u.Client.SetCookies(secUser.Client.GetClient().Jar.Cookies(secJWUrl))
165 | // u.Client = secUser.Client
166 | // u.Client.SetDebug(true)
167 | u.IsBoundSec = true
168 |
169 | return
170 |
171 | }
172 |
--------------------------------------------------------------------------------
/client/jw/login.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | "github.com/PuerkitoBio/goquery"
8 | "github.com/icepie/oh-my-lit/client/util"
9 | )
10 |
11 | // JwUser Login
12 | func (u *JwUser) Login() (err error) {
13 |
14 | if len(u.Username) == 0 {
15 | err = errors.New("empty username")
16 | return
17 | }
18 |
19 | if len(u.Password) == 0 {
20 | err = errors.New("empty password")
21 | return
22 | }
23 |
24 | if len(u.SelType) == 0 {
25 | err = errors.New("empty selType")
26 | return
27 | }
28 |
29 | // if u.IsBoundSec {
30 | // // 暂时未实现登陆任意帐号
31 | // err = errors.New("please use LoginBySec()")
32 | // return
33 | // }
34 |
35 | LoginUrl := u.Url.String() + LoginPath
36 |
37 | resp, _ := u.Client.R().
38 | SetHeader("referer", LoginUrl).
39 | Get(LoginUrl)
40 |
41 | // 将 gb2312 转换为 utf-8
42 | body := util.GB18030ToUTF8(resp.String())
43 |
44 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(body))
45 | if err != nil {
46 | return
47 | }
48 |
49 | // 拿到参数
50 | vs, isExist := doc.Find("input[name=__VIEWSTATE]").First().Attr("value")
51 | if !isExist {
52 | err = errors.New("vs is no exist")
53 | return
54 | }
55 |
56 | // the last step
57 | u.Client.R().
58 | SetFormData(map[string]string{
59 | "__VIEWSTATE": vs,
60 | "Sel_Type": u.SelType, // SYS etc..
61 | "txt_sdsdfdsfryuiighgdf": u.Username,
62 | "txt_dsfdgtjhjuixssdsdf": "",
63 | "txt_sftfgtrefjdndcfgerg": "",
64 | "typeName": "",
65 | "sdfdfdhgwerewt": chkpwd(u.Username, u.Password),
66 | "cxfdsfdshjhjlk": "",
67 | }).
68 | SetHeader("referer", LoginUrl).
69 | Post(LoginUrl)
70 |
71 | isLogged := u.IsLogged()
72 |
73 | // log.Println(resp.String())
74 |
75 | if !isLogged {
76 | err = errors.New("jw fail to login")
77 | }
78 |
79 | return
80 |
81 | }
82 |
83 | // LoginBySec 通过智慧门户帐号快速登陆
84 | func (u *JwUser) LoginBySec() (err error) {
85 |
86 | if !u.IsBoundSec {
87 | // 暂时未实现登陆任意帐号
88 | err = errors.New("only for sec user")
89 | return
90 | }
91 |
92 | if len(u.SelType) == 0 {
93 | err = errors.New("empty selType")
94 | return
95 | }
96 |
97 | LoginBySecUrl := u.Url.String() + LoginBySecPath + "?vpn-0"
98 |
99 | resp, _ := u.Client.R().
100 | SetHeader("referer", LoginBySecUrl).
101 | Get(LoginBySecUrl)
102 |
103 | body := util.GB18030ToUTF8(resp.String())
104 |
105 | isMultiRole := false
106 | if strings.Contains(body, "请选择相应角色") {
107 | isMultiRole = true
108 | }
109 |
110 | isHaveRole := false
111 | if isMultiRole {
112 | sel_Type := ""
113 | doc, _ := goquery.NewDocumentFromReader(strings.NewReader(body))
114 | doc.Find("select#Sel_Grp>option").Each(func(i int, option *goquery.Selection) {
115 | op, _ := option.Attr("value")
116 | if op[:3] == u.SelType {
117 | isHaveRole = true
118 | sel_Type = op
119 | }
120 | })
121 |
122 | userID, _ := doc.Find("input#UserID").First().Attr("value")
123 | roleCHK, _ := doc.Find("input#roleCHK").First().Attr("value")
124 | typeName, _ := doc.Find("input#typeName").First().Attr("value")
125 | pcInfo, _ := doc.Find("input#pcInfo").First().Attr("value")
126 |
127 | // the last step
128 | u.Client.R().
129 | SetFormData(map[string]string{
130 | "Sel_Type": sel_Type,
131 | "subBtn1": "%C8%B7%B6%A8",
132 | "vUserID": userID,
133 | "roleCHK": roleCHK,
134 | "typeName": typeName,
135 | "pcInfo": pcInfo,
136 | }).
137 | SetHeader("referer", LoginBySecUrl).
138 | Post(LoginBySecUrl)
139 |
140 | }
141 |
142 | if isMultiRole && !isHaveRole {
143 | err = errors.New("you do not have the role by the selType")
144 | return
145 | }
146 |
147 | isLogged := u.IsLogged()
148 |
149 | if !isLogged {
150 | err = errors.New("jw fail to login")
151 | }
152 |
153 | return
154 |
155 | }
156 |
157 | // 是否登陆
158 | func (u *JwUser) IsLogged() (isLogged bool) {
159 |
160 | isLogged = false
161 |
162 | MAINFRMUrl := u.Url.String() + MenuPath
163 |
164 | resp, _ := u.Client.R().
165 | SetHeader("referer", MAINFRMUrl).
166 | Get(MAINFRMUrl)
167 |
168 | body := util.GB18030ToUTF8(resp.String())
169 |
170 | // 检测是否登陆成功
171 | if strings.Contains(body, "洛阳理工学院教务") {
172 | isLogged = true
173 | }
174 |
175 | return
176 | }
177 |
--------------------------------------------------------------------------------
/client/jw/model.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | // JwTime 教务时间
4 | type JwTime struct {
5 | Year uint `json:"year"`
6 | Term uint `json:"term"`
7 | Week uint `json:"week"`
8 | IsVacation bool `json:"is_vacation"`
9 | RawData string `json:"raw_data"`
10 | OnlineNum uint `json:"online_num"`
11 | Sub string `json:"sub"`
12 | }
13 |
14 | // 课程信息
15 | type CourseInfo struct {
16 | Day int `json:"day"`
17 | Titile string `json:"title"`
18 | Location string `json:"location"`
19 | Credit string `json:"credit"`
20 | Code string `json:"code"`
21 | Sections []int `json:"sections"`
22 | Start int `json:"start"`
23 | Duration int `json:"duration"`
24 | Teacher string `json:"teacher"`
25 | Weeks []int `json:"weeks"`
26 | Time string `json:"time"`
27 | }
28 |
--------------------------------------------------------------------------------
/client/jw/parser.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | import (
4 | "errors"
5 | "strconv"
6 | "strings"
7 |
8 | "github.com/PuerkitoBio/goquery"
9 | )
10 |
11 | // XKJGRptToSchedule 正选结果解析为课程表结构
12 | func XKJGRptToSchedule(body string) (courses []CourseInfo, err error) {
13 |
14 | if strings.Contains(body, "禁止选课!") {
15 | err = errors.New("Forbidden")
16 | return
17 | }
18 |
19 | doc, _ := goquery.NewDocumentFromReader(strings.NewReader(body))
20 |
21 | // 找到那张表
22 | tbody := doc.Find("table#pageRpt").Find("table").Eq(1).Find("tbody")
23 |
24 | // 开始遍历
25 | tbody.Find("tr").Each(func(i int, tr *goquery.Selection) {
26 | _, isExistsClass := tr.Attr("class")
27 | if isExistsClass {
28 | // 排除表头
29 | return
30 | }
31 |
32 | // 课程名称
33 | classNameFull := strings.Split(tr.Find("td").Eq(1).Text(), "]")
34 |
35 | if len(classNameFull) < 2 {
36 | return
37 | }
38 |
39 | classCode := classNameFull[0][1:]
40 | className := classNameFull[1]
41 |
42 | // 学分
43 | credit := tr.Find("td").Eq(2).Text()
44 |
45 | // 课程教师
46 | var teacher string
47 |
48 | // 教师(可能有多个, 以空格分隔)
49 | tr.Find("td").Eq(4).Find("a").Each(func(i int, a *goquery.Selection) {
50 | t := strings.Split(a.Text(), "[")[0]
51 | if teacher == "" {
52 | teacher = t
53 | } else {
54 | teacher = teacher + " & " + t
55 | }
56 | })
57 |
58 | // 课程地点
59 | timePlace, _ := tr.Find("td").Eq(10).Html()
60 |
61 | timePlaceList := strings.Split(strings.TrimSuffix(timePlace, "
"), "
")
62 |
63 | for _, tp := range timePlaceList {
64 |
65 | tmp := strings.Split(tp, "/")
66 |
67 | if len(tmp) == 0 {
68 | continue
69 | }
70 |
71 | var place string
72 |
73 | if len(tmp) > 1 {
74 | place = strings.ReplaceAll(tmp[1], " ", "")
75 | }
76 |
77 | if place == "" {
78 | if strings.HasPrefix(className, "m") {
79 | place = "MOOC"
80 | } else if strings.Contains(className, "体育") {
81 | place = "GYM"
82 | }
83 | }
84 |
85 | // 定义课程
86 | var course CourseInfo
87 |
88 | // 时间
89 | time := tmp[0]
90 |
91 | time = strings.TrimRight(strings.TrimPrefix(time, "["), "]")
92 | time = strings.ReplaceAll(strings.ReplaceAll(time, "[", " "), "]", " ")
93 | time = strings.ReplaceAll(time, " ", " ")
94 |
95 | timeList := strings.Split(time, " ")
96 |
97 | if len(timeList) < 3 {
98 | continue
99 | }
100 |
101 | // 使用格式一
102 | if strings.HasPrefix(tmp[0], "[") {
103 | course.Time = time
104 | course.Day = buildDay(timeList[1])
105 | course.Weeks = buildWeeks(timeList[0])
106 | course.Sections = buildSections(timeList[2])
107 | //使用格式二
108 | } else {
109 | course.Time = time
110 | if len(timeList) == 4 {
111 | course.Weeks = buildWeeks(timeList[2] + timeList[3])
112 | } else {
113 | course.Weeks = buildWeeks(timeList[2])
114 | }
115 | course.Day = buildDay(timeList[0])
116 | course.Sections = buildSections(timeList[1])
117 | }
118 |
119 | // log.Println(timeList)
120 |
121 | // log.Println(len(timeList))
122 |
123 | course.Code = classCode
124 | course.Titile = className
125 | course.Credit = credit
126 | course.Teacher = teacher
127 | course.Location = place
128 | course.Start = course.Sections[0]
129 | course.Duration = len(course.Sections)
130 | courses = append(courses, course)
131 |
132 | // log.Println(course)
133 |
134 | }
135 |
136 | // tr.Find("td").Each(func(i int, td *goquery.Selection) {
137 | // switch i {
138 | // case 1:
139 | // // 课程名称
140 | // log.Println(td.Text())
141 | // case 2:
142 | // // 学分
143 | // log.Println(td.Text())
144 | // case 4:
145 | // // 教师
146 | // log.Println(td.Text())
147 | // case 10:
148 | // // 时间/地点
149 | // log.Println(td.Text())
150 | // }
151 |
152 | // // log.Println(td.Text())
153 | // })
154 | })
155 |
156 | return
157 |
158 | }
159 |
160 | // buildWeeks 构建周数
161 | func buildWeeks(weekStr string) (rte []int) {
162 |
163 | flag := 0
164 |
165 | if strings.Contains(weekStr, "单") {
166 | flag = 1
167 | } else if strings.Contains(weekStr, "双") {
168 | flag = 2
169 | }
170 |
171 | weekStr = strings.ReplaceAll(strings.ReplaceAll(weekStr, "双", ""), "单", "")
172 | weekStr = strings.ReplaceAll(weekStr, "周", "")
173 |
174 | weekList := strings.Split(weekStr, ",")
175 | for _, v := range weekList {
176 | se := strings.Split(v, "-")
177 | if len(se) == 2 {
178 | s, _ := strconv.Atoi(se[0])
179 | e, _ := strconv.Atoi(se[1])
180 | for i := s; i <= e; i++ {
181 | if flag == 1 {
182 | if i%2 != 0 {
183 | rte = append(rte, i)
184 | }
185 | } else if flag == 2 {
186 | if i%2 == 0 {
187 | rte = append(rte, i)
188 | }
189 | } else {
190 | rte = append(rte, i)
191 | }
192 | }
193 |
194 | } else {
195 | i, err := strconv.Atoi(se[0])
196 | if err == nil {
197 | rte = append(rte, i)
198 | }
199 | }
200 |
201 | }
202 |
203 | return
204 | }
205 |
206 | // buildSections 构建节数
207 | func buildSections(sectionStr string) (rte []int) {
208 |
209 | se := strings.Split(strings.ReplaceAll(sectionStr, "节", ""), "-")
210 |
211 | if len(se) == 2 {
212 | s, _ := strconv.Atoi(se[0])
213 | e, _ := strconv.Atoi(se[1])
214 | for i := s; i <= e; i++ {
215 | rte = append(rte, i)
216 | }
217 |
218 | } else {
219 | i, err := strconv.Atoi(se[0])
220 | if err == nil {
221 | rte = append(rte, i)
222 | }
223 | }
224 |
225 | return
226 |
227 | }
228 |
229 | // buildDay 构建天
230 | func buildDay(dayStr string) (day int) {
231 | dayStr = strings.ReplaceAll(strings.ReplaceAll(dayStr, "天", "日"), "星期", "")
232 | return chDaytoNum(dayStr)
233 | }
234 |
235 | // chDaytoNum 根据星期转换为数字
236 | func chDaytoNum(day string) int {
237 | day = strings.ReplaceAll(day, "日", "七")
238 | dayMap := map[string]int{"一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6, "七": 7}
239 | return dayMap[day]
240 | }
241 |
242 | // 构建云小洛课表结构
243 | func BuildAirSchedule(raw []CourseInfo) (ret [20][7][5][]CourseInfo) {
244 |
245 | for _, c := range raw {
246 | for _, week := range c.Weeks {
247 | var s int
248 | if c.Start == 1 {
249 | s = 0
250 | } else {
251 | s = (c.Start - 1) / 2
252 | }
253 |
254 | if c.Day == 0 {
255 | ret[week-1][6][s] = append(ret[week-1][6][s], c)
256 | } else {
257 | ret[week-1][c.Day-1][s] = append(ret[week-1][c.Day-1][s], c)
258 | }
259 |
260 | }
261 | }
262 | return ret
263 | }
264 |
--------------------------------------------------------------------------------
/client/jw/pub_lookxl.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | "github.com/icepie/oh-my-lit/client/util"
8 | )
9 |
10 | // GetLookxlRpt 获取校历返回页面
11 | func (u *JwUser) GetLookxlRpt() (body string, err error) {
12 |
13 | theUrl := u.Url.String() + LookxlRptPath
14 |
15 | resp, _ := u.Client.R().
16 | SetHeader("referer", theUrl).
17 | Get(theUrl)
18 |
19 | body = util.GB18030ToUTF8(resp.String())
20 |
21 | if strings.Contains(body, "您无权访问此页") {
22 | err = errors.New("your account does not have permission")
23 | }
24 |
25 | return
26 | }
27 |
--------------------------------------------------------------------------------
/client/jw/pub_znpk.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | import (
4 | "github.com/icepie/oh-my-lit/client/util"
5 | )
6 |
7 | // GetClassSelRpt 获取公共班级课表页面
8 | func (u *JwUser) GetClassSelPage() (body string, err error) {
9 |
10 | theUrl := u.Url.String() + ClassSelPath
11 |
12 | resp, _ := u.Client.R().
13 | SetHeader("referer", theUrl).
14 | Get(theUrl)
15 |
16 | body = util.GB18030ToUTF8(resp.String())
17 |
18 | // if strings.Contains(body, "您无权访问此页") {
19 | // err = errors.New("your account does not have permission")
20 | // }
21 |
22 | return
23 | }
24 |
--------------------------------------------------------------------------------
/client/jw/stu_jxjh.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | "github.com/icepie/oh-my-lit/client/util"
8 | )
9 |
10 | // GetStuXKJGRpt 获取学生理论课程原始数据
11 | func (u *JwUser) GetStuBYFAKCRpt() (body string, err error) {
12 |
13 | theUrl := u.Url.String() + StuBYFAKCPath
14 |
15 | resp, _ := u.Client.R().
16 | SetHeader("referer", theUrl).
17 | Get(theUrl)
18 |
19 | body = util.GB18030ToUTF8(resp.String())
20 |
21 | if strings.Contains(body, "您无权访问此页") {
22 | err = errors.New("your account does not have permission")
23 | }
24 |
25 | return
26 | }
27 |
28 | // GetStuXKJGRpt 获取学生教学方案原始数据
29 | func (u *JwUser) GetStuBYFAHJRpt() (body string, err error) {
30 |
31 | StuBYFAHJUrl := u.Url.String() + StuBYFAHJPath
32 |
33 | resp, _ := u.Client.R().
34 | SetHeader("referer", StuBYFAHJUrl).
35 | Get(StuBYFAHJUrl)
36 |
37 | body = util.GB18030ToUTF8(resp.String())
38 |
39 | if strings.Contains(body, "您无权访问此页") {
40 | err = errors.New("your account does not have permission")
41 | }
42 |
43 | return
44 | }
45 |
--------------------------------------------------------------------------------
/client/jw/stu_wsxk.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | "github.com/icepie/oh-my-lit/client/util"
8 | )
9 |
10 | // GetStuXKJGRpt 获取学生正选结果原数据
11 | func (u *JwUser) GetStuXKJGRpt() (body string, err error) {
12 |
13 | StuZXJGUrl := u.Url.String() + StuZXJGPath
14 |
15 | resp, _ := u.Client.R().
16 | SetHeader("referer", StuZXJGUrl).
17 | Get(StuZXJGUrl)
18 |
19 | body = util.GB18030ToUTF8(resp.String())
20 |
21 | if strings.Contains(body, "您无权访问此页") {
22 | err = errors.New("your account does not have permission")
23 | }
24 |
25 | return
26 | }
27 |
--------------------------------------------------------------------------------
/client/jw/stu_xscj.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | "github.com/icepie/oh-my-lit/client/util"
8 | )
9 |
10 | // GetStuDJKSCJ 获取学生等级成绩
11 | func (u *JwUser) GetStuDJKSCJ() (body string, err error) {
12 |
13 | StuZXJGUrl := u.Url.String() + StuDJKSCJPath
14 |
15 | resp, _ := u.Client.R().
16 | SetHeader("referer", StuZXJGUrl).
17 | Get(StuZXJGUrl)
18 |
19 | body = util.GB18030ToUTF8(resp.String())
20 |
21 | if strings.Contains(body, "您无权访问此页") {
22 | err = errors.New("your account does not have permission")
23 | }
24 |
25 | return
26 | }
27 |
--------------------------------------------------------------------------------
/client/jw/stu_xsxj.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | "github.com/icepie/oh-my-lit/client/util"
8 | )
9 |
10 | // GetStuMyInfoRpt 获取学生学籍信息原数据
11 | func (u *JwUser) GetStuMyInfoRpt() (body string, err error) {
12 |
13 | theUrl := u.Url.String() + StuDJKSCJPath
14 |
15 | resp, _ := u.Client.R().
16 | SetHeader("referer", theUrl).
17 | Get(theUrl)
18 |
19 | body = util.GB18030ToUTF8(resp.String())
20 |
21 | if strings.Contains(body, "您无权访问此页") {
22 | err = errors.New("your account does not have permission")
23 | }
24 |
25 | return
26 | }
27 |
--------------------------------------------------------------------------------
/client/jw/sys_banner.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | "github.com/icepie/oh-my-lit/client/util"
8 | )
9 |
10 | // GetBannerRpt 获取主页顶部横幅原始数据
11 | func (u *JwUser) GetBannerRpt() (body string, err error) {
12 |
13 | theUrl := u.Url.String() + SysBannerPath
14 |
15 | resp, _ := u.Client.R().
16 | SetHeader("referer", theUrl).
17 | Get(theUrl)
18 |
19 | body = util.GB18030ToUTF8(resp.String())
20 |
21 | if strings.Contains(body, "您无权访问此页") {
22 | err = errors.New("your account does not have permission")
23 | }
24 |
25 | return
26 | }
27 |
--------------------------------------------------------------------------------
/client/jw/sys_xscj.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/icepie/oh-my-lit/client/util"
9 | )
10 |
11 | // GetListXSRpt 查询学生
12 | func (u *JwUser) GetListXSRpt(stuID string) (body string, err error) {
13 |
14 | theUrl := u.Url.String() + SysListXSPath
15 |
16 | resp, _ := u.Client.R().
17 | SetQueryParam("id", stuID).
18 | SetHeader("referer", theUrl).
19 | Get(theUrl)
20 |
21 | body = util.GB18030ToUTF8(resp.String())
22 |
23 | if strings.Contains(body, "您无权访问此页") {
24 | err = errors.New("your account does not have permission")
25 | }
26 |
27 | return
28 | }
29 |
30 | // GetCXGRCJRpt 获取学生成绩
31 | // timeMode {0: 入学以来, 1: 按照学年, 2: 按照学期}
32 | // mode {0: 原始成绩, 1: 有效成绩}
33 | // flag {0: 主修, 1: 辅修}
34 | func (u *JwUser) GetCXGRCJRpt(timeMode uint, mode uint, flag uint, year uint, term uint, stuID string) (body string, err error) {
35 |
36 | theUrl := u.Url.String() + SysXSGRCJPath
37 |
38 | resp, _ := u.Client.R().
39 | SetFormData(map[string]string{
40 | "txt_xm": u.Username,
41 | "SelXNXQ": fmt.Sprint(timeMode),
42 | "SJ": fmt.Sprint(mode),
43 | "sel_xn": fmt.Sprint(year),
44 | "sel_xq": fmt.Sprint(term),
45 | "zfx_flag": fmt.Sprint(flag),
46 | "sel_xs": stuID,
47 | }).
48 | SetHeader("referer", theUrl).
49 | Post(theUrl)
50 |
51 | body = util.GB18030ToUTF8(resp.String())
52 |
53 | if strings.Contains(body, "您无权访问此页") {
54 | err = errors.New("your account does not have permission")
55 | }
56 |
57 | return
58 | }
59 |
60 | // GetXscjbmLeftRpt 有效成绩排名 left 结果
61 | // timeMode {0: 入学以来, 1: 按照学年, 2: 按照学期}
62 | func (u *JwUser) GetXscjbmLeftRpt(classID string, year uint, term uint, timeMode uint, isBX bool, isXX bool, isRX bool, isHJ bool) (body string, err error) {
63 |
64 | theUrl := u.Url.String() + SysXscjbmLeftPath
65 |
66 | p := map[string]string{
67 | "sel_bj": classID,
68 | "sel_xn": fmt.Sprint(year),
69 | "sel_xq": fmt.Sprint(term),
70 | "xnxq": fmt.Sprintf("%d%d", year, term),
71 | "SelXNXQ": fmt.Sprint(timeMode),
72 | }
73 |
74 | if isBX {
75 | p["bxkc"] = "01"
76 | }
77 |
78 | if isXX {
79 | p["xxkc"] = "02"
80 | }
81 |
82 | if isRX {
83 | p["rxkc"] = "03"
84 | }
85 |
86 | if isHJ {
87 | p["hj"] = "1"
88 | }
89 |
90 | resp, _ := u.Client.R().
91 | SetFormData(p).
92 | SetHeader("referer", theUrl).
93 | Post(theUrl)
94 |
95 | body = util.GB18030ToUTF8(resp.String())
96 |
97 | if strings.Contains(body, "您无权访问此页") {
98 | err = errors.New("your account does not have permission")
99 | }
100 |
101 | return
102 | }
103 |
104 | // GetXscjbmRightRpt 有效成绩排名 right 最终结果
105 | // timeMode {0: 入学以来, 1: 按照学年, 2: 按照学期}
106 | func (u *JwUser) GetXscjbmRightRpt(classID string, year uint, term uint, timeMode uint, HidKC string, isBX bool, isXX bool, isRX bool, isHJ bool) (body string, err error) {
107 |
108 | theUrl := u.Url.String() + SysXscjbmRightPath
109 |
110 | p := map[string]string{
111 | "sel_bj": classID,
112 | "sel_xn": fmt.Sprint(year),
113 | "sel_xq": fmt.Sprint(term),
114 | "hid_kc": HidKC,
115 | "xnxq": fmt.Sprintf("%d%d", year, term),
116 | "SelXNXQ": fmt.Sprint(timeMode),
117 | }
118 |
119 | if isBX {
120 | p["bxkc"] = "01"
121 | }
122 |
123 | if isXX {
124 | p["xxkc"] = "02"
125 | }
126 |
127 | if isRX {
128 | p["rxkc"] = "03"
129 | }
130 |
131 | if isHJ {
132 | p["hj"] = "1"
133 | }
134 |
135 | resp, _ := u.Client.R().
136 | SetFormData(p).
137 | SetHeader("referer", theUrl).
138 | Post(theUrl)
139 |
140 | body = util.GB18030ToUTF8(resp.String())
141 |
142 | if strings.Contains(body, "您无权访问此页") {
143 | err = errors.New("your account does not have permission")
144 | }
145 |
146 | return
147 | }
148 |
149 | // GetXscjbmRpt 有效成绩排名 最终结果
150 | // timeMode {0: 入学以来, 1: 按照学年, 2: 按照学期}
151 | func (u *JwUser) GetXscjbmRpt(classID string, year uint, term uint, timeMode uint, isBX bool, isXX bool, isRX bool, isHJ bool) (body string, err error) {
152 |
153 | b, err := u.GetXscjbmLeftRpt(classID, year, term, timeMode, isBX, isXX, isRX, isHJ)
154 | if err != nil {
155 | return
156 | }
157 |
158 | body, err = u.GetXscjbmRightRpt(classID, year, term, timeMode, u.XscjbmLeftRptToHidKC(b), isBX, isXX, isRX, isHJ)
159 | if err != nil {
160 | return
161 | }
162 |
163 | return
164 | }
165 |
--------------------------------------------------------------------------------
/client/jw/sys_znpk.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/icepie/oh-my-lit/client/util"
9 | )
10 |
11 | var (
12 | // 王城
13 | WangchengCampus uint = 1
14 | // 开元
15 | KaiyuanCampus uint = 2
16 | // 九都
17 | JiuduCampus uint = 3
18 | )
19 |
20 | type DayfreeSelParam struct {
21 | Year uint
22 | Term uint
23 | Week uint
24 | Day uint
25 | Section uint
26 | Campus uint
27 | Building uint // 教学楼
28 | RoomType uint // 教室类型
29 | }
30 |
31 | // GetClassSelRpt 获取班级课表
32 | func (u *JwUser) GetClassSelRpt(year int, term int, calssID string) (body string, err error) {
33 |
34 | theUrl := u.Url.String() + SysClassSelPath
35 |
36 | resp, _ := u.Client.R().
37 | SetFormData(map[string]string{
38 | "Sel_XNXQ": fmt.Sprintf("%d%d", year, term),
39 | "Sel_BJ": calssID,
40 | }).
41 | SetHeader("referer", theUrl).
42 | Post(theUrl)
43 |
44 | body = util.GB18030ToUTF8(resp.String())
45 |
46 | if strings.Contains(body, "您无权访问此页") {
47 | err = errors.New("your account does not have permission")
48 | }
49 |
50 | return
51 | }
52 |
53 | // 获取空教室
54 | func (u *JwUser) GetDayfreeSelRpt(data DayfreeSelParam) (body string, err error) {
55 |
56 | theUrl := u.Url.String() + SysZNPKDayfreeSelPath
57 |
58 | formData := map[string]string{
59 | "SelXN": fmt.Sprintf("%d%d", data.Year, data.Term),
60 | "Sel_ZC": buildNumParam(data.Week), // 周次
61 | "selxqs": fmt.Sprintf("%d", data.Day), // 星期
62 | "Sel_JC": buildNumParam(data.Section), // 节次
63 | "Sel_JXL": buildNumParam(data.Building),
64 | "sel_jslx": buildNumParam(data.RoomType),
65 | "Submit01": "%BC%EC%CB%F7",
66 | "sel_roomname": "",
67 | }
68 |
69 | if data.Campus != 0 {
70 | formData["sel_xq"] = buildNumParam(data.Campus)
71 | }
72 |
73 | resp, _ := u.Client.R().
74 | SetFormData(formData).
75 | SetHeader("referer", theUrl).
76 | Post(theUrl)
77 |
78 | body = util.GB18030ToUTF8(resp.String())
79 |
80 | if strings.Contains(body, "您无权访问此页") {
81 | err = errors.New("your account does not have permission")
82 | }
83 |
84 | return
85 | }
86 |
87 | func buildNumParam(num uint) string {
88 | if num == 0 {
89 | return ""
90 | }
91 | if num < 10 {
92 | return fmt.Sprintf("0%d", num)
93 | }
94 | return fmt.Sprintf("%d", num)
95 | }
96 |
--------------------------------------------------------------------------------
/client/jw/util.go:
--------------------------------------------------------------------------------
1 | package jw
2 |
3 | import (
4 | "crypto/md5"
5 | "fmt"
6 | "strings"
7 | )
8 |
9 | func md5Encode(str string) string {
10 | data := []byte(str)
11 | has := md5.Sum(data)
12 | md5str := fmt.Sprintf("%x", has) //将[]byte转成16进制
13 | return md5str
14 | }
15 |
16 | // chkpwd 将用户密码进行处理
17 | func chkpwd(username string, password string) string {
18 |
19 | /* JavaScript
20 | function chkpwd(obj)
21 | {
22 | var schoolcode="11070";
23 | var yhm=document.all.txt_sdsdfdsfryuiighgdf.value;
24 | if(obj.value!="")
25 | {
26 | if(document.all.Sel_Type.value=="ADM")
27 | yhm=yhm.toUpperCase();
28 | var s=md5(yhm+md5(obj.value).substring(0,30).toUpperCase()+schoolcode).substring(0,30).toUpperCase();
29 | document.all.sdfdfdhgwerewt.value=s;
30 | }
31 | else
32 | {
33 | document.all.sdfdfdhgwerewt.value=obj.value;
34 | }
35 | }
36 | */
37 |
38 | return strings.ToUpper(md5Encode(username + strings.ToUpper(md5Encode(password)[0:30]) + SchoolCode)[0:30])
39 | }
40 |
--------------------------------------------------------------------------------
/client/jwc/jwc.go:
--------------------------------------------------------------------------------
1 | package jwc
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "regexp"
7 | "strings"
8 |
9 | "github.com/PuerkitoBio/goquery"
10 | "github.com/go-resty/resty/v2"
11 |
12 | md "github.com/JohannesKaufmann/html-to-markdown"
13 | )
14 |
15 | const (
16 | // ggtz
17 | GGTZ_URL = "https://www.lit.edu.cn/jwc/xb2021/ggtz.htm"
18 | // gttz by page
19 | GGTZ_PAGE_URL = "https://www.lit.edu.cn/jwc/xb2021"
20 | // post url
21 | POST_URL = "https://www.lit.edu.cn/jwc"
22 | )
23 |
24 | type Attachment struct {
25 | Url string `json:"url"`
26 | Name string `json:"name"`
27 | }
28 |
29 | type PostContent struct {
30 | Content string `json:"content"`
31 | Attachment []Attachment `json:"attachment"`
32 | }
33 |
34 | type Post struct {
35 | Title string `json:"title"`
36 | Url string `json:"url"`
37 | Date string `json:"date"`
38 | }
39 |
40 | type PostList struct {
41 | Posts []Post `json:"posts"`
42 | Next string `json:"next"`
43 | }
44 |
45 | // JwUser 教务在线结构体
46 | type JwCUser struct {
47 | Client *resty.Client
48 | }
49 |
50 | // NewJwUser 新建教务用户
51 | func NewJwCUser() *JwCUser {
52 |
53 | var u JwCUser
54 | u.Client = resty.New()
55 | return &u
56 | }
57 |
58 | // GetGGTZPage 获取公告通知
59 | func (u *JwCUser) getGGTZPage(nextPath string) (string, error) {
60 |
61 | var url string
62 |
63 | if nextPath != "" {
64 | url = GGTZ_PAGE_URL + "/" + nextPath
65 | } else {
66 | url = GGTZ_URL
67 | }
68 |
69 | resp, err := u.Client.R().Get(url)
70 | if err != nil {
71 | return "", err
72 | }
73 | return resp.String(), nil
74 | }
75 |
76 | // getPost 获取公告通知
77 | func (u *JwCUser) getGGTZPostPage(postPath string) (string, error) {
78 |
79 | url := POST_URL + "/" + postPath
80 |
81 | resp, err := u.Client.R().Get(url)
82 | if err != nil {
83 | return "", err
84 | }
85 | return resp.String(), nil
86 | }
87 |
88 | //
89 |
90 | func (u *JwCUser) GetGGTZPost(postPath string) (content PostContent, err error) {
91 |
92 | content.Attachment = make([]Attachment, 0)
93 |
94 | data, err := u.getGGTZPostPage(postPath)
95 | if err != nil {
96 | return
97 | }
98 |
99 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))
100 | if err != nil {
101 | return
102 | }
103 |
104 | // 获取附件
105 |
106 | //
109 |
110 | // 标题
111 |
112 | title := doc.Find("h1").Text()
113 |
114 | html, err := doc.Find("div.v_news_content").Html()
115 | if err != nil {
116 | return
117 | }
118 |
119 | converter := md.NewConverter("", true, nil)
120 |
121 | markdown, err := converter.ConvertString(html)
122 | if err != nil {
123 | log.Fatal(err)
124 | }
125 |
126 | // 替换/*.png 为 https://www.lit.edu.cn/*.png
127 |
128 | // reg := regexp.MustCompile(`\!\[.*\]\((.*)\)`)
129 | // markdown = reg.ReplaceAllStringFunc(markdown, func(s string) string {
130 | // return strings.Replace(s, "(", "(https://www.lit.edu.cn", 1)
131 | // })
132 |
133 | // /__local/ to https://www.lit.edu.cn/__local/
134 |
135 | markdown = strings.Replace(markdown, "/__local/", "https://www.lit.edu.cn/__local/", -1)
136 |
137 | // 多个换行替换为两个换行
138 | reg := regexp.MustCompile(`\n{2,}`)
139 | markdown = reg.ReplaceAllString(markdown, "\n\n")
140 |
141 | // 替换一行中的多个空格为一个空格
142 | reg = regexp.MustCompile(` {2,}`)
143 | markdown = reg.ReplaceAllString(markdown, "")
144 |
145 | content.Content = markdown
146 |
147 | doc.Find("a[target=_blank]").Each(func(_ int, s *goquery.Selection) {
148 | // For each item found, get the band and title
149 | url, _ := s.Attr("href")
150 | name := s.Text()
151 | if strings.Contains(url, "download.jsp") {
152 |
153 | // /system/ to https://www.lit.edu.cn/system/
154 |
155 | url = strings.Replace(url, "/system/", "https://www.lit.edu.cn/system/", 1)
156 |
157 | content.Attachment = append(content.Attachment, Attachment{
158 | Url: url,
159 | Name: name,
160 | })
161 | }
162 | })
163 |
164 | if title != "" {
165 | content.Content = fmt.Sprintf("# %s\n\n", title) + content.Content
166 | }
167 |
168 | if len(content.Attachment) > 0 {
169 |
170 | // 在md 最后添加附件列表
171 |
172 | // 分割线
173 |
174 | content.Content += "\n\n---\n\n"
175 |
176 | content.Content += "\n\n**附件列表**\n\n"
177 |
178 | for _, v := range content.Attachment {
179 | content.Content += fmt.Sprintf("* [%s](%s)\n", v.Name, v.Url)
180 | }
181 |
182 | }
183 |
184 | // var content string
185 |
186 | // doc.Find("div#vsb_content").First().Find("p").Each(func(_ int, s *goquery.Selection) {
187 | // // For each item found, get the band and title
188 | // content += s.Text()
189 | // })
190 |
191 | return
192 | }
193 |
194 | // 获取公告通知
195 | func (u *JwCUser) GetGGTZPostList(nextPath string) (list PostList, err error) {
196 |
197 | data, err := u.getGGTZPage(nextPath)
198 | if err != nil {
199 | return
200 | }
201 |
202 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))
203 | if err != nil {
204 | return
205 | }
206 |
207 | // log.Println(doc.Html())
208 |
209 | var posts []Post
210 |
211 | doc.Find("ul.list_news").First().Find("li").Each(func(_ int, s *goquery.Selection) {
212 | // For each item found, get the band and title
213 | title := s.Find("a").Text()
214 | url, _ := s.Find("a").Attr("href")
215 |
216 | url = strings.Replace(url, "../", "", -1)
217 | date := s.Find("span").Text()
218 | // log.Println(title, url, date)
219 | posts = append(posts, Post{
220 | Title: title,
221 | Url: url,
222 | Date: date,
223 | })
224 | })
225 |
226 | next, _ := doc.Find("a.Next").First().Attr("href")
227 |
228 | list.Posts = posts
229 | list.Next = next
230 |
231 | if next != "" {
232 | if strings.Contains(next, "ggtz") {
233 | list.Next = next
234 | } else {
235 | list.Next = "ggtz/" + next
236 | }
237 | }
238 |
239 | // log.Println(
240 |
241 | return
242 | }
243 |
--------------------------------------------------------------------------------
/client/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/icepie/oh-my-lit/client/jwc"
7 | )
8 |
9 | func main() {
10 |
11 | jwcUser := jwc.NewJwCUser()
12 |
13 | var next string
14 | for {
15 |
16 | data, err := jwcUser.GetGGTZPostList(next)
17 | if err != nil {
18 | panic(err)
19 | }
20 |
21 | next = data.Next
22 |
23 | if data.Next == "" {
24 | break
25 | }
26 |
27 | for _, post := range data.Posts {
28 | data, err := jwcUser.GetGGTZPost(post.Url)
29 | if err != nil {
30 | panic(err)
31 | }
32 |
33 | log.Println(data)
34 | }
35 |
36 | log.Println(data.Posts)
37 |
38 | }
39 |
40 | // log.Println(data)
41 | }
42 |
--------------------------------------------------------------------------------
/client/sec/home.go:
--------------------------------------------------------------------------------
1 | package sec
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | )
7 |
8 | // GetHomeParam 获取主页参数, 用于检测第一层登陆的成功性
9 | func (u *SecUser) GetHomeParam() (rte HomeParam, err error) {
10 |
11 | resp, _ := u.Client.R().
12 | SetHeader("accept", "application/json, text/plain, */*").
13 | SetHeader("referer", u.AuthUrl).
14 | SetHeader("referer", SecUrl+"/frontend_static/frontend/login/index.html").
15 | Get(GetHomeParamUrl)
16 |
17 | err = json.Unmarshal(resp.Body(), &rte)
18 | if err != nil {
19 | return
20 | }
21 |
22 | // 接口错误解析
23 | if rte.Code != 0 {
24 | err = errors.New(rte.Msg)
25 | }
26 |
27 | return
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/client/sec/login.go:
--------------------------------------------------------------------------------
1 | package sec
2 |
3 | import (
4 | "errors"
5 | "html"
6 | "net/http"
7 | "strings"
8 |
9 | "github.com/go-resty/resty/v2"
10 | "github.com/icepie/oh-my-lit/client/util"
11 | )
12 |
13 | // IsLogged() 用户是否已登陆
14 | func (u *SecUser) IsLogged() (isLogged bool) {
15 |
16 | _, err := u.GetHomeParam()
17 |
18 | return err == nil
19 | }
20 |
21 | // IsPortalLogged 用户是否已登陆门户
22 | func (u *SecUser) IsPortalLogged() (isLogged bool) {
23 |
24 | _, err := u.GetCurrentMember()
25 |
26 | return err == nil
27 | }
28 |
29 | // // IsNeedCaptcha 判断是否需要验证码登陆
30 | // func (u *SecUser) IsNeedCaptcha() (isNeed bool, err error) {
31 |
32 | // resp, reqErr := u.Client.R().
33 | // SetQueryParams(map[string]string{
34 | // "username": u.Username,
35 | // "_": fmt.Sprint(time.Now().Unix()),
36 | // }).
37 | // SetHeader("referer", u.AuthUrl).
38 | // Get(u.AuthUrlPerfix + NeedCaptchaPath)
39 |
40 | // if resp.StatusCode() != 200 {
41 | // err = reqErr
42 | // return
43 | // }
44 |
45 | // body := resp.String()
46 |
47 | // // 最后判断是否需要验证码进行登陆
48 | // if strings.HasPrefix(body, "false") {
49 | // isNeed = false
50 | // } else if strings.HasPrefix(body, "true") {
51 | // isNeed = true
52 | // } else {
53 | // err = errors.New("can not get the info")
54 | // }
55 |
56 | // return
57 |
58 | // }
59 |
60 | // // GetCaptche 获取验证码 (JPEG)
61 | // func (u *SecUser) GetCaptche() (pix []byte, err error) {
62 |
63 | // resp, err := u.Client.R().
64 | // SetQueryParams(map[string]string{
65 | // "username": u.Username,
66 | // "_": fmt.Sprint(time.Now().Unix()),
67 | // }).
68 | // SetHeader("referer", u.AuthUrl).
69 | // SetHeader("accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8").
70 | // Get(u.AuthUrlPerfix + CaptchaPath)
71 |
72 | // if err != nil {
73 | // return
74 | // }
75 |
76 | // pix = resp.Body()
77 |
78 | // return
79 |
80 | // }
81 |
82 | // login 通用登陆
83 | func (u *SecUser) login(captcha string) (err error) {
84 |
85 | if len(u.Username) == 0 {
86 | err = errors.New("empty username")
87 | return
88 | }
89 |
90 | if len(u.Password) == 0 {
91 | err = errors.New("empty password")
92 | return
93 | }
94 |
95 | // 刷新 webvpn path
96 | err = u.PerSetCooikes()
97 | if err != nil {
98 | return
99 | }
100 |
101 | // client := &http.Client{}
102 |
103 | resp, _ := u.Client.R().
104 | SetHeader("referer", u.AuthUrl).
105 | Get(u.AuthUrl)
106 |
107 | body := resp.String()
108 |
109 | // 获取所有可需参数
110 | actionUrl, err := util.GetSubstringBetweenStringsByRE(body, `id="form" action="`, `"`)
111 | if err != nil {
112 | return
113 | }
114 |
115 | // lt, err := util.GetSubstringBetweenStringsByRE(body, `name="lt" value="`, `"`)
116 | // if err != nil {
117 | // return
118 | // }
119 |
120 | execution, err := util.GetSubstringBetweenStringsByRE(body, `name="execution" value="`, `"`)
121 | if err != nil {
122 | return
123 | }
124 |
125 | eventId, err := util.GetSubstringBetweenStringsByRE(body, `name="_eventId" value="`, `"`)
126 | if err != nil {
127 | return
128 | }
129 |
130 | salt, err := util.GetSubstringBetweenStringsByRE(body, `id="salt" value="`, `"`)
131 | if err != nil {
132 | return
133 | }
134 |
135 | // 这个地址需要html解码
136 | decodeurl := html.UnescapeString(actionUrl)
137 |
138 | // var data = strings.NewReader("username=" + u.Username + "&password=" + u.Password + captchaParam + "<=" + lt + "&execution=" + execution + "&_eventId=" + eventId + "&rmShown=" + rmShown)
139 |
140 | dealPassword, err := loginCrypto(u.Password, salt, "1234567890abcdef")
141 | if err != nil {
142 | return
143 | }
144 |
145 | req := u.Client.R().
146 | SetHeader("referer", u.AuthUrl).
147 | SetHeader("authority", actionUrl).
148 | SetFormData(map[string]string{
149 | "username": u.Username,
150 | "password": dealPassword,
151 | // "lt": lt,
152 | "execution": execution,
153 | "_eventId": eventId,
154 | // "salt": salt,
155 | "rememberMe": "true", // 一周内免登录 on/off
156 | "_rememberMe": "on",
157 | })
158 |
159 | // // 预定.....
160 | // if len(captcha) > 0 {
161 | // req.SetFormData(map[string]string{
162 | // "captchaResponse": captcha,
163 | // })
164 | // }
165 |
166 | resp, _ = req.Post(SecUrl + decodeurl)
167 | if err != nil {
168 | return
169 | }
170 |
171 | body = resp.String()
172 |
173 | // log.Println(body)
174 |
175 | // 判断是否有错误
176 | if strings.Contains(body, "credential.errors") {
177 | loginErrStr, _ := util.GetSubstringBetweenStringsByRE(body, `credential.errors">`, ``)
178 | err = errors.New(loginErrStr)
179 | return
180 | }
181 |
182 | // 确保账号登陆成功
183 | // if !u.IsLogged() {
184 | // u.login(captcha)
185 | // }
186 |
187 | // 获取门户path
188 | err = u.PerSetPortalPath()
189 |
190 | return
191 | }
192 |
193 | // Login 第一层普通登陆
194 | func (u *SecUser) Login() (err error) {
195 |
196 | for i := 0; i < 10; i++ {
197 | err = u.login("")
198 | if err != nil {
199 | if err.Error() == "no result" {
200 | continue
201 | } else {
202 | break
203 | }
204 | } else {
205 | break
206 | }
207 | }
208 |
209 | return
210 | }
211 |
212 | // // LoginWithCap 第一层验证码登陆
213 | // func (u *SecUser) LoginWithCap(captcha string) (err error) {
214 | // return u.login(captcha)
215 | // }
216 |
217 | // PortalLogin 第二层门户登陆
218 | func (u *SecUser) PortalLogin() (err error) {
219 |
220 | if len(u.PortalUrlPerfix) == 0 {
221 | err = errors.New("please login first")
222 | return
223 | }
224 |
225 | if !u.IsLogged() {
226 | err = errors.New("please login first")
227 | return
228 | }
229 |
230 | // 增加重定向次数
231 | tmpClient := u.Client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(15))
232 |
233 | resp, reqErr := tmpClient.R().
234 | SetHeader("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9").
235 | SetHeader("referer", u.PortalUrlPerfix+PortalIndexPath).
236 | Get(u.PortalUrlPerfix + PortalLoginPath + "?vpn-0")
237 |
238 | if resp.StatusCode() != http.StatusOK {
239 | err = reqErr
240 | return
241 | }
242 |
243 | // 确保账号登陆成功
244 | if !u.IsPortalLogged() {
245 | err = errors.New("fail to login")
246 | }
247 |
248 | return
249 | }
250 |
--------------------------------------------------------------------------------
/client/sec/model.go:
--------------------------------------------------------------------------------
1 | package sec
2 |
3 | // HomeParam 主页参数模型
4 | type HomeParam struct {
5 | Code int64 `json:"code"`
6 | Data struct {
7 | Backend string `json:"backend"`
8 | CopyRight string `json:"copy_right"`
9 | HasBeianPerm bool `json:"hasBeianPerm"`
10 | HasLibrary bool `json:"hasLibrary"`
11 | IsLocalUser bool `json:"isLocalUser"`
12 | Organization string `json:"organization"`
13 | PlacardShow interface{} `json:"placard_show"`
14 | Protocols []string `json:"protocols"`
15 | SysPlacard interface{} `json:"sys_placard"`
16 | Username string `json:"username"`
17 | } `json:"data"`
18 | Msg string `json:"msg"`
19 | }
20 |
21 | // CurrentMember 当前用户返回结构
22 | type CurrentMemberRte struct {
23 | Attributes interface{} `json:"attributes"`
24 | Count interface{} `json:"count"`
25 | Msg string `json:"msg"`
26 | Obj struct {
27 | DeptCodeList interface{} `json:"deptCodeList"`
28 | DeptList interface{} `json:"deptList"`
29 | LastLoginTime string `json:"lastLoginTime"`
30 | MemberAcademicNumber string `json:"memberAcademicNumber"`
31 | MemberAesEncrypt string `json:"memberAesEncrypt"`
32 | MemberAppLastLoginTime interface{} `json:"memberAppLastLoginTime"`
33 | MemberCasLastLoginTime interface{} `json:"memberCasLastLoginTime"`
34 | MemberCreateTime int64 `json:"memberCreateTime"`
35 | MemberID string `json:"memberId"`
36 | MemberIDAesEncrypt string `json:"memberIdAesEncrypt"`
37 | MemberIDNumber string `json:"memberIdNumber"`
38 | MemberIDNumberSign interface{} `json:"memberIdNumberSign"`
39 | MemberImage interface{} `json:"memberImage"`
40 | MemberMailbox interface{} `json:"memberMailbox"`
41 | MemberNickname string `json:"memberNickname"`
42 | MemberOtherBirthday interface{} `json:"memberOtherBirthday"`
43 | MemberOtherClass interface{} `json:"memberOtherClass"`
44 | MemberOtherDepartment interface{} `json:"memberOtherDepartment"`
45 | MemberOtherGrade interface{} `json:"memberOtherGrade"`
46 | MemberOtherMajor interface{} `json:"memberOtherMajor"`
47 | MemberOtherNation interface{} `json:"memberOtherNation"`
48 | MemberOtherNative interface{} `json:"memberOtherNative"`
49 | MemberOtherSchoolNumber interface{} `json:"memberOtherSchoolNumber"`
50 | MemberPcLastLoginTime interface{} `json:"memberPcLastLoginTime"`
51 | MemberPhone interface{} `json:"memberPhone"`
52 | MemberPwd string `json:"memberPwd"`
53 | MemberSalt interface{} `json:"memberSalt"`
54 | MemberSex int64 `json:"memberSex"`
55 | MemberSign interface{} `json:"memberSign"`
56 | MemberState int64 `json:"memberState"`
57 | MemberUpdatePasswordTime interface{} `json:"memberUpdatePasswordTime"`
58 | MemberUsername string `json:"memberUsername"`
59 | QuicklyTicket interface{} `json:"quicklyTicket"`
60 | RoleCodeList []string `json:"roleCodeList"`
61 | RoleList []struct {
62 | RoleCode string `json:"roleCode"`
63 | RoleComment interface{} `json:"roleComment"`
64 | RoleName string `json:"roleName"`
65 | RoleState interface{} `json:"roleState"`
66 | } `json:"roleList"`
67 | } `json:"obj"`
68 | Success bool `json:"success"`
69 | }
70 |
71 | // GetStudentRte 查询学生返回结构
72 | type GetStudentRte struct {
73 | Attributes interface{} `json:"attributes"`
74 | Count interface{} `json:"count"`
75 | Msg string `json:"msg"`
76 | Obj struct {
77 | StudentAdmissionTime string `json:"studentAdmissionTime"`
78 | StudentAdress interface{} `json:"studentAdress"`
79 | StudentBirthday string `json:"studentBirthday"`
80 | StudentCategory string `json:"studentCategory"`
81 | StudentClassCode string `json:"studentClassCode"`
82 | StudentClassName string `json:"studentClassName"`
83 | StudentEductionalSystme string `json:"studentEductionalSystme"`
84 | StudentFacultiesCode string `json:"studentFacultiesCode"`
85 | StudentFacultiesName string `json:"studentFacultiesName"`
86 | StudentGrade string `json:"studentGrade"`
87 | StudentID string `json:"studentId"`
88 | StudentIDNumber string `json:"studentIdNumber"`
89 | StudentMajor string `json:"studentMajor"`
90 | StudentMajorName string `json:"studentMajorName"`
91 | StudentName string `json:"studentName"`
92 | StudentNation interface{} `json:"studentNation"`
93 | StudentPhone string `json:"studentPhone"`
94 | StudentPoliticalStatus interface{} `json:"studentPoliticalStatus"`
95 | StudentRegisterState string `json:"studentRegisterState"`
96 | StudentSex string `json:"studentSex"`
97 | } `json:"obj"`
98 | Success bool `json:"success"`
99 | }
100 |
101 | // GetStaffRte 教职工返回结构
102 | type GetStaffRte struct {
103 | Attributes interface{} `json:"attributes"`
104 | Count interface{} `json:"count"`
105 | Msg string `json:"msg"`
106 | Obj struct {
107 | StaffAcademicDegree string `json:"staffAcademicDegree"`
108 | StaffAcademicQualifications string `json:"staffAcademicQualifications"`
109 | StaffBelongUnit string `json:"staffBelongUnit"`
110 | StaffBirthday string `json:"staffBirthday"`
111 | StaffCellphoneNumber interface{} `json:"staffCellphoneNumber"`
112 | StaffCredentialsID string `json:"staffCredentialsId"`
113 | StaffCredentialsType string `json:"staffCredentialsType"`
114 | StaffCurrentState string `json:"staffCurrentState"`
115 | StaffLevel interface{} `json:"staffLevel"`
116 | StaffMail interface{} `json:"staffMail"`
117 | StaffName string `json:"staffName"`
118 | StaffNational string `json:"staffNational"`
119 | StaffNumber string `json:"staffNumber"`
120 | StaffPoliticalStatus string `json:"staffPoliticalStatus"`
121 | StaffPostState interface{} `json:"staffPostState"`
122 | StaffSex string `json:"staffSex"`
123 | StaffTechnicalTitles string `json:"staffTechnicalTitles"`
124 | StaffWorkingYears string `json:"staffWorkingYears"`
125 | } `json:"obj"`
126 | Success bool `json:"success"`
127 | }
128 |
129 | // GetClassmatesDetailRte 获取同班同学信息响应结构
130 | type GetClassmatesDetailRte struct {
131 | Attributes struct {
132 | CountMan uint64 `json:"countMan"`
133 | CountWoman uint64 `json:"countWoman"`
134 | InstructorClassClassNumber string `json:"instructorClassClassNumber"`
135 | StudentSex string `json:"studentSex"`
136 | } `json:"attributes"`
137 | Count interface{} `json:"count"`
138 | Msg string `json:"msg"`
139 | Obj interface{} `json:"obj"`
140 | Success bool `json:"success"`
141 | }
142 |
143 | // GetClassmatesRte 获取同班同学列表响应结构
144 | type GetClassmatesRte struct {
145 | Attributes interface{} `json:"attributes"`
146 | Count int64 `json:"count"`
147 | Msg string `json:"msg"`
148 | Obj []struct {
149 | StudentAdmissionTime string `json:"studentAdmissionTime"`
150 | StudentAdress interface{} `json:"studentAdress"`
151 | StudentBirthday string `json:"studentBirthday"`
152 | StudentCategory string `json:"studentCategory"`
153 | StudentClassCode string `json:"studentClassCode"`
154 | StudentClassName string `json:"studentClassName"`
155 | StudentEductionalSystme string `json:"studentEductionalSystme"`
156 | StudentFacultiesCode string `json:"studentFacultiesCode"`
157 | StudentFacultiesName string `json:"studentFacultiesName"`
158 | StudentGrade string `json:"studentGrade"`
159 | StudentID string `json:"studentId"`
160 | StudentIDNumber string `json:"studentIdNumber"`
161 | StudentMajor string `json:"studentMajor"`
162 | StudentMajorName string `json:"studentMajorName"`
163 | StudentName string `json:"studentName"`
164 | StudentNation interface{} `json:"studentNation"`
165 | StudentPhone string `json:"studentPhone"`
166 | StudentPoliticalStatus interface{} `json:"studentPoliticalStatus"`
167 | StudentRegisterState string `json:"studentRegisterState"`
168 | StudentSex string `json:"studentSex"`
169 | } `json:"obj"`
170 | Success bool `json:"success"`
171 | }
172 |
173 | // 课程结构
174 | type Course struct {
175 | CourseAdressCode string `json:"courseAdressCode"`
176 | CourseCategoryName interface{} `json:"courseCategoryName"`
177 | CourseClassCode interface{} `json:"courseClassCode"`
178 | CourseClassName interface{} `json:"courseClassName"`
179 | CourseClassRoomCode interface{} `json:"courseClassRoomCode"`
180 | CourseClassRoomName interface{} `json:"courseClassRoomName"`
181 | CourseCode string `json:"courseCode"`
182 | CourseCredit interface{} `json:"courseCredit"`
183 | CourseDate string `json:"courseDate"`
184 | CourseDepartmentCourse interface{} `json:"courseDepartmentCourse"`
185 | CourseExaminationMethodCode interface{} `json:"courseExaminationMethodCode"`
186 | CourseHours interface{} `json:"courseHours"`
187 | CourseID interface{} `json:"courseId"`
188 | CourseName string `json:"courseName"`
189 | CoursePlan interface{} `json:"coursePlan"`
190 | CourseSchoolYear string `json:"courseSchoolYear"`
191 | CourseSection string `json:"courseSection"`
192 | CourseSectionWeek interface{} `json:"courseSectionWeek"`
193 | CourseSingleDoubleWeek string `json:"courseSingleDoubleWeek"`
194 | CourseStudentID interface{} `json:"courseStudentId"`
195 | CourseStudentName interface{} `json:"courseStudentName"`
196 | CourseSubjectCourseNumber interface{} `json:"courseSubjectCourseNumber"`
197 | CourseTeacherName string `json:"courseTeacherName"`
198 | CourseTeacherNumber interface{} `json:"courseTeacherNumber"`
199 | CourseTeachingNumber interface{} `json:"courseTeachingNumber"`
200 | CourseTerm string `json:"courseTerm"`
201 | CourseTotolHours interface{} `json:"courseTotolHours"`
202 | CourseWeek interface{} `json:"courseWeek"`
203 | CourseWeekNumber interface{} `json:"courseWeekNumber"`
204 | CourseWeekly string `json:"courseWeekly"`
205 | TeacherName string `json:"teacherName"`
206 | }
207 |
208 | // GetWeekCoursesRte 获取周课表返回结构
209 | type GetWeekCoursesRte struct {
210 | Attributes struct {
211 | CourseListTopTwo []Course `json:"courseListTopTwo"`
212 | CourseTime string `json:"courseTime"`
213 | } `json:"attributes"`
214 | Count interface{} `json:"count"`
215 | Msg string `json:"msg"`
216 | Obj []Course `json:"obj"`
217 | Success bool `json:"success"`
218 | }
219 |
220 | // GetOneCardBalanceRte 获取一卡通余额返回结构
221 | type GetOneCardBalanceRte struct {
222 | Attributes interface{} `json:"attributes"`
223 | Count interface{} `json:"count"`
224 | Msg string `json:"msg"`
225 | Obj struct {
226 | Balance string `json:"balance"`
227 | LastMonthMoney string `json:"lastMonthMoney"`
228 | ThisMonthMoney string `json:"thisMonthMoney"`
229 | } `json:"obj"`
230 | Success bool `json:"success"`
231 | }
232 |
233 | // GetOneCardChargeRecordsRte 获取一卡通充值记录返回结构
234 | type GetOneCardChargeRecordsRte struct {
235 | Attributes interface{} `json:"attributes"`
236 | Count int64 `json:"count"`
237 | Msg string `json:"msg"`
238 | Obj []struct {
239 | GeneraCardRechargeRecordID int64 `json:"generaCardRechargeRecordId"`
240 | GeneraCardRechargeRecordNumber string `json:"generaCardRechargeRecordNumber"`
241 | GeneraCardRechargeRecordTransactionAdress string `json:"generaCardRechargeRecordTransactionAdress"`
242 | GeneraCardRechargeRecordTransactionBalance string `json:"generaCardRechargeRecordTransactionBalance"`
243 | GeneraCardRechargeRecordTransactionMoney string `json:"generaCardRechargeRecordTransactionMoney"`
244 | GeneraCardRechargeRecordTransactionTime string `json:"generaCardRechargeRecordTransactionTime"`
245 | GeneraCardRechargeRecordTransactionType string `json:"generaCardRechargeRecordTransactionType"`
246 | GeneraCardRechargeRecordWalletType interface{} `json:"generaCardRechargeRecordWalletType"`
247 | } `json:"obj"`
248 | Success bool `json:"success"`
249 | }
250 |
251 | // GetOneCardConsumeRecordsRte 获取一卡通消费记录返回结构
252 | type GetOneCardConsumeRecordsRte struct {
253 | Attributes interface{} `json:"attributes"`
254 | Count int64 `json:"count"`
255 | Msg string `json:"msg"`
256 | Obj []struct {
257 | GeneraCardConsumeRecordID int64 `json:"generaCardConsumeRecordId"`
258 | GeneraCardConsumeRecordNumber string `json:"generaCardConsumeRecordNumber"`
259 | GeneraCardConsumeRecordTransactionAdress string `json:"generaCardConsumeRecordTransactionAdress"`
260 | GeneraCardConsumeRecordTransactionBalance string `json:"generaCardConsumeRecordTransactionBalance"`
261 | GeneraCardConsumeRecordTransactionMoney string `json:"generaCardConsumeRecordTransactionMoney"`
262 | GeneraCardConsumeRecordTransactionTime string `json:"generaCardConsumeRecordTransactionTime"`
263 | GeneraCardConsumeRecordTransactionType string `json:"generaCardConsumeRecordTransactionType"`
264 | GeneraCardConsumeRecordWalletType interface{} `json:"generaCardConsumeRecordWalletType"`
265 | } `json:"obj"`
266 | Success bool `json:"success"`
267 | }
268 |
269 | type ExamArrangement struct {
270 | ExaminationAdressCode interface{} `json:"examinationAdressCode"`
271 | ExaminationAdressName string `json:"examinationAdressName"`
272 | ExaminationCourseCode string `json:"examinationCourseCode"`
273 | ExaminationCourseName string `json:"examinationCourseName"`
274 | ExaminationEndTime string `json:"examinationEndTime"`
275 | ExaminationSchoolYear string `json:"examinationSchoolYear"`
276 | ExaminationSeat string `json:"examinationSeat"`
277 | ExaminationStartTime string `json:"examinationStartTime"`
278 | ExaminationStudentID string `json:"examinationStudentId"`
279 | ExaminationStudentName string `json:"examinationStudentName"`
280 | ExaminationTerm string `json:"examinationTerm"`
281 | ExaminationTime string `json:"examinationTime"`
282 | }
283 |
284 | // GetExamArrangementsRte 获取考试安排返回结构
285 | type GetExamArrangementsRte struct {
286 | Attributes interface{} `json:"attributes"`
287 | Count interface{} `json:"count"`
288 | Msg string `json:"msg"`
289 | Obj []ExamArrangement `json:"obj"`
290 | Success bool `json:"success"`
291 | }
292 |
293 | // GetClassStudentsRte 获取班级学生返回结构
294 | type GetClassStudentsRte struct {
295 | Attributes interface{} `json:"attributes"`
296 | Count int64 `json:"count"`
297 | Msg string `json:"msg"`
298 | Obj []struct {
299 | StudentAdmissionTime string `json:"studentAdmissionTime"`
300 | StudentAdress interface{} `json:"studentAdress"`
301 | StudentBirthday string `json:"studentBirthday"`
302 | StudentCategory interface{} `json:"studentCategory"`
303 | StudentClassCode string `json:"studentClassCode"`
304 | StudentClassName string `json:"studentClassName"`
305 | StudentEductionalSystme string `json:"studentEductionalSystme"`
306 | StudentFacultiesCode string `json:"studentFacultiesCode"`
307 | StudentFacultiesName string `json:"studentFacultiesName"`
308 | StudentGrade string `json:"studentGrade"`
309 | StudentID string `json:"studentId"`
310 | StudentIDNumber string `json:"studentIdNumber"`
311 | StudentMajor string `json:"studentMajor"`
312 | StudentMajorName string `json:"studentMajorName"`
313 | StudentName string `json:"studentName"`
314 | StudentNation interface{} `json:"studentNation"`
315 | StudentPhone string `json:"studentPhone"`
316 | StudentPoliticalStatus interface{} `json:"studentPoliticalStatus"`
317 | StudentRegisterState string `json:"studentRegisterState"`
318 | StudentSex string `json:"studentSex"`
319 | } `json:"obj"`
320 | Success bool `json:"success"`
321 | }
322 |
323 | // GetAllInvigilateRte 获取监考安排返回结构
324 | type GetAllInvigilateRte struct {
325 | Attributes interface{} `json:"attributes"`
326 | Count interface{} `json:"count"`
327 | Msg string `json:"msg"`
328 | Obj []struct {
329 | InvigilateAdressCode interface{} `json:"invigilateAdressCode"`
330 | InvigilateAdressName string `json:"invigilateAdressName"`
331 | InvigilateCourseCode string `json:"invigilateCourseCode"`
332 | InvigilateCourseName string `json:"invigilateCourseName"`
333 | InvigilateEndTime string `json:"invigilateEndTime"`
334 | InvigilateExaminationBatch string `json:"invigilateExaminationBatch"`
335 | InvigilateExaminationCount string `json:"invigilateExaminationCount"`
336 | InvigilateID int64 `json:"invigilateId"`
337 | InvigilateSchoolYear string `json:"invigilateSchoolYear"`
338 | InvigilateStaffNumber string `json:"invigilateStaffNumber"`
339 | InvigilateStartTime string `json:"invigilateStartTime"`
340 | InvigilateTerm string `json:"invigilateTerm"`
341 | InvigilateTime string `json:"invigilateTime"`
342 | } `json:"obj"`
343 | Success bool `json:"success"`
344 | }
345 |
346 | // GetAssetsRte 获取资产返回结构
347 | type GetAssetsRte struct {
348 | Attributes interface{} `json:"attributes"`
349 | Count int64 `json:"count"`
350 | Msg string `json:"msg"`
351 | Obj []struct {
352 | AssetsAmount string `json:"assetsAmount"`
353 | AssetsBookkeeper string `json:"assetsBookkeeper"`
354 | AssetsCode interface{} `json:"assetsCode"`
355 | AssetsDepositName string `json:"assetsDepositName"`
356 | AssetsFinancialNationality string `json:"assetsFinancialNationality"`
357 | AssetsFinancialVoucher string `json:"assetsFinancialVoucher"`
358 | AssetsFinancialWarrantyPeriod string `json:"assetsFinancialWarrantyPeriod"`
359 | AssetsFundsSubject string `json:"assetsFundsSubject"`
360 | AssetsHandlers string `json:"assetsHandlers"`
361 | AssetsManufactor string `json:"assetsManufactor"`
362 | AssetsModel string `json:"assetsModel"`
363 | AssetsMoney string `json:"assetsMoney"`
364 | AssetsName string `json:"assetsName"`
365 | AssetsPurchaseDate string `json:"assetsPurchaseDate"`
366 | AssetsSpecification string `json:"assetsSpecification"`
367 | AssetsStaffID int64 `json:"assetsStaffId"`
368 | AssetsStaffName string `json:"assetsStaffName"`
369 | AssetsStaffNumber string `json:"assetsStaffNumber"`
370 | AssetsType string `json:"assetsType"`
371 | AssetsUnitName string `json:"assetsUnitName"`
372 | } `json:"obj"`
373 | Success bool `json:"success"`
374 | }
375 |
376 | // DepartmentPhoneListRte 获取部门电话返回结构
377 | type DepartmentPhoneListRte struct {
378 | Attributes interface{} `json:"attributes"`
379 | Count interface{} `json:"count"`
380 | Msg string `json:"msg"`
381 | Obj interface{} `json:"obj"`
382 | Success bool `json:"success"`
383 | }
384 |
--------------------------------------------------------------------------------
/client/sec/portal.go:
--------------------------------------------------------------------------------
1 | package sec
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "strconv"
7 | )
8 |
9 | // GetCurrentMember 获取当前用户信息
10 | func (u *SecUser) GetCurrentMember() (rte CurrentMemberRte, err error) {
11 |
12 | resp, _ := u.Client.R().
13 | SetHeader("referer", u.PortalUrlPerfix+PortalUserPath).
14 | Get(u.PortalUrlPerfix + GetCurrentMemberPath + "?vpn-0")
15 |
16 | err = json.Unmarshal(resp.Body(), &rte)
17 | if err != nil {
18 | return
19 | }
20 |
21 | // 接口错误解析
22 | if !rte.Success {
23 | err = errors.New(rte.Msg)
24 | }
25 |
26 | // 门户未登陆的情况
27 | if len(rte.Obj.MemberID) == 0 {
28 | err = errors.New("portal not be logged")
29 | }
30 |
31 | return
32 | }
33 |
34 | // GetStudentByStuID 通过学号获取学生信息
35 | func (u *SecUser) GetStudentByStuID(stuID string) (rte GetStudentRte, err error) {
36 |
37 | resp, _ := u.Client.R().
38 | SetHeader("referer", u.PortalUrlPerfix+PortalUserPath).
39 | SetFormData(map[string]string{
40 | "studentId": stuID,
41 | }).
42 | Post(u.PortalUrlPerfix + GetStuPath + "?vpn-0")
43 |
44 | err = json.Unmarshal(resp.Body(), &rte)
45 | if err != nil {
46 | return
47 | }
48 |
49 | // 接口错误解析
50 | if !rte.Success {
51 | err = errors.New(rte.Msg)
52 | }
53 |
54 | // 门户未登陆的情况
55 | if len(rte.Obj.StudentID) == 0 {
56 | err = errors.New("no result")
57 | }
58 |
59 | return
60 | }
61 |
62 | // GetStudent 获取学生信息
63 | func (u *SecUser) GetStudent() (GetStudentRte, error) {
64 | return u.GetStudentByStuID(u.IDAesEncrypt)
65 | }
66 |
67 | // GetClassmatesByStuI 通过学号获取学生同班同学信息
68 | func (u *SecUser) GetClassmatesDetailByStuID(stuID string) (rte GetClassmatesDetailRte, err error) {
69 |
70 | resp, _ := u.Client.R().
71 | SetHeader("referer", u.PortalUrlPerfix+PortalUserPath).
72 | SetFormData(map[string]string{
73 | "userName": stuID,
74 | }).
75 | Post(u.PortalUrlPerfix + GetClassmatesDetailPath + "?vpn-0")
76 |
77 | err = json.Unmarshal(resp.Body(), &rte)
78 | if err != nil {
79 | return
80 | }
81 |
82 | // 接口错误解析
83 | if !rte.Success {
84 | err = errors.New(rte.Msg)
85 | }
86 |
87 | return
88 | }
89 |
90 | // GetClassmatesDetail 获取同班同学详情
91 | func (u *SecUser) GetClassmatesDetail() (GetClassmatesDetailRte, error) {
92 | return u.GetClassmatesDetailByStuID(u.IDAesEncrypt)
93 | }
94 |
95 | // GetClassmatesByStuID 通过学号获取学生同班同学列表
96 | // pageNum := "1"
97 | // pageSize := "99"
98 | func (u *SecUser) GetClassmatesByStuID(stuID string, pageNum int, pageSize int) (rte GetClassmatesRte, err error) {
99 | resp, _ := u.Client.R().
100 | SetHeader("referer", u.PortalUrlPerfix+PortalUserPath).
101 | SetFormData(map[string]string{
102 | "userName": stuID,
103 | "pageNum": strconv.Itoa(pageNum),
104 | "pageSize": strconv.Itoa(pageSize),
105 | }).
106 | Post(u.PortalUrlPerfix + GetClassmatesPath + "?vpn-0")
107 |
108 | err = json.Unmarshal(resp.Body(), &rte)
109 | if err != nil {
110 | return
111 | }
112 |
113 | // 接口错误解析
114 | if !rte.Success {
115 | err = errors.New(rte.Msg)
116 | }
117 |
118 | return
119 | }
120 |
121 | // GetClassmates 获取同班同学
122 | func (u *SecUser) GetClassmates(pageNum int, pageSize int) (GetClassmatesRte, error) {
123 | return u.GetClassmatesByStuID(u.IDAesEncrypt, pageNum, pageSize)
124 | }
125 |
126 | // GetWeekCoursesByID 通过账号获取获取周课表
127 | // currentTime: 如 2021-10-29
128 | // role: {学生:1, 教师:2}
129 | func (u *SecUser) GetWeekCoursesByID(username string, currentTime string, role int) (rte GetWeekCoursesRte, err error) {
130 |
131 | resp, _ := u.Client.R().
132 | SetHeader("referer", u.PortalUrlPerfix+PortalUserPath).
133 | SetFormData(map[string]string{
134 | "userName": username,
135 | "currentTime": currentTime,
136 | "role": strconv.Itoa(role),
137 | }).
138 | Post(u.PortalUrlPerfix + GetWeekCoursesPath + "?vpn-0")
139 |
140 | err = json.Unmarshal(resp.Body(), &rte)
141 | if err != nil {
142 | return
143 | }
144 |
145 | // 接口错误解析
146 | if !rte.Success {
147 | err = errors.New(rte.Msg)
148 | }
149 |
150 | // 接口错误解析
151 | if !rte.Success {
152 | err = errors.New(rte.Msg)
153 | }
154 |
155 | return
156 | }
157 |
158 | // GetWeekCourses 获取周课表
159 | // currentTime: 如 2021-10-29
160 | // role: {学生:1, 教师:2}
161 | func (u *SecUser) GetWeekCourses(currentTime string, role int) (rte GetWeekCoursesRte, err error) {
162 | return u.GetWeekCoursesByID(u.IDAesEncrypt, currentTime, role)
163 | }
164 |
165 | // GetExamArrangementsByStuID 通过学号获取考试安排
166 | // schoolYear := "2021" // 学年
167 | // schoolTerm := "1" // {第一学期:0,第二学期:1}
168 | func (u *SecUser) GetExamArrangementsByStuID(stuID string, schoolYear int, schoolTerm int) (rte GetExamArrangementsRte, err error) {
169 |
170 | resp, _ := u.Client.R().
171 | SetHeader("referer", u.PortalUrlPerfix+PortalUserPath).
172 | SetFormData(map[string]string{
173 | "examinationStudentId": stuID,
174 | "examinationSchoolYear": strconv.Itoa(schoolYear),
175 | "examinationTerm": strconv.Itoa(schoolTerm),
176 | }).
177 | Post(u.PortalUrlPerfix + GetExamArrangementsPath + "?vpn-0")
178 |
179 | err = json.Unmarshal(resp.Body(), &rte)
180 | if err != nil {
181 | return
182 | }
183 |
184 | // 接口错误解析
185 | if !rte.Success {
186 | err = errors.New(rte.Msg)
187 | }
188 |
189 | return
190 | }
191 |
192 | // GetExamArrangement 获取考试安排
193 | // schoolYear := "2021" // 学年
194 | // schoolTerm := "1" // {第一学期:0,第二学期:1}
195 | func (u *SecUser) GetExamArrangemen(schoolYear int, schoolTerm int) (rte GetExamArrangementsRte, err error) {
196 | return u.GetExamArrangementsByStuID(u.IDAesEncrypt, schoolYear, schoolTerm)
197 | }
198 |
199 | // GetOneCardConsumeRecordsByID 通过帐号获取一卡通充值记录
200 | // pageNum := "1"
201 | // pageSize := "99"
202 | func (u *SecUser) GetOneCardConsumeRecordsByID(ID string, pageNum int, pageSize int) (rte GetOneCardConsumeRecordsRte, err error) { // GetOneCardConsumeRecords 通过学号获取一卡通充值记录
203 |
204 | resp, _ := u.Client.R().
205 | SetHeader("referer", u.PortalUrlPerfix+PortalUserPath).
206 | SetFormData(map[string]string{
207 | "generaCardConsumeRecordNumber": ID,
208 | "pageNum": strconv.Itoa(pageNum),
209 | "pageSize": strconv.Itoa(pageSize),
210 | }).
211 | Post(u.PortalUrlPerfix + GetOneCardConsumeRecordsPath + "?vpn-0")
212 |
213 | err = json.Unmarshal(resp.Body(), &rte)
214 | if err != nil {
215 | return
216 | }
217 |
218 | // 接口错误解析
219 | if !rte.Success {
220 | err = errors.New(rte.Msg)
221 | }
222 |
223 | return
224 | }
225 |
226 | // GetOneCardConsumeRecords 通帐号获取一卡通充值记录
227 | // pageNum := "1"
228 | // pageSize := "99"
229 | func (u *SecUser) GetOneCardConsumeRecords(pageNum int, pageSize int) (rte GetOneCardConsumeRecordsRte, err error) { // GetOneCardConsumeRecords 通过学号获取一卡通充值记录
230 | return u.GetOneCardConsumeRecordsByID(u.IDAesEncrypt, pageNum, pageSize)
231 | }
232 |
233 | // GetOneCardChargeRecordsByID 通过帐号获取一卡通充值记录
234 | // pageNum := "1"
235 | // pageSize := "99"
236 | func (u *SecUser) GetOneCardChargeRecordsByID(ID string, pageNum int, pageSize int) (rte GetOneCardChargeRecordsRte, err error) {
237 |
238 | resp, _ := u.Client.R().
239 | SetHeader("referer", u.PortalUrlPerfix+PortalUserPath).
240 | SetFormData(map[string]string{
241 | "generaCardRechargeRecordNumber": ID,
242 | "pageNum": strconv.Itoa(pageNum),
243 | "pageSize": strconv.Itoa(pageSize),
244 | }).
245 | Post(u.PortalUrlPerfix + GetOneCardChargeRecordsPath + "?vpn-0")
246 |
247 | err = json.Unmarshal(resp.Body(), &rte)
248 | if err != nil {
249 | return
250 | }
251 |
252 | // 接口错误解析
253 | if !rte.Success {
254 | err = errors.New(rte.Msg)
255 | }
256 |
257 | return
258 |
259 | }
260 |
261 | // GetOneCardConsumeRecords 通帐号获取一卡通充值记录
262 | // pageNum := "1"
263 | // pageSize := "99"
264 | func (u *SecUser) GetOneCardChargeRecords(pageNum int, pageSize int) (rte GetOneCardChargeRecordsRte, err error) { // GetOneCardConsumeRecords 通过学号获取一卡通充值记录
265 | return u.GetOneCardChargeRecordsByID(u.IDAesEncrypt, pageNum, pageSize)
266 | }
267 |
268 | // GetOneCardBalanceByID 通过帐号获取一卡通剩余金额
269 | func (u *SecUser) GetOneCardBalanceByID(ID string) (rte GetOneCardBalanceRte, err error) {
270 |
271 | resp, _ := u.Client.R().
272 | SetHeader("referer", u.PortalUrlPerfix+PortalUserPath).
273 | SetFormData(map[string]string{
274 | "username": ID,
275 | }).
276 | Post(u.PortalUrlPerfix + GetOneCardBalancePath + "?vpn-0")
277 |
278 | err = json.Unmarshal(resp.Body(), &rte)
279 | if err != nil {
280 | return
281 | }
282 |
283 | // 接口错误解析
284 | if !rte.Success {
285 | err = errors.New(rte.Msg)
286 | }
287 |
288 | return
289 |
290 | }
291 |
292 | // GetOneCardBalance 获取一卡通剩余金额
293 | func (u *SecUser) GetOneCardBalance() (rte GetOneCardBalanceRte, err error) {
294 | return u.GetOneCardBalanceByID(u.IDAesEncrypt)
295 | }
296 |
297 | // GetStaffByStaffID 通过职工号获取教职工信息
298 | func (u *SecUser) GetStaffByStaffID(staffID string) (rte GetStaffRte, err error) {
299 |
300 | resp, _ := u.Client.R().
301 | SetHeader("referer", u.PortalUrlPerfix+PortalUserPath).
302 | SetFormData(map[string]string{
303 | "staffNumber": staffID,
304 | }).
305 | Post(u.PortalUrlPerfix + GetStaffPath + "?vpn-0")
306 |
307 | err = json.Unmarshal(resp.Body(), &rte)
308 | if err != nil {
309 | return
310 | }
311 |
312 | // 接口错误解析
313 | if !rte.Success {
314 | err = errors.New(rte.Msg)
315 | }
316 |
317 | // 门户未登陆的情况
318 | if len(rte.Obj.StaffName) == 0 {
319 | err = errors.New("no result")
320 | }
321 |
322 | return
323 | }
324 |
325 | // GetStaff 获取教职工信息
326 | func (u *SecUser) GetStaff() (rte GetStaffRte, err error) {
327 | return u.GetStaffByStaffID(u.IDAesEncrypt)
328 | }
329 |
330 | // GetClassStudents 获取班级学生
331 | func (u *SecUser) GetClassStudents(classCode string, pageNum int, pageSize int) (rte GetClassStudentsRte, err error) {
332 |
333 | resp, _ := u.Client.R().
334 | SetHeader("referer", u.PortalUrlPerfix+PortalUserPath).
335 | SetFormData(map[string]string{
336 | "classcode": classCode,
337 | "pageNum": strconv.Itoa(pageNum),
338 | "pageSize": strconv.Itoa(pageSize),
339 | }).
340 | Post(u.PortalUrlPerfix + GetClassStudentsPath + "?vpn-0")
341 |
342 | err = json.Unmarshal(resp.Body(), &rte)
343 | if err != nil {
344 | return
345 | }
346 |
347 | // 接口错误解析
348 | if !rte.Success {
349 | err = errors.New(rte.Msg)
350 | return
351 | }
352 |
353 | // if len(rte.Obj) == 0 {
354 | // err = errors.New("no result")
355 | // }
356 |
357 | return
358 | }
359 |
360 | // GetAllInvigilateByStaffID 通过职工号获取监考安排
361 | // schoolYear := "2021" // 学年
362 | // schoolTerm := "1" // {第一学期:0,第二学期:1}
363 | func (u *SecUser) GetAllInvigilateByStaffID(StaffID string, schoolYear int, schoolTerm int) (rte GetAllInvigilateRte, err error) {
364 |
365 | resp, _ := u.Client.R().
366 | SetHeader("referer", u.PortalUrlPerfix+PortalUserPath).
367 | SetFormData(map[string]string{
368 | "invigilateStaffNumber": StaffID,
369 | "invigilateSchoolYear": strconv.Itoa(schoolYear),
370 | "invigilateTerm": strconv.Itoa(schoolTerm),
371 | }).
372 | Post(u.PortalUrlPerfix + GetAllInvigilatePath + "?vpn-0")
373 |
374 | err = json.Unmarshal(resp.Body(), &rte)
375 | if err != nil {
376 | return
377 | }
378 |
379 | // 接口错误解析
380 | if !rte.Success {
381 | err = errors.New(rte.Msg)
382 | }
383 |
384 | return
385 | }
386 |
387 | // GetAllInvigilate 获取监考安排
388 | // schoolYear := "2021" // 学年
389 | // schoolTerm := "1" // {第一学期:0,第二学期:1}
390 | func (u *SecUser) GetAllInvigilate(schoolYear int, schoolTerm int) (rte GetAllInvigilateRte, err error) {
391 | return u.GetAllInvigilateByStaffID(u.Username, schoolYear, schoolTerm)
392 | }
393 |
394 | // GetAssetsByStaffID 通过职工号获取资产
395 | func (u *SecUser) GetAssetsByStaffID(staffID string, pageNum int, pageSize int) (rte GetAssetsRte, err error) {
396 |
397 | resp, _ := u.Client.R().
398 | SetHeader("referer", u.PortalUrlPerfix+PortalUserPath).
399 | SetFormData(map[string]string{
400 | "assetsStaffNumber": staffID,
401 | "pageNum": strconv.Itoa(pageNum),
402 | "pageSize": strconv.Itoa(pageSize),
403 | }).
404 | Post(u.PortalUrlPerfix + GetAssetsPath + "?vpn-0")
405 |
406 | err = json.Unmarshal(resp.Body(), &rte)
407 | if err != nil {
408 | return
409 | }
410 |
411 | // 接口错误解析
412 | if !rte.Success {
413 | err = errors.New(rte.Msg)
414 | }
415 |
416 | return
417 | }
418 |
419 | // GetAssetsByStaff 获取资产
420 | func (u *SecUser) GetAssets(pageNum int, pageSize int) (rte GetAssetsRte, err error) {
421 | return u.GetAssetsByStaffID(u.IDAesEncrypt, pageNum, pageSize)
422 | }
423 |
424 | // GetDepartmentPhoneList 获取部门电话
425 | func (u *SecUser) GetDepartmentPhoneList() (rte DepartmentPhoneListRte, err error) {
426 | resp, _ := u.Client.R().
427 | SetHeader("referer", u.PortalUrlPerfix+PortalUserPath).
428 | Get(u.PortalUrlPerfix + GetDepartmentPhoneListPath + "?vpn-0")
429 |
430 | err = json.Unmarshal(resp.Body(), &rte)
431 | if err != nil {
432 | return
433 | }
434 |
435 | // 接口错误解析
436 | if !rte.Success {
437 | err = errors.New(rte.Msg)
438 | }
439 |
440 | return
441 | }
442 |
--------------------------------------------------------------------------------
/client/sec/sec.go:
--------------------------------------------------------------------------------
1 | package sec
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | "time"
7 |
8 | "github.com/go-resty/resty/v2"
9 | "github.com/icepie/oh-my-lit/client/util"
10 | )
11 |
12 | // 一些常量的定义
13 | var (
14 | AuthorityUrl = "sec.lit.edu.cn"
15 | // SecUrl 智慧门户主页
16 | SecUrl = "https://" + AuthorityUrl
17 | // LibraryUrl
18 | LibraryUrl = SecUrl + "/rump_frontend/connect/?target=Library&id=9"
19 |
20 | // AuthPath 认证界面的特殊路径
21 | //AuthPath = "LjIwNi4xNzAuMjE4LjE2Mg==/LjIwNy4xNTQuMjE3Ljk2LjE2MS4xNTkuMTY0Ljk3LjE1MS4xOTkuMTczLjE0NC4xOTguMjEy"
22 | // PortalPath 门户界面的特殊路径
23 | //PortalPath = "LjIwNi4xNzAuMjE4LjE2Mg==/LjIxMS4xNzUuMTQ4LjE1OC4xNTguMTcwLjk0LjE1Mi4xNTAuMjE2LjEwMi4xOTcuMjA5"
24 | // AuthlUrlPerfix 认证页面前戳
25 | // AuthlUrlPerfix = SecUrl + "/webvpn/" + AuthPath
26 | // PortalUrlPerfix 门户页面前戳
27 | //PortalUrlPerfix = SecUrl + "/webvpn/" + PortalPath
28 |
29 | //JWUrlPerfix
30 | JWUrlPerfix = SecUrl + "/webvpn/LjE1Ni4xNzEuMjE2LjE2NQ==/LjE1OC4xNzQuMTQ2LjE2MS4xNTkuMTczLjE0NS4xNTguMTk5LjE2Ni45NS4xNTIuMTU4"
31 | // NeedCaptchaPath 检查是否需要验证码登陆的接口
32 | NeedCaptchaPath = "/authserver/needCaptcha.html"
33 | // CaptchaPath 获取验证码
34 | CaptchaPath = "/authserver/captcha.html"
35 | // HomeIndexUrl 导航主页
36 | HomeIndexUrl = SecUrl + "/frontend_static/frontend/login/index.html"
37 | // GetHomeParamUrl 主页参数
38 | GetHomeParamUrl = SecUrl + "/rump_frontend/getHomeParam/"
39 | // Home
40 | // PortalIndexPath 门户首页
41 | PortalIndexPath = "/pc/lit/index.html"
42 | // PortalLoginPath 门户登陆地址 (第二层)
43 | PortalLoginPath = "/portal/login/pcLogin"
44 | // PortalUserPath 门户个人信息主页
45 | PortalUserPath = "/portal/pc/lit/user.html"
46 | // GetCurrentMemberPath 获取当前门户用户的接口
47 | GetCurrentMemberPath = "/portal/myCenter/getMemberInfoForCurrentMember"
48 | // GetStuPath 获取学生信息接口
49 | GetStuPath = "/microapplication/api/v1/index/getStudentByStudentId"
50 | // GetClassmatesDetail 获取学生同班同学信息接口
51 | GetClassmatesDetailPath = "/microapplication/api/myclass/findMyclassmatesDetailCount"
52 | // GetClassmatesPath 获取学生同班同学列表接口
53 | GetClassmatesPath = "/microapplication/api/myclass/findMyclassmates"
54 | // GetOneCardBalancePath 获取一卡通余额接口
55 | GetOneCardBalancePath = "/microapplication/api/v1/index/getBalanceAndConsumeThisMonthAndLastMonth"
56 | // GetOneCardChargeRecordsPath 获取一卡通充值记录接口
57 | GetOneCardChargeRecordsPath = "/microapplication/api/v1/index/listGeneraCardRechargeRecordByGeneraCardRechargeRecordNumberPage"
58 | // GetOneCardChargeRecordsUrl 获取一卡通消费记录接口
59 | GetOneCardConsumeRecordsPath = "/microapplication/api/v1/index/ListGeneraCardConsumeRecordByGeneraCardConsumeRecordNumberPage"
60 | // GetExamArrangementsPath 获取考试安排接口
61 | GetExamArrangementsPath = "/microapplication/api/examArrangementController/findAllExamArrangements"
62 | // GetweekCourses 获取周课表接口
63 | GetWeekCoursesPath = "/microapplication/api/course/getCourse"
64 | // GetDepartmentPhoneList 获取部门电话列表
65 | GetDepartmentPhoneListPath = "/microapplication/api/queryDepartmentPhone/querydepartmentphonelist"
66 | // GetStaffPath 获取教职工接口
67 | GetStaffPath = "/microapplication/api/index/getStaffByStaffNumber"
68 | // GetClassStudents 获取班级学生接口
69 | GetClassStudentsPath = "/microapplication/api/myclass/findTeachclassStudents"
70 | // GetAllInvigilate 获取监考信息接口
71 | GetAllInvigilatePath = "/microapplication/api/examArrangementController/findAllInvigilate"
72 | // GetGetAssetsPath 获取资产接口
73 | GetAssetsPath = "/microapplication/api/index/listAssetsByAssetsStaffNumberPage"
74 | // UA
75 | UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36"
76 | // MainHeaders
77 | // MainHeaders 主请求头
78 | MainHeaders = map[string]string{
79 | "authority": AuthorityUrl,
80 | "dnt": "1",
81 | "x-requested-with": "XMLHttpRequest",
82 | "sec-ch-ua-mobile": "1",
83 | "User-Agent": UA,
84 | "sec-fetch-site": "same-origin",
85 | "sec-fetch-mode": "cors",
86 | "sec-fetch-dest": "empty",
87 | "Accept-Language": "zh-CN,zh;q=0.9",
88 | }
89 | )
90 |
91 | // SecUser 智慧门户用户结构体
92 | type SecUser struct {
93 | Username string
94 | Password string
95 | IDAesEncrypt string
96 | AuthUrl string // 真实认证地址 (SecUrl" + "/webvpn/LjIwNi4xNzAuMjE4LjE2Mg==/LjIwNy4xNTQuMjE3Ljk2LjE2MS4xNTkuMTY0Ljk3LjE1MS4xOTkuMTczLjE0NC4xOTguMjEy/authserver/login?service=https%3A%2F%2Fsec.lit.edu.cn%2Frump_frontend%2FloginFromCas%2F)
97 | // AuthPath string
98 | AuthUrlPerfix string
99 | PortalUrlPerfix string
100 | Client *resty.Client
101 | }
102 |
103 | // NewSecUser 新建智慧门户用户
104 | func NewSecUser() *SecUser {
105 |
106 | var u SecUser
107 |
108 | u.Client = resty.New()
109 | u.Client.SetHeaders(MainHeaders)
110 | u.Client.SetTimeout(60 * time.Second)
111 |
112 | // 刷新 webvpn path
113 | u.PerSetCooikes()
114 |
115 | return &u
116 | }
117 |
118 | // SetPassword 设置用户名
119 | func (u *SecUser) SetUsername(username string) *SecUser {
120 | u.Username = username
121 | return u
122 | }
123 |
124 | // SetPassword 设置密码
125 | func (u *SecUser) SetPassword(password string) *SecUser {
126 | u.Password = password
127 | return u
128 | }
129 |
130 | func (u *SecUser) PerSetCooikes() (err error) {
131 |
132 | // 先访问一下页面,获取cookie
133 | resp, _ := u.Client.R().
134 | Get(SecUrl)
135 |
136 | u.AuthUrl, err = util.GetSubstringBetweenStringsByRE(resp.String(), `ul.mui-table-view").Each(func(i int, s *goquery.Selection) {
27 |
28 | var de DormElectricity
29 |
30 | name := s.Find("li.mui-table-view-divider").First()
31 | de.Name = name.Text()
32 |
33 | s.Find("span.mui-badge").Each(func(i int, s *goquery.Selection) {
34 | switch i {
35 | case 0:
36 | de.BuildName = s.Text()
37 | case 1:
38 | de.Room = s.Text()
39 | case 2:
40 | de.Electricity = s.Text()
41 | case 3:
42 | de.Balance = s.Text()
43 | case 4:
44 | de.ElectricitySubsidy = s.Text()
45 | case 5:
46 | de.BalanceSubsidy = s.Text()
47 | }
48 |
49 | })
50 | rte = append(rte, de)
51 | })
52 |
53 | return
54 |
55 | }
56 |
57 | // GetElectricityDetails 获取寝室用电明细
58 | func (u *ZhydUser) GetElectricityDetails() (rte []ElectricityDetails, err error) {
59 |
60 | resp, _ := u.Client.R().
61 | Get(GetElectricityDetailsUrl)
62 |
63 | doc, err := goquery.NewDocumentFromReader(bytes.NewReader(resp.Body()))
64 | if err != nil {
65 | return
66 | }
67 |
68 | doc.Find("div.mui-content>div").Each(func(i int, s *goquery.Selection) {
69 |
70 | var ed ElectricityDetails
71 |
72 | name := s.Find("div.mui-card>ul.mui-table-view>li.mui-table-view-divider").First()
73 | ed.Name = name.Text()
74 |
75 | s.Find("div.mui-card>ul.mui-table-view>li>span.mui-badge").Each(func(i int, s *goquery.Selection) {
76 | switch i {
77 | case 0:
78 | ed.BuildName = s.Text()
79 | case 1:
80 | ed.Room = s.Text()
81 | case 2:
82 | ed.Electricity = s.Text()
83 | }
84 | })
85 |
86 | // 用电详情获取
87 | s.Find("div.mui-scroll>li.mui-table-view-cell").Each(func(i int, s *goquery.Selection) {
88 |
89 | // 去除空格
90 | timeStr := strings.TrimSpace(s.Nodes[0].FirstChild.Data)
91 |
92 | // timeStr = strings.Trim(timeStr, "\n")
93 |
94 | // log.Println(timeStr)
95 |
96 | var d Detail
97 |
98 | dTime, err := time.ParseInLocation(TimeLayout, timeStr, Location)
99 | if err != nil {
100 | return
101 | }
102 |
103 | d.Time = dTime.Format("2006-01-02 15:04:05")
104 |
105 | v := s.Find("span.mui-badge.mui-badge-primary").First().Text()
106 | if len(v) > 0 {
107 | d.Value = v
108 | }
109 |
110 | ed.Details = append(ed.Details, d)
111 |
112 | })
113 |
114 | // 添加到最后结果
115 | rte = append(rte, ed)
116 | })
117 |
118 | return
119 |
120 | }
121 |
122 | // GetChargeRecords 获取消费记录
123 | func (u *ZhydUser) GetChargeRecords() (rte []ChargeRecords, err error) {
124 |
125 | resp, _ := u.Client.R().
126 | Get(GetChargeRecordsUrl)
127 |
128 | reg := regexp.MustCompile(`this.infoList = \[(.*)\]`)
129 |
130 | result := reg.FindAllStringSubmatch(resp.String(), -1)
131 |
132 | if len(result) == 0 {
133 | err = errors.New("no result")
134 | return
135 | }
136 |
137 | err = json.Unmarshal([]byte("["+result[0][1]+"]"), &rte)
138 | if err != nil {
139 | return
140 | }
141 |
142 | return
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/client/zhyd/login.go:
--------------------------------------------------------------------------------
1 | package zhyd
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | "github.com/icepie/oh-my-lit/client/util"
8 | )
9 |
10 | func (u *ZhydUser) IsLogged() bool {
11 |
12 | resp, _ := u.Client.R().
13 | Get(ZhydHostUrl)
14 |
15 | return !strings.Contains(resp.String(), "统一身份认证")
16 | }
17 |
18 | // // IsNeedCaptcha 判断是否需要验证码登陆
19 | // func (u *ZhydUser) IsNeedCaptcha() (isNeed bool, err error) {
20 |
21 | // resp, reqErr := u.Client.R().
22 | // SetQueryParams(map[string]string{
23 | // "username": u.Username,
24 | // "_": fmt.Sprint(time.Now().Unix()),
25 | // }).
26 | // Get(NeedCaptchaUrl)
27 |
28 | // if resp.StatusCode() != 200 {
29 | // err = reqErr
30 | // return
31 | // }
32 |
33 | // body := resp.String()
34 |
35 | // // 最后判断是否需要验证码进行登陆
36 | // if strings.HasPrefix(body, "false") {
37 | // isNeed = false
38 | // } else if strings.HasPrefix(body, "true") {
39 | // isNeed = true
40 | // } else {
41 | // err = errors.New("can not get the info")
42 | // }
43 |
44 | // return
45 |
46 | // }
47 |
48 | // // GetCaptche 获取验证码 (JPEG)
49 | // func (u *ZhydUser) GetCaptche() (pix []byte, err error) {
50 |
51 | // resp, err := u.Client.R().
52 | // SetQueryParams(map[string]string{
53 | // "username": u.Username,
54 | // "_": fmt.Sprint(time.Now().Unix()),
55 | // }).
56 | // SetHeader("accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8").
57 | // Get(CaptchaUrl)
58 |
59 | // if err != nil {
60 | // return
61 | // }
62 |
63 | // pix = resp.Body()
64 |
65 | // return
66 | // }
67 |
68 | // 登陆凑合用
69 | func (u *ZhydUser) login(captcha string) (err error) {
70 |
71 | if len(u.Username) == 0 {
72 | err = errors.New("empty username")
73 | return
74 | }
75 |
76 | if len(u.Password) == 0 {
77 | err = errors.New("empty password")
78 | return
79 | }
80 |
81 | // 禁止重定向
82 | // tmpClient := u.Client.SetRedirectPolicy(resty.NoRedirectPolicy())
83 | // u.Client.SetRedirectPolicy(resty.NoRedirectPolicy())
84 |
85 | resp, err := u.Client.R().
86 | Get(LoginUrl)
87 | if err != nil {
88 | return
89 | }
90 |
91 | body := resp.String()
92 |
93 | // 获取所有可需参数
94 | actionUrl, err := util.GetSubstringBetweenStringsByRE(body, `id="form" action="`, `"`)
95 | if err != nil {
96 | return
97 | }
98 |
99 | // lt, err := util.GetSubstringBetweenStringsByRE(body, `name="lt" value="`, `"`)
100 | // if err != nil {
101 | // return
102 | // }
103 |
104 | execution, err := util.GetSubstringBetweenStringsByRE(body, `name="execution" value="`, `"`)
105 | if err != nil {
106 | return
107 | }
108 |
109 | eventId, err := util.GetSubstringBetweenStringsByRE(body, `name="_eventId" value="`, `"`)
110 | if err != nil {
111 | return
112 | }
113 |
114 | salt, err := util.GetSubstringBetweenStringsByRE(body, `id="salt" value="`, `"`)
115 | if err != nil {
116 | return
117 | }
118 |
119 | // 这个地址需要html解码
120 | // decodeurl := html.UnescapeString(actionUrl)
121 |
122 | // var data = strings.NewReader("username=" + u.Username + "&password=" + u.Password + captchaParam + "<=" + lt + "&execution=" + execution + "&_eventId=" + eventId + "&rmShown=" + rmShown)
123 |
124 | dealPassword, err := loginCrypto(u.Password, salt, "1234567890abcdef")
125 | if err != nil {
126 | return
127 | }
128 |
129 | req := u.Client.R().
130 | SetHeader("authority", actionUrl).
131 | SetFormData(map[string]string{
132 | "username": u.Username,
133 | "password": dealPassword,
134 | // "lt": lt,
135 | "execution": execution,
136 | "_eventId": eventId,
137 | // "salt": salt,
138 | "rememberMe": "true", // 一周内免登录 on/off
139 | "_rememberMe": "on",
140 | })
141 |
142 | // // 预定.....
143 | // if len(captcha) > 0 {
144 | // req.SetFormData(map[string]string{
145 | // "captchaResponse": captcha,
146 | // })
147 | // }
148 |
149 | resp, err = req.Post(LoginUrl)
150 | if err != nil {
151 | return
152 | }
153 |
154 | body = resp.String()
155 |
156 | // 判断是否有错误
157 | if strings.Contains(resp.String(), "credential.errors") {
158 | loginErrStr, _ := util.GetSubstringBetweenStringsByRE(body, `credential.errors">`, ``)
159 | err = errors.New(loginErrStr)
160 | return
161 | }
162 |
163 | return
164 | }
165 |
166 | // Login 普通登陆
167 | func (u *ZhydUser) Login() (err error) {
168 | // 操蛋玩意,多登陆几次
169 | for i := 0; i <= 2; i++ {
170 | err = u.login("")
171 | if err == nil {
172 | return
173 | }
174 | }
175 | return
176 | }
177 |
178 | // // LoginWithCap 验证码登陆
179 | // func (u *ZhydUser) LoginWithCap(captcha string) error {
180 | // return u.login(captcha)
181 | // }
182 |
--------------------------------------------------------------------------------
/client/zhyd/model.go:
--------------------------------------------------------------------------------
1 | package zhyd
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | const (
8 | TimeLayout = "2006/1/2 15:04:05"
9 | TwoTimeLayout = `"2006\/1\/2 15:04:05"`
10 | )
11 |
12 | var Location = time.FixedZone("GMT", 8*3600)
13 |
14 | // DormElectricity 宿舍用电余额结构
15 | type DormElectricity struct {
16 | Name string `json:"name"`
17 | BuildName string `json:"build_name"`
18 | Room string `json:"room"`
19 | Electricity string `json:"electricity"`
20 | Balance string `json:"balance"`
21 | ElectricitySubsidy string `json:"electricity_subsidy"`
22 | BalanceSubsidy string `json:"balance_subsidy"`
23 | }
24 |
25 | // ElectricityDetails 宿舍用电详情结构
26 | type ElectricityDetails struct {
27 | Name string `json:"name"`
28 | BuildName string `json:"build_name"`
29 | Room string `json:"room"`
30 | Electricity string `json:"electricity"`
31 | Details []Detail `json:"details"`
32 | }
33 |
34 | // Detail 日详情结构
35 | type Detail struct {
36 | Time string `json:"time"`
37 | Value string `json:"value"`
38 | }
39 |
40 | // ChargeRecords 充值记录结构
41 | type ChargeRecords struct {
42 | Xtbz string `json:"XTBZ"`
43 | BuildName string `json:"buildName"`
44 | Device string `json:"device"`
45 | Mdid string `json:"mdid"`
46 | Mx []struct {
47 | Accounttime CustomTime `json:"accounttime"`
48 | Inmoney string `json:"inmoney"`
49 | Paytype string `json:"paytype"`
50 | } `json:"mx"`
51 | Room string `json:"room"`
52 | RoomID string `json:"roomId"`
53 | Electricity string `json:"syl"`
54 | }
55 |
56 | type CustomTime struct {
57 | Time time.Time
58 | }
59 |
60 | func (ct *CustomTime) UnmarshalJSON(b []byte) error {
61 |
62 | body := string(b)
63 |
64 | // Ignore null, like in the main JSON package.
65 | if body == "null" {
66 | return nil
67 | }
68 | // Fractional seconds are handled implicitly by Parse.
69 | var err error
70 | ct.Time, err = time.ParseInLocation(TwoTimeLayout, body, Location)
71 | return err
72 | }
73 |
74 | func (ct CustomTime) MarshalJSON() ([]byte, error) {
75 | return []byte(`"` + ct.Time.Format("2006-01-02 15:04:05") + `"`), nil
76 | }
77 |
--------------------------------------------------------------------------------
/client/zhyd/util.go:
--------------------------------------------------------------------------------
1 | package zhyd
2 |
3 | import (
4 | "encoding/base64"
5 |
6 | "github.com/wumansgy/goEncrypt"
7 | )
8 |
9 | // 登录参数加密
10 | func loginCrypto(plain string, key string, iv string) (data string, err error) {
11 |
12 | cryptText, err := goEncrypt.AesCbcEncrypt([]byte(plain), []byte(key), []byte(iv))
13 | if err != nil {
14 | return
15 | }
16 |
17 | data = base64.StdEncoding.EncodeToString(cryptText)
18 |
19 | return
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/client/zhyd/zhyd.go:
--------------------------------------------------------------------------------
1 | package zhyd
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/go-resty/resty/v2"
7 | )
8 |
9 | var (
10 | // AuthHostUrl 登陆主页面
11 | AuthHostUrl = "http://ids.lit.edu.cn"
12 | // NeedCaptchaUrl 判断是否需要验证码的接口
13 | NeedCaptchaUrl = AuthHostUrl + "/authserver/needCaptcha.html"
14 | // CaptchaUrl 获取验证码
15 | CaptchaUrl = AuthHostUrl + "/authserver/captcha.html"
16 | // LoginUrl 登陆接口
17 | LoginUrl = AuthHostUrl + "/authserver/login"
18 | // ZhydHost
19 | ZhydHost = "http://zhyd.sec.lit.edu.cn"
20 | // ZhydHostUrl 智慧用电主页
21 | ZhydHostUrl = ZhydHost + "/zhyd"
22 | // GetDormElectricityURl 获取宿舍电量主页
23 | GetDormElectricityURl = ZhydHostUrl + "/sydl/index"
24 | // GetElectricityDetailsUrl 获取用电明细
25 | GetElectricityDetailsUrl = ZhydHostUrl + "/ydmx/index"
26 | // GetConsumptionRecordsUrl 获取消费记录
27 | GetChargeRecordsUrl = ZhydHostUrl + "/zzgd/index"
28 | // UA
29 | UA = "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36"
30 | // MainHeaders 主请求头
31 | MainHeaders = map[string]string{
32 | "User-Agent": UA,
33 | "Accept": "*/*",
34 | "Accept-Encoding": "gzip, deflate",
35 | "Connection": "keep-alive",
36 | }
37 | )
38 |
39 | // ZhydUser 智能控电用户结构体
40 | type ZhydUser struct {
41 | Username string
42 | Password string
43 | Client *resty.Client
44 | }
45 |
46 | // SetPassword 设置用户名
47 | func (u *ZhydUser) SetUsername(username string) *ZhydUser {
48 | u.Username = username
49 | return u
50 | }
51 |
52 | // SetPassword 设置密码
53 | func (u *ZhydUser) SetPassword(password string) *ZhydUser {
54 | u.Password = password
55 | return u
56 | }
57 |
58 | // NewZhydUser 新建智能控电用户
59 | func NewZhydUser() *ZhydUser {
60 |
61 | var u ZhydUser
62 |
63 | u.Client = resty.New()
64 | u.Client.SetHeaders(MainHeaders)
65 | u.Client.SetTimeout(20 * time.Second)
66 |
67 | // 拿个cookies
68 | u.PerSetCooikes()
69 |
70 | return &u
71 | }
72 |
73 | // PerSetCooikes 预先设置必要Cookies
74 | func (u *ZhydUser) PerSetCooikes() *ZhydUser {
75 |
76 | // 先访问一下页面,获取cookie
77 | u.Client.R().
78 | Get(ZhydHost)
79 |
80 | return u
81 | }
82 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/icepie/oh-my-lit
2 |
3 | go 1.16
4 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icepie/oh-my-lit/23f3ce92bf90dec99582b341164276cd4dafb5f6/go.sum
--------------------------------------------------------------------------------