Showing preview only (2,302K chars total). Download the full file or copy to clipboard to get everything.
Repository: fenixsoft/monolithic_arch_springboot
Branch: master
Commit: bcd4e69cb201
Files: 102
Total size: 2.1 MB
Directory structure:
gitextract_udx1wp_1/
├── .gitignore
├── .mvn/
│ └── wrapper/
│ ├── MavenWrapperDownloader.java
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── .travis.yml
├── Dockerfile
├── LICENSE
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── github/
│ │ │ └── fenixsoft/
│ │ │ └── bookstore/
│ │ │ ├── BookstoreApplication.java
│ │ │ ├── applicaiton/
│ │ │ │ ├── AccountApplicationService.java
│ │ │ │ ├── ProductApplicationService.java
│ │ │ │ └── payment/
│ │ │ │ ├── PaymentApplicationService.java
│ │ │ │ └── dto/
│ │ │ │ └── Settlement.java
│ │ │ ├── domain/
│ │ │ │ ├── BaseEntity.java
│ │ │ │ ├── account/
│ │ │ │ │ ├── Account.java
│ │ │ │ │ ├── AccountRepository.java
│ │ │ │ │ └── validation/
│ │ │ │ │ ├── AccountValidation.java
│ │ │ │ │ ├── AuthenticatedAccount.java
│ │ │ │ │ ├── ExistsAccount.java
│ │ │ │ │ ├── NotConflictAccount.java
│ │ │ │ │ └── UniqueAccount.java
│ │ │ │ ├── auth/
│ │ │ │ │ ├── AuthenticAccount.java
│ │ │ │ │ ├── AuthenticAccountRepository.java
│ │ │ │ │ ├── Role.java
│ │ │ │ │ ├── provider/
│ │ │ │ │ │ ├── PreAuthenticatedAuthenticationProvider.java
│ │ │ │ │ │ └── UsernamePasswordAuthenticationProvider.java
│ │ │ │ │ └── service/
│ │ │ │ │ ├── AuthenticAccountDetailsService.java
│ │ │ │ │ ├── JWTAccessToken.java
│ │ │ │ │ ├── JWTAccessTokenService.java
│ │ │ │ │ └── OAuthClientDetailsService.java
│ │ │ │ ├── payment/
│ │ │ │ │ ├── Payment.java
│ │ │ │ │ ├── PaymentRepository.java
│ │ │ │ │ ├── PaymentService.java
│ │ │ │ │ ├── Stockpile.java
│ │ │ │ │ ├── StockpileRepository.java
│ │ │ │ │ ├── StockpileService.java
│ │ │ │ │ ├── Wallet.java
│ │ │ │ │ ├── WalletRepository.java
│ │ │ │ │ ├── WalletService.java
│ │ │ │ │ └── validation/
│ │ │ │ │ ├── SettlementValidator.java
│ │ │ │ │ └── SufficientStock.java
│ │ │ │ └── warehouse/
│ │ │ │ ├── Advertisement.java
│ │ │ │ ├── AdvertisementRepository.java
│ │ │ │ ├── Product.java
│ │ │ │ ├── ProductRepository.java
│ │ │ │ ├── ProductService.java
│ │ │ │ └── Specification.java
│ │ │ ├── infrastructure/
│ │ │ │ ├── cache/
│ │ │ │ │ └── CacheConfiguration.java
│ │ │ │ ├── configuration/
│ │ │ │ │ ├── AuthenticationServerConfiguration.java
│ │ │ │ │ ├── AuthorizationServerConfiguration.java
│ │ │ │ │ ├── JerseyConfiguration.java
│ │ │ │ │ ├── ResourceServerConfiguration.java
│ │ │ │ │ └── WebSecurityConfiguration.java
│ │ │ │ ├── jaxrs/
│ │ │ │ │ ├── AccessDeniedExceptionMapper.java
│ │ │ │ │ ├── BaseExceptionMapper.java
│ │ │ │ │ ├── CodedMessage.java
│ │ │ │ │ ├── CommonResponse.java
│ │ │ │ │ └── ViolationExceptionMapper.java
│ │ │ │ └── utility/
│ │ │ │ └── Encryption.java
│ │ │ └── resource/
│ │ │ ├── AccountResource.java
│ │ │ ├── AdvertisementResource.java
│ │ │ ├── PaymentResource.java
│ │ │ ├── ProductResource.java
│ │ │ └── SettlementResource.java
│ │ └── resources/
│ │ ├── application-mysql.yml
│ │ ├── application-test.yml
│ │ ├── application.yml
│ │ ├── banner.txt
│ │ ├── db/
│ │ │ ├── hsqldb/
│ │ │ │ ├── data.sql
│ │ │ │ └── schema.sql
│ │ │ └── mysql/
│ │ │ ├── data.sql
│ │ │ ├── schema.sql
│ │ │ └── user.sql
│ │ └── static/
│ │ ├── index.html
│ │ └── static/
│ │ ├── board/
│ │ │ ├── gitalk.css
│ │ │ ├── gitalk.html
│ │ │ └── gitalk.min.js
│ │ ├── css/
│ │ │ └── app.13440f960e43a3574b009b7352447f18.css
│ │ └── js/
│ │ ├── 0.c178f427b3d08777c70f.js
│ │ ├── 1.a33faf036923758c7965.js
│ │ ├── 2.626ed94f3752555e21f0.js
│ │ ├── 3.bc7f0b2154007257c317.js
│ │ ├── 4.b4e48a42cf742af20851.js
│ │ ├── 5.d375cbd6c7e1463cdbed.js
│ │ ├── 6.68562501db5734ef1531.js
│ │ ├── 7.184a5e39cc0c624f6a6d.js
│ │ ├── 8.176f9455c3442c06ebf6.js
│ │ ├── 9.527be297aba1594ffe0d.js
│ │ ├── app.ea66dc0be78c3ed2ae63.js
│ │ ├── manifest.0437a7f02d3154ee1abb.js
│ │ └── vendor.c2f13a2146485051ae24.js
│ └── test/
│ └── java/
│ └── com/
│ └── github/
│ └── fenixsoft/
│ └── bookstore/
│ ├── DBRollbackBase.java
│ └── resource/
│ ├── AccountResourceTest.java
│ ├── AdvertisementResourceTest.java
│ ├── AuthResourceTest.java
│ ├── JAXRSResourceBase.java
│ ├── PaymentResourceTest.java
│ └── ProductResourceTest.java
└── travis_docker_push.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**
!**/src/test/**
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
### VS Code ###
.vscode/
================================================
FILE: .mvn/wrapper/MavenWrapperDownloader.java
================================================
/*
* Copyright 2007-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if (mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if (mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if (!outputFile.getParentFile().exists()) {
if (!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}
================================================
FILE: .mvn/wrapper/maven-wrapper.properties
================================================
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
================================================
FILE: .travis.yml
================================================
language: java
jdk:
- openjdk12
before_install:
- export TZ='Asia/Shanghai'
install:
- mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
script:
- mvn clean test package jacoco:report coveralls:report -DrepoToken=$repoToken
deploy:
- provider: script
keep_history: true
skip_cleanup: true
script: bash travis_docker_push.sh
on:
branch: master
================================================
FILE: Dockerfile
================================================
FROM openjdk:12-alpine
MAINTAINER icyfenix
ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \
JAVA_OPTS="" \
PROFILES="default"
ADD /target/*.jar /bookstore.jar
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /bookstore.jar --spring.profiles.active=$PROFILES"]
EXPOSE 8080
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# Fenix's BookStore后端:以单体架构实现
<p align="center">
<a href="https://icyfenix.cn" target="_blank">
<img width="180" src="https://raw.githubusercontent.com/fenixsoft/awesome-fenix/master/.vuepress/public/images/logo-color.png" alt="logo">
</a>
</p>
<p align="center">
<a href="https://icyfenix.cn" style="display:inline-block"><img src="https://raw.githubusercontent.com/fenixsoft/awesome-fenix/master/.vuepress/public/images/Release-v1.svg"></a>
<a href="https://travis-ci.com/fenixsoft/monolithic_arch_springboot" target="_blank" style="display:inline-block"><img src="https://travis-ci.com/fenixsoft/monolithic_arch_springboot.svg?branch=master" alt="Travis-CI"></a>
<a href='https://coveralls.io/github/fenixsoft/monolithic_arch_springboot?branch=master'><img src='https://coveralls.io/repos/github/fenixsoft/monolithic_arch_springboot/badge.svg?branch=master' target="_blank" style="display:inline-block" alt='Coverage Status' /></a>
<a href="https://www.apache.org/licenses/LICENSE-2.0" target="_blank" style="display:inline-block"><img src="https://raw.githubusercontent.com/fenixsoft/awesome-fenix/master/.vuepress/public/images/License-Apache.svg" alt="License"></a>
<a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" style="display:inline-block"><img src="https://raw.githubusercontent.com/fenixsoft/awesome-fenix/master/.vuepress/public/images/DocLicense-CC-red.svg" alt="Document License"></a>
<a href="https://icyfenix.cn/introduction/about-me.html" target="_blank" style="display:inline-block"><img src="https://raw.githubusercontent.com/fenixsoft/awesome-fenix/master/.vuepress/public/images/Author-IcyFenix-blue.svg" alt="About Author"></a>
</p>
如果你此时并不曾了解过什么是“The Fenix Project”,建议先阅读<a href="https://icyfenix.cn/introduction/about-the-fenix-project.html">这部分内容</a>。
单体架构是Fenix's Bookstore'第一个版本的服务端实现,它与此后基于微服务(Spring Cloud、Kubernetes)、服务网格(Istio)、无服务(Serverless)架构风格实现的其他版本,在业务功能上的表现是完全一致的。如果你不是针对性地带着解决某个具体问题、了解某项具体工具、技术的目的而来,而是时间充裕,希望了解软件架构的全貌与发展的话,笔者推荐以此工程入手来了解现代软件架构,因为单体架构的结构是相对直观的,易于理解的架构,对后面接触的其他架构风格也起良好的铺垫作用。此外,笔者在对应的文档中详细分析了作为一个架构设计者,会考虑哪些的通用问题,希望把抽象的“架构”一词具象化出来。
## 运行程序
以下几种途径,可以运行程序,浏览最终的效果:
- 通过Docker容器方式运行:
> ```bash
> $ docker run -d -p 8080:8080 --name bookstore icyfenix/bookstore:monolithic
> ```
>
> 然后在浏览器访问:[http://localhost:8080](http://localhost:8080),系统预置了一个用户(user:icyfenix,pw:123456),也可以注册新用户来测试。
>
> 默认会使用HSQLDB的内存模式作为数据库,并在系统启动时自动初始化好了Schema,完全开箱即用。但这同时也意味着当程序运行结束时,所有的数据都将不会被保留。
>
> 如果希望使用HSQLDB的文件模式,或者其他非嵌入式的独立的数据库支持的话,也是很简单的。以常用的MySQL/MariaDB为例,程序中也已内置了MySQL的表结构初始化脚本,你可以使用环境变量“PROFILES”来激活SpringBoot中针对MySQL所提供的配置,命令如下所示:
>
> ```bash
> $ docker run -d -p 8080:8080 --name bookstore icyfenix/bookstore:monolithic -e PROFILES=mysql
> ```
>
> 此时你需要通过Docker link、Docker Compose或者直接在主机的Host文件中提供一个名为“mysql_lan”的DNS映射,使程序能顺利链接到数据库,关于数据库的更多配置,可参考源码中的[application-mysql.yml](https://github.com/fenixsoft/monolithic_arch_springboot/blob/70f435911b0e0753d7e4cee27cd96304dbef786d/src/main/resources/application-mysql.yml)。
- 通过Git上的源码,以Maven运行:
>```bash
># 克隆获取源码
>$ git clone https://github.com/fenixsoft/monolithic_arch_springboot.git
>
># 进入工程根目录
>$ cd monolithic_arch_springboot
>
># 编译打包
># 采用Maven Wrapper,此方式只需要机器安装有JDK 8或以上版本即可,无需包括Maven在内的其他任何依赖
># 如在Windows下应使用mvnw.cmd package代替以下命令
>$ ./mvnw package
>
># 运行程序,地址为localhost:8080
>$ java -jar target/bookstore-1.0.0-Monolithic-SNAPSHOT.jar
>```
>
>然后在浏览器访问:[http://localhost:8080](http://localhost:8080),系统预置了一个用户(user:icyfenix,pw:123456),也可以注册新用户来测试。
- 通过Git上的源码,在IDE环境中运行:
> - 以IntelliJ IDEA为例,Git克隆本项目后,在File -> Open菜单选择本项目所在的目录,或者pom.xml文件,以Maven方式导入工程。
>
> - IDEA将自动识别出这是一个SpringBoot工程,并定位启动入口为BookstoreApplication,待IDEA内置的Maven自动下载完所有的依赖包后,运行该类即可启动。
>
> - 如你使用其他的IDE,没有对SpringBoot的直接支持,亦可自行定位到BookstoreApplication,这是一个带有main()方法的Java类,运行即可。
>
> - 可通过IDEA的Maven面板中Lifecycle里面的package来对项目进行打包、发布。
>
> - 在IDE环境中修改配置(如数据库等)会更加简单,具体可以参考工程中application.yml和application-mysql.yml中的内容。
## 技术组件
Fenix's BookStore单体架构后端尽可能采用标准的技术组件进行构建,不依赖与具体的实现,包括:
- [JSR 370:Java API for RESTful Web Services 2.1](https://jcp.org/en/jsr/detail?id=370)(JAX-RS 2.1)<br/>RESTFul服务方面,采用的实现为Jersey 2,亦可替换为Apache CXF、RESTeasy、WebSphere、WebLogic等
- [JSR 330:Dependency Injection for Java 1.0](https://jcp.org/en/jsr/detail?id=330)<br/>依赖注入方面,采用的的实现为SpringBoot 2中内置的Spring Framework 5。虽然在多数场合中尽可能地使用了JSR 330的标准注解,但仍有少量地方由于Spring在对@Named、@Inject等注解的支持表现上与本身提供的注解差异,使用了Spring的私有注解。如替换成其他的CDI实现,如HK2,需要较大的改动
- [JSR 338:Java Persistence 2.2](https://jcp.org/en/jsr/detail?id=338)<br/>持久化方面,采用的实现为Spring Data JPA。可替换为Batoo JPA、EclipseLink、OpenJPA等实现,只需将使用CrudRepository所省略的代码手动补全回来即可,无需其他改动。
- [JSR 380:Bean Validation 2.0](https://jcp.org/en/jsr/detail?id=380)<br/>
数据验证方面,采用的实现为Hibernate Validator 6,可替换为Apache BVal等其他验证框架
- [JSR 315:Java Servlet 3.0](https://jcp.org/en/jsr/detail?id=315)<br/>
Web访问方面,采用的实现为SpringBoot 2中默认的Tomcat 9 Embed,可替换为Jetty、Undertow等其他Web服务器
有以下组件仍然依赖了非标准化的技术实现,包括:
- [JSR 375:Java EE Security API specification 1.0](https://jcp.org/en/jsr/detail?id=375)<br/>
认证/授权方面,在2017年才发布的JSR 375中仍然没有直接包含OAuth2和JWT的直接支持,因后续实现微服务架构时对比的需要,单体架构中选择了Spring Security 5作为认证服务,Spring Security OAuth 2.3作为授权服务,Spring Security JWT作为JWT令牌支持,并未采用标准的JSR 375实现,如Soteria。
- [JSR 353/367:Java API for JSON Processing/Binding](https://jcp.org/en/jsr/detail?id=353)<br/>在JSON序列化/反序列化方面,由于Spring Security OAuth的限制(使用JSON-B作为反序列化器时的结果与Jackson等有差异),采用了Spring Security OAuth默认的Jackson,并未采用标准的JSR 353/367实现,如Apache Johnzon、Eclipse Yasson等。
## 工程结构
Fenix's BookStore单体架构后端参考(并未完全遵循)了DDD的分层模式和设计原则,整体分为以下四层:
1. Resource:对应DDD中的User Interface层,负责向用户显示信息或者解释用户发出的命令。请注意,这里指的“用户”不一定是使用用户界面的人,可以是位于另一个进程或计算机的服务。由于本工程采用了MVVM前后端分离模式,这里所指的用户实际上是前端的服务消费者,所以这里以RESTFul中的核心概念”资源“(Resource)来命名。
2. Application:对应DDD中的Application层,负责定义软件本身对外暴露的能力,即软件本身可以完成哪些任务,并负责对内协调领域对象来解决问题。根据DDD的原则,应用层要尽量简单,不包含任何业务规则或者知识,而只为下一层中的领域对象协调任务,分配工作,使它们互相协作,这一点在代码上表现为Application层中一般不会存在任何的条件判断语句。在许多项目中,Application层都会被选为包裹事务(代码进入此层事务开始,退出此层事务提交或者回滚)的载体。
3. Domain:对应DDD中的Domain层,负责实现业务逻辑,即表达业务概念,处理业务状态信息以及业务规则这些行为,此层是整个项目的重点。
4. Infrastructure:对应DDD中的Infrastructure层,向其他层提供通用的技术能力,譬如持久化能力、远程服务通讯、工具集,等等。
<GitHubWrapper>
<p align="center">
<img src="https://raw.githubusercontent.com/fenixsoft/awesome-fenix/master/.vuepress/public/images/ddd-arch.png" >
</p>
</GitHubWrapper>
## 协议
- 本文档代码部分采用[Apache 2.0协议](https://www.apache.org/licenses/LICENSE-2.0)进行许可。遵循许可的前提下,你可以自由地对代码进行修改,再发布,可以将代码用作商业用途。但要求你:
- **署名**:在原有代码和衍生代码中,保留原作者署名及代码来源信息。
- **保留许可证**:在原有代码和衍生代码中,保留Apache 2.0协议文件。
- 本作品文档部分采用[知识共享署名 4.0 国际许可协议](http://creativecommons.org/licenses/by/4.0/)进行许可。 遵循许可的前提下,你可以自由地共享,包括在任何媒介上以任何形式复制、发行本作品,亦可以自由地演绎、修改、转换或以本作品为基础进行二次创作。但要求你:
- **署名**:应在使用本文档的全部或部分内容时候,注明原作者及来源信息。
- **非商业性使用**:不得用于商业出版或其他任何带有商业性质的行为。如需商业使用,请联系作者。
- **相同方式共享的条件**:在本文档基础上演绎、修改的作品,应当继续以知识共享署名 4.0国际许可协议进行许可。
================================================
FILE: mvnw
================================================
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
================================================
FILE: mvnw.cmd
================================================
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%
================================================
FILE: pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.github.fenixsoft</groupId>
<artifactId>bookstore-monolithic-springboot</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>bookstore-monolithic-springboot</name>
<description>Monolithic Architecture Demonstrate with Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<argLine>-Dfile.encoding=UTF-8</argLine>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Databases - Uses HSQL by default -->
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
<plugin>
<groupId>org.eluder.coveralls</groupId>
<artifactId>coveralls-maven-plugin</artifactId>
<version>4.3.0</version>
<configuration>
</configuration>
<dependencies>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2.3</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.5</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/BookstoreApplication.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@SpringBootApplication
@EnableCaching
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
public class BookstoreApplication {
public static void main(String[] args) {
SpringApplication.run(BookstoreApplication.class, args);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/applicaiton/AccountApplicationService.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.applicaiton;
import com.github.fenixsoft.bookstore.domain.account.Account;
import com.github.fenixsoft.bookstore.domain.account.AccountRepository;
import com.github.fenixsoft.bookstore.infrastructure.utility.Encryption;
import javax.inject.Inject;
import javax.inject.Named;
import javax.transaction.Transactional;
/**
* 用户资源的应用服务接口
*
* @author icyfenix@gmail.com
* @date 2020/3/10 17:46
**/
@Named
@Transactional
public class AccountApplicationService {
@Inject
private AccountRepository repository;
@Inject
private Encryption encoder;
public void createAccount(Account account) {
account.setPassword(encoder.encode(account.getPassword()));
repository.save(account);
}
public Account findAccountByUsername(String username) {
return repository.findByUsername(username);
}
public void updateAccount(Account account) {
repository.save(account);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/applicaiton/ProductApplicationService.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.applicaiton;
import com.github.fenixsoft.bookstore.domain.payment.Stockpile;
import com.github.fenixsoft.bookstore.domain.payment.StockpileService;
import com.github.fenixsoft.bookstore.domain.warehouse.Product;
import com.github.fenixsoft.bookstore.domain.warehouse.ProductService;
import javax.inject.Inject;
import javax.inject.Named;
import javax.transaction.Transactional;
/**
* 产品的应用服务接口
*
* @author icyfenix@gmail.com
* @date 2020/3/15 20:05
**/
@Named
@Transactional
public class ProductApplicationService {
@Inject
private ProductService service;
@Inject
private StockpileService stockpileService;
/**
* 获取仓库中所有的货物信息
*/
public Iterable<Product> getAllProducts() {
return service.getAllProducts();
}
/**
* 获取仓库中指定的货物信息
*/
public Product getProduct(Integer id) {
return service.getProduct(id);
}
/**
* 创建或更新产品信息
*/
public Product saveProduct(Product product) {
return service.saveProduct(product);
}
/**
* 删除指定产品
*/
public void removeProduct(Integer id) {
service.removeProduct(id);
}
/**
* 根据产品查询库存
*/
public Stockpile getStockpile(Integer productId) {
return stockpileService.getByProductId(productId);
}
/**
* 将指定的产品库存调整为指定数额
*/
public void setStockpileAmountByProductId(Integer productId, Integer amount) {
stockpileService.set(productId, amount);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/applicaiton/payment/PaymentApplicationService.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.applicaiton.payment;
import com.github.fenixsoft.bookstore.applicaiton.payment.dto.Settlement;
import com.github.fenixsoft.bookstore.domain.payment.*;
import com.github.fenixsoft.bookstore.domain.warehouse.ProductService;
import org.springframework.cache.Cache;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.transaction.Transactional;
/**
* 支付应用务
*
* @author icyfenix@gmail.com
* @date 2020/3/12 16:29
**/
@Named
@Transactional
public class PaymentApplicationService {
@Inject
private PaymentService paymentService;
@Inject
private ProductService productService;
@Inject
private WalletService walletService;
@Resource(name = "settlement")
private Cache settlementCache;
/**
* 根据结算清单的内容执行,生成对应的支付单
*/
public Payment executeBySettlement(Settlement bill) {
// 从服务中获取商品的价格,计算要支付的总价(安全原因,这个不能由客户端传上来)
productService.replenishProductInformation(bill);
// 冻结部分库存(保证有货提供),生成付款单
Payment payment = paymentService.producePayment(bill);
// 设立解冻定时器(超时未支付则释放冻结的库存和资金)
paymentService.setupAutoThawedTrigger(payment);
return payment;
}
/**
* 完成支付
* 立即取消解冻定时器,执行扣减库存和资金
*/
public void accomplishPayment(Integer accountId, String payId) {
// 订单从冻结状态变为派送状态,扣减库存
double price = paymentService.accomplish(payId);
// 扣减货款
walletService.decrease(accountId, price);
// 支付成功的清除缓存
settlementCache.evict(payId);
}
/**
* 取消支付
* 立即触发解冻定时器,释放库存和资金
*/
public void cancelPayment(String payId) {
// 释放冻结的库存
paymentService.cancel(payId);
// 支付成功的清除缓存
settlementCache.evict(payId);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/applicaiton/payment/dto/Settlement.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.applicaiton.payment.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.fenixsoft.bookstore.domain.warehouse.Product;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Collection;
import java.util.Map;
/**
* 支付结算单模型
*
* @author icyfenix@gmail.com
* @date 2020/3/12 11:35
**/
public class Settlement {
@Size(min = 1, message = "结算单中缺少商品清单")
private Collection<Item> items;
@NotNull(message = "结算单中缺少配送信息")
private Purchase purchase;
/**
* 购物清单中的商品信息
* 基于安全原因(避免篡改价格),改信息不会取客户端的,需在服务端根据商品ID再查询出来
*/
public transient Map<Integer, Product> productMap;
public Collection<Item> getItems() {
return items;
}
public void setItems(Collection<Item> items) {
this.items = items;
}
public Purchase getPurchase() {
return purchase;
}
public void setPurchase(Purchase purchase) {
this.purchase = purchase;
}
/**
* 结算单中要购买的商品
*/
public static class Item {
@NotNull(message = "结算单中必须有明确的商品数量")
@Min(value = 1, message = "结算单中商品数量至少为一件")
private Integer amount;
@JsonProperty("id")
@NotNull(message = "结算单中必须有明确的商品信息")
private Integer productId;
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public Integer getProductId() {
return productId;
}
public void setProductId(Integer productId) {
this.productId = productId;
}
}
/**
* 结算单中的配送信息
*/
public static class Purchase {
private Boolean delivery = true;
@NotEmpty(message = "配送信息中缺少支付方式")
private String pay;
@NotEmpty(message = "配送信息中缺少收件人姓名")
private String name;
@NotEmpty(message = "配送信息中缺少收件人电话")
private String telephone;
@NotEmpty(message = "配送信息中缺少收件地址")
private String location;
public Boolean getDelivery() {
return delivery;
}
public void setDelivery(Boolean delivery) {
this.delivery = delivery;
}
public String getPay() {
return pay;
}
public void setPay(String pay) {
this.pay = pay;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/BaseEntity.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
/**
* JavaBean领域对象的共同基类,定义了ID属性和其访问字段
*
* @author icyfenix@gmail.com
* @date 2020/3/6 10:52
**/
@MappedSuperclass
public class BaseEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/account/Account.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.account;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.fenixsoft.bookstore.domain.BaseEntity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
/**
* 用户实体
*
* @author icyfenix@gmail.com
* @date 2020/3/6 23:08
*/
@Entity
public class Account extends BaseEntity {
@NotEmpty(message = "用户不允许为空")
private String username;
// 密码字段不参与序列化(但反序列化是参与的)、不参与更新(但插入是参与的)
// 这意味着密码字段不会在获取对象(很多操作都会关联用户对象)的时候泄漏出去;
// 也意味着此时“修改密码”一类的功能无法以用户对象资源的接口来处理(因为更新对象时密码不会被更新),需要单独提供接口去完成
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Column(updatable = false)
private String password;
@NotEmpty(message = "用户姓名不允许为空")
private String name;
private String avatar;
@Pattern(regexp = "1\\d{10}", message = "手机号格式不正确")
private String telephone;
@Email(message = "邮箱格式不正确")
private String email;
private String location;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/account/AccountRepository.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.account;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.repository.CrudRepository;
import java.util.Collection;
import java.util.Optional;
/**
* 用户对象数据仓库
*
* @author icyfenix@gmail.com
* @date 2020/3/6 23:10
**/
@CacheConfig(cacheNames = "repository.account")
public interface AccountRepository extends CrudRepository<Account, Integer> {
@Override
Iterable<Account> findAll();
@Cacheable(key = "#username")
Account findByUsername(String username);
/**
* 判断唯一性,用户名、邮箱、电话不允许任何一个重复
*/
boolean existsByUsernameOrEmailOrTelephone(String username, String email, String telephone);
/**
* 判断唯一性,用户名、邮箱、电话不允许任何一个重复
*/
Collection<Account> findByUsernameOrEmailOrTelephone(String username, String email, String telephone);
/**
* 判断存在性,用户名存在即为存在
*/
@Cacheable(key = "#username")
boolean existsByUsername(String username);
// 覆盖以下父类中需要处理缓存失效的方法
// 父类取不到CacheConfig的配置信息,所以不能抽象成一个通用的父类接口中完成
@Caching(evict = {
@CacheEvict(key = "#entity.id"),
@CacheEvict(key = "#entity.username")
})
<S extends Account> S save(S entity);
@CacheEvict
<S extends Account> Iterable<S> saveAll(Iterable<S> entities);
@Cacheable(key = "#id")
Optional<Account> findById(Integer id);
@Cacheable(key = "#id")
boolean existsById(Integer id);
@CacheEvict(key = "#id")
void deleteById(Integer id);
@CacheEvict(key = "#entity.id")
void delete(Account entity);
@CacheEvict(allEntries = true)
void deleteAll(Iterable<? extends Account> entities);
@CacheEvict(allEntries = true)
void deleteAll();
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/account/validation/AccountValidation.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.account.validation;
import com.github.fenixsoft.bookstore.domain.account.Account;
import com.github.fenixsoft.bookstore.domain.account.AccountRepository;
import com.github.fenixsoft.bookstore.domain.auth.AuthenticAccount;
import org.springframework.security.core.context.SecurityContextHolder;
import javax.inject.Inject;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.function.Predicate;
/**
* 用户对象校验器
* <p>
* 如,新增用户时,判断该用户对象是否允许唯一,在修改用户时,判断该用户是否存在
*
* @author icyfenix@gmail.com
* @date 2020/3/11 14:22
**/
public class AccountValidation<T extends Annotation> implements ConstraintValidator<T, Account> {
@Inject
protected AccountRepository repository;
protected Predicate<Account> predicate = c -> true;
@Override
public boolean isValid(Account value, ConstraintValidatorContext context) {
// 在JPA持久化时,默认采用Hibernate实现,插入、更新时都会调用BeanValidationEventListener进行验证
// 而验证行为应该尽可能在外层进行,Resource中已经通过@Vaild注解触发过一次验证,这里会导致重复执行
// 正常途径是使用分组验证避免,但@Vaild不支持分组,@Validated支持,却又是Spring的私有标签
// 另一个途径是设置Hibernate配置文件中的javax.persistence.validation.mode参数为“none”,这个参数在Spring的yml中未提供桥接
// 为了避免涉及到数据库操作的验证重复进行,在这里做增加此空值判断,利用Hibernate验证时验证器不是被Spring创建的特点绕开
return repository == null || predicate.test(value);
}
public static class ExistsAccountValidator extends AccountValidation<ExistsAccount> {
public void initialize(ExistsAccount constraintAnnotation) {
predicate = c -> repository.existsById(c.getId());
}
}
public static class AuthenticatedAccountValidator extends AccountValidation<AuthenticatedAccount> {
public void initialize(AuthenticatedAccount constraintAnnotation) {
predicate = c -> {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if ("anonymousUser".equals(principal)) {
return false;
} else {
AuthenticAccount loginUser = (AuthenticAccount) principal;
return c.getId().equals(loginUser.getId());
}
};
}
}
public static class UniqueAccountValidator extends AccountValidation<UniqueAccount> {
public void initialize(UniqueAccount constraintAnnotation) {
predicate = c -> !repository.existsByUsernameOrEmailOrTelephone(c.getUsername(), c.getEmail(), c.getTelephone());
}
}
public static class NotConflictAccountValidator extends AccountValidation<NotConflictAccount> {
public void initialize(NotConflictAccount constraintAnnotation) {
predicate = c -> {
Collection<Account> collection = repository.findByUsernameOrEmailOrTelephone(c.getUsername(), c.getEmail(), c.getTelephone());
// 将用户名、邮件、电话改成与现有完全不重复的,或者只与自己重复的,就不算冲突
return collection.isEmpty() || (collection.size() == 1 && collection.iterator().next().getId().equals(c.getId()));
};
}
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/account/validation/AuthenticatedAccount.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.account.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 代表用户必须与当前登陆的用户一致
* 相当于使用Spring Security的@PreAuthorize("#{user.name == authentication.name}")的验证
*
* @author icyfenix@gmail.com
* @date 2020/3/16 11:06
**/
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = AccountValidation.AuthenticatedAccountValidator.class)
public @interface AuthenticatedAccount {
String message() default "不是当前登陆用户";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/account/validation/ExistsAccount.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.account.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 代表一个用户在数据仓库中是存在的
*
* @author icyfenix@gmail.com
* @date 2020/3/11 19:51
**/
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = AccountValidation.ExistsAccountValidator.class)
public @interface ExistsAccount {
String message() default "用户不存在";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/account/validation/NotConflictAccount.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.account.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 表示一个用户的信息是无冲突的
* <p>
* “无冲突”是指该用户的敏感信息与其他用户不重合,譬如将一个注册用户的邮箱,修改成与另外一个已存在的注册用户一致的值,这便是冲突
*
* @author icyfenix@gmail.com
* @date 2020/3/11 20:19
**/
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = AccountValidation.NotConflictAccountValidator.class)
public @interface NotConflictAccount {
String message() default "用户名称、邮箱、手机号码与现存用户产生重复";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/account/validation/UniqueAccount.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.account.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 表示一个用户是唯一的
* <p>
* 唯一不仅仅是用户名,还要求手机、邮箱均不允许重复
*
* @author icyfenix@gmail.com
* @date 2020/3/11 19:49
**/
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = AccountValidation.UniqueAccountValidator.class)
public @interface UniqueAccount {
String message() default "用户名称、邮箱、手机号码均不允许与现存用户重复";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/AuthenticAccount.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.auth;
import com.github.fenixsoft.bookstore.domain.account.Account;
import org.springframework.beans.BeanUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.HashSet;
/**
* 认证用户模型
* <p>
* 用户注册之后,包含其业务属性,如姓名、电话、地址,用于业务发生,存储于Account对象中
* 也包含其用于认证的属性,譬如密码、角色、是否停用,存储于AuthenticAccount对象中
*
* @author icyfenix@gmail.com
* @date 2020/3/7 20:46
**/
public class AuthenticAccount extends Account implements UserDetails {
public AuthenticAccount() {
super();
authorities.add(new SimpleGrantedAuthority(Role.USER));
}
public AuthenticAccount(Account origin) {
this();
BeanUtils.copyProperties(origin, this);
if (getId() == 1) {
// 由于没有做用户管理功能,默认给系统中第一个用户赋予管理员角色
authorities.add(new SimpleGrantedAuthority(Role.ADMIN));
}
}
/**
* 该用户拥有的授权,譬如读取权限、修改权限、增加权限等等
*/
private Collection<GrantedAuthority> authorities = new HashSet<>();
@Override
public Collection<GrantedAuthority> getAuthorities() {
return authorities;
}
public void setAuthorities(Collection<GrantedAuthority> authorities) {
this.authorities = authorities;
}
/**
* 账号是否过期
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 是否锁定
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 密码是否过期
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否被锁定
*/
@Override
public boolean isEnabled() {
return true;
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/AuthenticAccountRepository.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.auth;
import com.github.fenixsoft.bookstore.domain.account.AccountRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.common.exceptions.UnauthorizedUserException;
import org.springframework.stereotype.Component;
import java.util.Optional;
/**
* 认证用户的数据仓库
*
* @author icyfenix@gmail.com
* @date 2020/3/8 15:21
**/
@Component
public class AuthenticAccountRepository {
@Autowired
private AccountRepository databaseUserRepo;
public AuthenticAccount findByUsername(String username) {
return new AuthenticAccount(Optional.ofNullable(databaseUserRepo.findByUsername(username)).orElseThrow(() -> new UsernameNotFoundException("用户" + username + "不存在")));
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/Role.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.auth;
/**
* 角色常量类,目前系统中只有2种角色:用户,管理员
*
* @author icyfenix@gmail.com
* @date 2020/3/16 11:32
**/
public interface Role {
String USER = "ROLE_USER";
String ADMIN = "ROLE_ADMIN";
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/provider/PreAuthenticatedAuthenticationProvider.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.auth.provider;
import com.github.fenixsoft.bookstore.domain.auth.AuthenticAccount;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
import javax.inject.Named;
/**
* 预验证身份认证器
* <p>
* 预验证是指身份已经在其他地方(第三方)确认过
* 预验证器的目的是将第三方身份管理系统集成到具有Spring安全性的Spring应用程序中,在本项目中,用于JWT令牌过期后重刷新时的验证
* 此时只要检查用户是否有停用、锁定、密码过期、账号过期等问题,如果没有,可根据JWT令牌的刷新过期期限,重新给客户端发放访问令牌
*
* @author icyfenix@gmail.com
* @date 2020/3/10 11:25
* @see <a href="https://docs.spring.io/spring-security/site/docs/3.0.x/reference/preauth.html">Pre-Authentication Scenarios</a>
**/
@Named
public class PreAuthenticatedAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication.getPrincipal() instanceof UsernamePasswordAuthenticationToken) {
AuthenticAccount user = (AuthenticAccount) ((UsernamePasswordAuthenticationToken) authentication.getPrincipal()).getPrincipal();
// 检查用户没有停用、锁定、密码过期、账号过期等问题
// 在本项目中这些功能都未启用,实际上此检查肯定是会通过的,但为了严谨和日后扩展,还是依次进行了检查
if (user.isEnabled() && user.isCredentialsNonExpired() && user.isAccountNonExpired() && user.isCredentialsNonExpired()) {
return new PreAuthenticatedAuthenticationToken(user, "", user.getAuthorities());
} else {
throw new DisabledException("用户状态不正确");
}
} else {
throw new PreAuthenticatedCredentialsNotFoundException("预验证失败,传上来的令牌是怎么来的?");
}
}
/**
* 判断该验证器能处理哪些认证
*/
@Override
public boolean supports(Class<?> clazz) {
return clazz.equals(PreAuthenticatedAuthenticationToken.class);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/provider/UsernamePasswordAuthenticationProvider.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.auth.provider;
import com.github.fenixsoft.bookstore.domain.auth.service.AuthenticAccountDetailsService;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.inject.Inject;
import javax.inject.Named;
/**
* 基于用户名、密码的身份认证器
* 该身份认证器会被{@link AuthenticationManager}验证管理器调用
* 验证管理器支持多种验证方式,这里基于用户名、密码的的身份认证是方式之一
*
* @author icyfenix@gmail.com
* @date 2020/3/7 21:45
*/
@Named
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {
@Inject
private AuthenticAccountDetailsService authenticAccountDetailsService;
@Inject
private PasswordEncoder passwordEncoder;
/**
* 认证处理
* <p>
* 根据用户名查询用户资料,对比资料中加密后的密码
* 结果将返回一个Authentication的实现类(此处为UsernamePasswordAuthenticationToken)则代表认证成功
* 返回null或者抛出AuthenticationException的子类异常则代表认证失败
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName().toLowerCase();
String password = (String) authentication.getCredentials();
// AuthenticationException的子类定义了多种认证失败的类型,这里仅处“理用户不存在”、“密码不正确”两种
// 用户不存在的话会直接由loadUserByUsername()抛出异常
UserDetails user = authenticAccountDetailsService.loadUserByUsername(username);
if (!passwordEncoder.matches(password, user.getPassword())) throw new BadCredentialsException("密码不正确");
// 认证通过,返回令牌
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
/**
* 判断该验证器能处理哪些认证
*/
@Override
public boolean supports(Class<?> clazz) {
return clazz.equals(UsernamePasswordAuthenticationToken.class);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/service/AuthenticAccountDetailsService.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.auth.service;
import com.github.fenixsoft.bookstore.domain.auth.AuthenticAccountRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.Optional;
/**
* 认证用户信息查询服务
* <p>
* {@link UserDetailsService}接口定义了从外部(数据库、LDAP,任何地方)根据用户名查询到
*/
@Named
public class AuthenticAccountDetailsService implements UserDetailsService {
@Inject
private AuthenticAccountRepository accountRepository;
/**
* 根据用户名查询用户角色、权限等信息
* 如果用户名无法查询到对应的用户,或者权限不满足,请直接抛出{@link UsernameNotFoundException},勿返回null
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return accountRepository.findByUsername(username);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/service/JWTAccessToken.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.auth.service;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.HashMap;
import java.util.Map;
/**
* JWT访问令牌
* <p>
* JWT令牌的结构为三部分组成:[令牌头(Header)].[负载信息(Payload)].[签名(Signature)]
* 令牌头:定义了令牌的元数据,如令牌采用的签名算法,默认为HMAC SHA256算法
* 负载信息:由签发者自定义的数据,一般会包括过期时间(Expire)、授权范围(Authority)、令牌ID编号(JTI)等
* 签名:签名是使用私钥和头部指定的算法,前两部分进行的数字签名,防止数据被篡改。
* 以上,令牌头和负载均为JSON结构,进行Base64URLEncode之后进行签名,然后用“.”连接,构成令牌报文
* <p>
* Spring Security OAuth2的{@link JwtAccessTokenConverter}提供了令牌的基础结构(令牌头、部分负载,如过期时间、JTI)的转换实现
* 继承此类,在加入自己定义的负载信息即可使用。一般来说负载中至少要告知服务端当前用户是谁,但又不应存放过多信息导致HTTP Header过大,尤其不应存放敏感信息。
*
* @author icyfenix@gmail.com
* @date 2020/3/9 9:46
*/
@Named
public class JWTAccessToken extends JwtAccessTokenConverter {
// 签名私钥
// 此处内容是我随便写的UUID,按照JWT约定默认是256Bit的,其实任何格式都可以,只是要注意保密,不要公开出去
private static final String JWT_TOKEN_SIGNING_PRIVATE_KEY = "601304E0-8AD4-40B0-BD51-0B432DC47461";
@Inject
JWTAccessToken(UserDetailsService userDetailsService) {
// 设置签名私钥
setSigningKey(JWT_TOKEN_SIGNING_PRIVATE_KEY);
// 设置从资源请求中带上来的JWT令牌转换回安全上下文中的用户信息的查询服务
// 如果不设置该服务,则从JWT令牌获得的Principal就只有一个用户名(令牌中确实就只存了用户名)
// 将用户用户信息查询服务提供给默认的令牌转换器,使得转换令牌时自动根据用户名还原出完整的用户对象
// 这方便了后面编码(可以在直接获得登陆用户信息),但也稳定地为每次请求增加了一次(从数据库/缓存)查询,自行取舍
DefaultUserAuthenticationConverter converter = new DefaultUserAuthenticationConverter();
converter.setUserDetailsService(userDetailsService);
((DefaultAccessTokenConverter) getAccessTokenConverter()).setUserTokenConverter(converter);
}
/**
* 增强令牌
* 增强主要就是在令牌的负载中加入额外的信息
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Authentication user = authentication.getUserAuthentication();
String[] authorities = user.getAuthorities().stream().map(GrantedAuthority::getAuthority).toArray(String[]::new);
Map<String, Object> payLoad = new HashMap<>();
// Spring Security OAuth的JWT令牌默认实现中就加入了一个“user_name”的项存储了当前用户名
// 这里主要是出于演示Payload的用途,以及方便客户端获取(否则客户端要从令牌中解码Base64来获取),设置了一个“username”,两者的内容是一致的
payLoad.put("username", user.getName());
payLoad.put("authorities", authorities);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(payLoad);
return super.enhance(accessToken, authentication);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/service/JWTAccessTokenService.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.auth.service;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import javax.inject.Inject;
import javax.inject.Named;
/**
* JWT访问令牌服务
* <p>
* 在此服务中提供了令牌如何存储、携带哪些信息、如何签名、持续多长时间等相关内容的定义
* 令牌服务应当会被授权服务器{@link com.github.fenixsoft.bookstore.infrastructure.configuration.AuthorizationServerConfiguration}注册验证Endpoint时候调用到
*
* @author icyfenix@gmail.com
* @date 2020/3/8 11:07
**/
@Named
public class JWTAccessTokenService extends DefaultTokenServices {
/**
* 构建JWT令牌,并进行默认的配置
*/
@Inject
public JWTAccessTokenService(JWTAccessToken token, OAuthClientDetailsService clientService, AuthenticationManager authenticationManager) {
// 设置令牌的持久化容器
// 令牌持久化有多种方式,单节点服务可以存放在Session中,集群可以存放在Redis中
// 而JWT是后端无状态、前端存储的解决方案,Token的存储由前端完成
setTokenStore(new JwtTokenStore(token));
// 令牌支持的客户端详情
setClientDetailsService(clientService);
// 设置验证管理器,在鉴权的时候需要用到
setAuthenticationManager(authenticationManager);
// 定义令牌的额外负载
setTokenEnhancer(token);
// access_token有效期,单位:秒,默认12小时
setAccessTokenValiditySeconds(60 * 60 * 3);
// refresh_token的有效期,单位:秒, 默认30天
// 这决定了客户端选择“记住当前登录用户”的最长时效,即失效前都不用再请求用户赋权了
setRefreshTokenValiditySeconds(60 * 60 * 24 * 15);
// 是否支持refresh_token,默认false
setSupportRefreshToken(true);
// 是否复用refresh_token,默认为true
// 如果为false,则每次请求刷新都会删除旧的refresh_token,创建新的refresh_token
setReuseRefreshToken(true);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/service/OAuthClientDetailsService.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.auth.service;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
/**
* OAuth2客户端类型定义
* <p>
* OAuth2支持四种授权模式,这里仅定义了密码模式(Resource Owner Password Credentials Grant)一种
* OAuth2作为开放的(面向不同服务提供商)授权协议,要求用户提供明文用户名、密码的这种“密码模式”并不常用
* 而这里可以采用是因为前端(BookStore FrontEnd)与后端服务是属于同一个服务提供者的,实质上不存在密码会不会被第三方保存的敏感问题
* 如果永远只考虑单体架构、单一服务提供者,则并无引入OAuth的必要,Spring Security的表单认证就能很良好、便捷地解决认证和授权的问题
* 这里使用密码模式来解决,是为了下一阶段演示微服务化后,服务之间鉴权作准备,以便后续扩展以及对比。
*
* @author icyfenix@gmail.com
* @date 2020/3/7 19:45
**/
@Named
public class OAuthClientDetailsService implements ClientDetailsService {
/**
* 客户端ID
* 这里的客户端就是指本项目的前端代码
*/
private static final String CLIENT_ID = "bookstore_frontend";
/**
* 客户端密钥
* 在OAuth2协议中,ID是可以公开的,密钥应当保密,密钥用以证明当前申请授权的客户端是未被冒充的
*/
private static final String CLIENT_SECRET = "bookstore_secret";
@Inject
private PasswordEncoder passwordEncoder;
private ClientDetailsService clientDetailsService;
/**
* 构造密码授权模式
* <p>
* 由于实质上只有一个客户端,所以就不考虑存储和客户端的增删改查了,直接在内存中配置出客户端的信息
* <p>
* 授权Endpoint示例:
* /oauth/token?grant_type=password & username=#USER# & password=#PWD# & client_id=bookstore_frontend & client_secret=bookstore_secret
* 刷新令牌Endpoint示例:
* /oauth/token?grant_type=refresh_token & refresh_token=#REFRESH_TOKEN# & client_id=bookstore_frontend & client_secret=bookstore_secret
*/
@PostConstruct
public void init() throws Exception {
InMemoryClientDetailsServiceBuilder builder = new InMemoryClientDetailsServiceBuilder();
// 提供客户端ID和密钥,并指定该客户端支持密码授权、刷新令牌两种访问类型
builder.withClient(CLIENT_ID)
.secret(passwordEncoder.encode(CLIENT_SECRET))
.scopes("BROWSER")
.authorizedGrantTypes("password", "refresh_token");
clientDetailsService = builder.build();
}
/**
* 外部根据客户端ID查询验证方式
*/
@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
return clientDetailsService.loadClientByClientId(clientId);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/Payment.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.payment;
import com.github.fenixsoft.bookstore.domain.BaseEntity;
import com.github.fenixsoft.bookstore.domain.account.Account;
import org.springframework.security.core.context.SecurityContextHolder;
import javax.persistence.Entity;
import java.util.Date;
import java.util.UUID;
/**
* 支付单模型
* <p>
* 就是传到客户端让用户给扫码或者其他别的方式付钱的对象
*
* @author icyfenix@gmail.com
* @date 2020/3/12 17:07
**/
@Entity
public class Payment extends BaseEntity {
/**
* 支付状态
*/
public enum State {
/**
* 等待支付中
*/
WAITING,
/**
* 已取消
*/
CANCEL,
/**
* 已支付
*/
PAYED,
/**
* 已超时回滚(未支付,并且商品已恢复)
*/
TIMEOUT
}
public Payment() {
}
public Payment(Double totalPrice, Long expires) {
setTotalPrice(totalPrice);
setExpires(expires);
setCreateTime(new Date());
setPayState(State.WAITING);
// 下面这两个是随便写的,实际应该根据情况调用支付服务,返回待支付的ID
setPayId(UUID.randomUUID().toString());
// 产生支付单的时候一定是有用户的
Account account = (Account) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
setPaymentLink("/pay/modify/" + getPayId() + "?state=PAYED&accountId=" + account.getId());
}
private Date createTime;
private String payId;
private Double totalPrice;
private Long expires;
private String paymentLink;
private State payState;
public String getPayId() {
return payId;
}
public void setPayId(String payId) {
this.payId = payId;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Long getExpires() {
return expires;
}
public void setExpires(Long expires) {
this.expires = expires;
}
public String getPaymentLink() {
return paymentLink;
}
public void setPaymentLink(String paymentLink) {
this.paymentLink = paymentLink;
}
public Double getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(Double totalPrice) {
this.totalPrice = totalPrice;
}
public State getPayState() {
return payState;
}
public void setPayState(State payState) {
this.payState = payState;
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/PaymentRepository.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.payment;
import org.springframework.data.repository.CrudRepository;
import java.util.Optional;
/**
* 支付单数据仓库
*
* @author icyfenix@gmail.com
* @date 2020/3/12 23:25
**/
public interface PaymentRepository extends CrudRepository<Payment, Integer> {
Payment getByPayId(String payId);
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/PaymentService.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.payment;
import com.github.fenixsoft.bookstore.applicaiton.payment.dto.Settlement;
import com.github.fenixsoft.bookstore.infrastructure.cache.CacheConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.persistence.EntityNotFoundException;
import java.util.Objects;
import java.util.Timer;
import java.util.TimerTask;
/**
* 支付单相关的领域服务
*
* @author icyfenix@gmail.com
* @date 2020/3/12 23:24
**/
@Named
public class PaymentService {
/**
* 默认支付单超时时间:2分钟(缓存TTL时间的一半)
*/
private static final long DEFAULT_PRODUCT_FROZEN_EXPIRES = CacheConfiguration.SYSTEM_DEFAULT_EXPIRES / 2;
private static final Logger log = LoggerFactory.getLogger(PaymentService.class);
private final Timer timer = new Timer();
@Inject
private StockpileService stockpileService;
@Inject
private PaymentRepository paymentRepository;
@Resource(name = "settlement")
private Cache settlementCache;
/**
* 生成支付单
* <p>
* 根据结算单冻结指定的货物,计算总价,生成支付单
*/
public Payment producePayment(Settlement bill) {
Double total = bill.getItems().stream().mapToDouble(i -> {
stockpileService.frozen(i.getProductId(), i.getAmount());
return bill.productMap.get(i.getProductId()).getPrice() * i.getAmount();
}).sum() + 12; // 12元固定运费,客户端写死的,这里陪着演一下,避免总价对不上
Payment payment = new Payment(total, DEFAULT_PRODUCT_FROZEN_EXPIRES);
paymentRepository.save(payment);
// 将支付单存入缓存
settlementCache.put(payment.getPayId(), bill);
log.info("创建支付订单,总额:{}", payment.getTotalPrice());
return payment;
}
/**
* 完成支付单
* <p>
* 意味着客户已经完成付款,这个方法在正式业务中应当作为三方支付平台的回调,而演示项目就直接由客户端发起调用了
*/
public double accomplish(String payId) {
synchronized (payId.intern()) {
Payment payment = paymentRepository.getByPayId(payId);
if (payment.getPayState() == Payment.State.WAITING) {
payment.setPayState(Payment.State.PAYED);
paymentRepository.save(payment);
accomplishSettlement(Payment.State.PAYED, payment.getPayId());
log.info("编号为{}的支付单已处理完成,等待支付", payId);
return payment.getTotalPrice();
} else {
throw new UnsupportedOperationException("当前订单不允许支付,当前状态为:" + payment.getPayState());
}
}
}
/**
* 取消支付单
* <p>
* 客户取消支付单,此时应当立即释放处于冻结状态的库存
* 由于支付单的存储中应该保存而未持久化的购物明细(在Settlement中),所以这步就不做处理了,等2分钟后在触发器中释放
*/
public void cancel(String payId) {
synchronized (payId.intern()) {
Payment payment = paymentRepository.getByPayId(payId);
if (payment.getPayState() == Payment.State.WAITING) {
payment.setPayState(Payment.State.CANCEL);
paymentRepository.save(payment);
accomplishSettlement(Payment.State.CANCEL, payment.getPayId());
log.info("编号为{}的支付单已被取消", payId);
} else {
throw new UnsupportedOperationException("当前订单不允许取消,当前状态为:" + payment.getPayState());
}
}
}
/**
* 设置支付单自动冲销解冻的触发器
* <p>
* 如果在触发器超时之后,如果支付单未仍未被支付(状态是WAITING)
* 则自动执行冲销,将冻结的库存商品解冻,以便其他人可以购买,并将Payment的状态修改为ROLLBACK。
* <p>
* 注意:
* 使用TimerTask意味着节点带有状态,这在分布式应用中是必须明确【反对】的,如以下缺陷:
* 1. 如果要考虑支付订单的取消场景,无论支付状态如何,这个TimerTask到时间之后都应当被执行。不应尝试使用TimerTask::cancel来取消任务。
* 因为只有带有上下文状态的节点才能完成取消操作,如果要在集群中这样做,就必须使用支持集群的定时任务(如Quartz)以保证多节点下能够正常取消任务。
* 2. 如果节点被重启、同样会面临到状态的丢失,导致一部分处于冻结的触发器永远无法被执行,所以需要系统启动时根据数据库状态有一个恢复TimeTask的的操作
* 3. 即时只考虑正常支付的情况,真正生产环境中这种代码需要一个支持集群的同步锁(如用Redis实现互斥量),避免解冻支付和该支付单被完成两个事件同时在不同的节点中发生
*/
public void setupAutoThawedTrigger(Payment payment) {
timer.schedule(new TimerTask() {
public void run() {
synchronized (payment.getPayId().intern()) {
// 使用2分钟之前的Payment到数据库中查出当前的Payment
Payment currentPayment = paymentRepository.findById(payment.getId()).orElseThrow(() -> new EntityNotFoundException(payment.getId().toString()));
if (currentPayment.getPayState() == Payment.State.WAITING) {
log.info("支付单{}当前状态为:WAITING,转变为:TIMEOUT", payment.getId());
accomplishSettlement(Payment.State.TIMEOUT, payment.getPayId());
}
}
}
}, payment.getExpires());
}
/**
* 根据支付状态,实际调整库存(扣减库存或者解冻)
*/
private void accomplishSettlement(Payment.State endState, String payId) {
Settlement settlement = (Settlement) Objects.requireNonNull(Objects.requireNonNull(settlementCache.get(payId)).get());
settlement.getItems().forEach(i -> {
if (endState == Payment.State.PAYED) {
stockpileService.decrease(i.getProductId(), i.getAmount());
} else {
// 其他状态,无论是TIMEOUT还是CANCEL,都进行解冻
stockpileService.thawed(i.getProductId(), i.getAmount());
}
});
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/Stockpile.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.payment;
import com.github.fenixsoft.bookstore.domain.BaseEntity;
import com.github.fenixsoft.bookstore.domain.warehouse.Product;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
/**
* 商品库存
*
* @author icyfenix@gmail.com
* @date 2020/3/12 16:34
**/
@Entity
public class Stockpile extends BaseEntity {
private Integer amount;
private Integer frozen;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id")
private transient Product product;
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public void frozen(Integer number) {
this.amount -= number;
this.frozen += number;
}
public void thawed(Integer number) {
frozen(-1 * number);
}
public void decrease(Integer number) {
this.frozen -= number;
}
public void increase(Integer number) {
this.amount += number;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/StockpileRepository.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.payment;
import org.springframework.data.repository.CrudRepository;
/**
* 库存数据仓库
*
* @author icyfenix@gmail.com
* @date 2020/3/12 16:36
**/
public interface StockpileRepository extends CrudRepository<Stockpile, Integer> {
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/StockpileService.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.payment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.persistence.EntityNotFoundException;
/**
* 商品库存的领域服务
*
* @author icyfenix@gmail.com
* @date 2020/3/12 20:23
**/
@Named
public class StockpileService {
private static final Logger log = LoggerFactory.getLogger(StockpileService.class);
@Inject
private StockpileRepository repository;
/**
* 根据产品查询库存
*/
public Stockpile getByProductId(Integer productId) {
return repository.findById(productId).orElseThrow(() -> new EntityNotFoundException(productId.toString()));
}
/**
* 货物售出
* 从冻结状态的货物中扣减
*/
public void decrease(Integer productId, Integer amount) {
Stockpile stock = repository.findById(productId).orElseThrow(() -> new EntityNotFoundException(productId.toString()));
stock.decrease(amount);
repository.save(stock);
log.info("库存出库,商品:{},数量:{}", productId, amount);
}
/**
* 货物增加
* 增加指定数量货物至正常货物状态
*/
public void increase(Integer productId, Integer amount) {
Stockpile stock = repository.findById(productId).orElseThrow(() -> new EntityNotFoundException(productId.toString()));
stock.increase(amount);
repository.save(stock);
log.info("库存入库,商品:{},数量:{}", productId, amount);
}
/**
* 货物冻结
* 从正常货物中移动指定数量至冻结状态
*/
public void frozen(Integer productId, Integer amount) {
Stockpile stock = repository.findById(productId).orElseThrow(() -> new EntityNotFoundException(productId.toString()));
stock.frozen(amount);
repository.save(stock);
log.info("冻结库存,商品:{},数量:{}", productId, amount);
}
/**
* 货物解冻
* 从冻结货物中移动指定数量至正常状态
*/
public void thawed(Integer productId, Integer amount) {
Stockpile stock = repository.findById(productId).orElseThrow(() -> new EntityNotFoundException(productId.toString()));
stock.thawed(amount);
repository.save(stock);
log.info("解冻库存,商品:{},数量:{}", productId, amount);
}
/**
* 设置货物数量
*/
public void set(Integer productId, Integer amount) {
Stockpile stock = repository.findById(productId).orElseThrow(() -> new EntityNotFoundException(productId.toString()));
stock.setAmount(amount);
repository.save(stock);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/Wallet.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.payment;
import com.github.fenixsoft.bookstore.domain.BaseEntity;
import com.github.fenixsoft.bookstore.domain.account.Account;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
/**
* 用户钱包
*
* @author icyfenix@gmail.com
* @date 2020/3/12 16:30
**/
@Entity
public class Wallet extends BaseEntity {
// 这里是偷懒,正式项目中请使用BigDecimal来表示金额
private Double money;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "account_id")
private Account account;
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/WalletRepository.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.payment;
import org.springframework.data.repository.CrudRepository;
import java.util.Optional;
/**
* 钱包数据仓库
*
* @author icyfenix@gmail.com
* @date 2020/3/12 16:35
**/
public interface WalletRepository extends CrudRepository<Wallet, Integer> {
Optional<Wallet> findByAccountId(Integer accountId);
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/WalletService.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.payment;
import com.github.fenixsoft.bookstore.domain.account.Account;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
/**
* 用户钱包的领域服务
* <p>
* 由于本工程中冻结、解冻款项的方法是为了在微服务中演示TCC事务所准备的,单体服务中由于与本地事务一同提交,无需用到
*
* @author icyfenix@gmail.com
* @date 2020/3/12 20:23
**/
@Named
public class WalletService {
private static final Logger log = LoggerFactory.getLogger(WalletService.class);
@Inject
private WalletRepository repository;
/**
* 账户资金减少
*/
public void decrease(Integer accountId, Double amount) {
Wallet wallet = repository.findByAccountId(accountId).orElseGet(() -> {
Wallet newWallet = new Wallet();
Account account = new Account();
account.setId(accountId);
newWallet.setMoney(0D);
newWallet.setAccount(account);
repository.save(newWallet);
return newWallet;
});
if (wallet.getMoney() > amount) {
wallet.setMoney(wallet.getMoney() - amount);
repository.save(wallet);
log.info("支付成功。用户余额:{},本次消费:{}", wallet.getMoney(), amount);
} else {
throw new RuntimeException("用户余额不足以支付,请先充值");
}
}
/**
* 账户资金增加(演示程序,没有做充值入口,实际这个方法无用)
*/
public void increase(Integer accountId, Double amount) {
}
// 以下两个方法是为TCC事务准备的,在单体架构中不需要实现
/**
* 账户资金冻结
* 从正常资金中移动指定数量至冻结状态
*/
public void frozen(Integer accountId, Double amount) {
}
/**
* 账户资金解冻
* 从冻结资金中移动指定数量至正常状态
*/
public void thawed(Integer accountId, Double amount) {
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/validation/SettlementValidator.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.payment.validation;
import com.github.fenixsoft.bookstore.applicaiton.payment.dto.Settlement;
import com.github.fenixsoft.bookstore.domain.payment.StockpileService;
import javax.inject.Inject;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* 结算单验证器
* <p>
* 结算单能够成功执行的约束是清单中每一项商品的库存量都足够。
* <p>
* 这个验证器的目的不在于保证商品高并发情况(如秒杀活动)下不超卖,而在于避免库存不足时仍可下单。高并发下的超卖是一种“不可重复读”现象
* (即读取过的数据在事务期间被另一个事务改变),如要严谨地避免,需要把数据库的隔离级别从默认的“Read Committed”提升至“Repeatable Read”
* 除了MySQL(InnoDB)外,主流的数据库,如Oracle、SQLServer默认都是Read committed,提升隔离级别会显著影响数据库的并发能力。
*
* @author icyfenix@gmail.com
* @date 2020/3/16 9:02
**/
public class SettlementValidator implements ConstraintValidator<SufficientStock, Settlement> {
@Inject
private StockpileService service;
@Override
public boolean isValid(Settlement value, ConstraintValidatorContext context) {
return value.getItems().stream().noneMatch(i -> service.getByProductId(i.getProductId()).getAmount() < i.getAmount());
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/validation/SufficientStock.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.payment.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 判断结算单中货物存量是充足的
*
* @author icyfenix@gmail.com
* @date 2020/3/16 8:59
**/
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = SettlementValidator.class)
public @interface SufficientStock {
String message() default "商品库存不足";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/Advertisement.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.warehouse;
import com.github.fenixsoft.bookstore.domain.BaseEntity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 广告对象模型
*
* @author icyfenix@gmail.com
* @date 2020/3/7 10:49
**/
@Entity
public class Advertisement extends BaseEntity {
@NotEmpty(message = "广告图片不允许为空")
private String image;
@NotNull(message = "广告应当有关联的商品")
@Column(name = "product_id")
private Integer productId;
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public Integer getProductId() {
return productId;
}
public void setProductId(Integer productId) {
this.productId = productId;
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/AdvertisementRepository.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.warehouse;
import org.springframework.dao.DataAccessException;
import org.springframework.data.repository.CrudRepository;
/**
* 广告对象数据仓库
*
* @author icyfenix@gmail.com
* @date 2020/3/7 10:51
**/
public interface AdvertisementRepository extends CrudRepository<Advertisement, Integer> {
Iterable<Advertisement> findAll() throws DataAccessException;
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/Product.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.warehouse;
import com.github.fenixsoft.bookstore.domain.BaseEntity;
import javax.persistence.*;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Set;
/**
* 商品对象模型
*
* @author icyfenix@gmail.com
* @date 2020/3/6 10:43
*/
@Entity
public class Product extends BaseEntity {
@NotEmpty(message = "商品名称不允许为空")
private String title;
@NotNull(message = "商品应当有明确的价格")
@Min(value = 0, message = "商品价格最低为零")
// 这里是偷懒,正式场合使用BigDecimal来表示金额
private Double price;
@Min(value = 0, message = "评分最低为0")
@Max(value = 10, message = "评分最高为10")
private Float rate;
private String description;
private String cover;
private String detail;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name = "product_id")
private Set<Specification> specifications;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Float getRate() {
return rate;
}
public void setRate(Float rate) {
this.rate = rate;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getCover() {
return cover;
}
public void setCover(String cover) {
this.cover = cover;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
public Set<Specification> getSpecifications() {
return specifications;
}
public void setSpecifications(Set<Specification> specifications) {
this.specifications = specifications;
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/ProductRepository.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.warehouse;
import org.springframework.data.repository.CrudRepository;
import java.util.Collection;
/**
* 商品对象数据仓库
*
* @author icyfenix@gmail.com
* @date 2020/3/6 20:56
**/
public interface ProductRepository extends CrudRepository<Product, Integer> {
Collection<Product> findByIdIn(Collection<Integer> ids);
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/ProductService.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.warehouse;
import com.github.fenixsoft.bookstore.applicaiton.payment.dto.Settlement;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 产品领域服务
*
* @author icyfenix@gmail.com
* @date 2020/3/12 20:58
**/
@Named
public class ProductService {
@Inject
private ProductRepository repository;
/**
* 根据结算单中货物的ID,填充货物的完整信息到结算单对象上
*/
public void replenishProductInformation(Settlement bill) {
List<Integer> ids = bill.getItems().stream().map(Settlement.Item::getProductId).collect(Collectors.toList());
bill.productMap = repository.findByIdIn(ids).stream().collect(Collectors.toMap(Product::getId, Function.identity()));
}
/**
* 获取仓库中所有的货物信息
*/
public Iterable<Product> getAllProducts() {
return repository.findAll();
}
/**
* 获取仓库中指定的货物信息
*/
public Product getProduct(Integer id) {
return repository.findById(id).orElse(null);
}
/**
* 创建或者更新产品信息
*/
public Product saveProduct(Product product) {
return repository.save(product);
}
/**
* 删除指定产品
*/
public void removeProduct(Integer id) {
repository.deleteById(id);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/Specification.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.domain.warehouse;
import com.github.fenixsoft.bookstore.domain.BaseEntity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 商品规格
*
* @author icyfenix@gmail.com
* @date 2020/3/6 19:33
**/
@Entity
public class Specification extends BaseEntity {
@NotEmpty(message = "商品规格名称不允许为空")
private String item;
@NotEmpty(message = "商品规格内容不允许为空")
private String value;
@NotNull(message = "商品规格必须归属于指定商品")
@Column(name = "product_id")
private Integer productId;
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Integer getProductId() {
return productId;
}
public void setProductId(Integer productId) {
this.productId = productId;
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/cache/CacheConfiguration.java
================================================
package com.github.fenixsoft.bookstore.infrastructure.cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
/**
* 为系统提供一些代码上使用的缓存
*
* @author icyfenix@gmail.com
* @date 2020/4/7 17:38
**/
@Configuration
public class CacheConfiguration {
/**
* 系统默认缓存TTL时间:4分钟
* 一些需要用到缓存的数据,譬如支付单,需要按此数据来规划过期时间
*/
public static final long SYSTEM_DEFAULT_EXPIRES = 4 * 60 * 1000;
@Bean
public CacheManager configCacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(SYSTEM_DEFAULT_EXPIRES, TimeUnit.MILLISECONDS));
return manager;
}
@Bean(name = "settlement")
public Cache getSettlementTTLCache() {
return new CaffeineCache("settlement", Caffeine.newBuilder().expireAfterAccess(SYSTEM_DEFAULT_EXPIRES, TimeUnit.MILLISECONDS).build());
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/AuthenticationServerConfiguration.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.infrastructure.configuration;
import com.github.fenixsoft.bookstore.domain.auth.provider.PreAuthenticatedAuthenticationProvider;
import com.github.fenixsoft.bookstore.domain.auth.provider.UsernamePasswordAuthenticationProvider;
import com.github.fenixsoft.bookstore.domain.auth.service.AuthenticAccountDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Spring Security的用户认证服务器配置
* <p>
* 借用Spring Security作为认证服务器,告知服务器通过怎样的途径去查询用户、加密密码和验证用户真伪
* 我们实际上并不使用Spring Security提供的认证表单,而是选择了前端通过OAuth2的密码模式,在授权过程中同时完成认证
* 由于服务端整套安全机制(方法授权判断、OAuth2密码模式的用户认证、密码的加密算法)仍然是构建在Spring Security基础之上
* 所以我们的认证服务、用户信息服务仍然继承着Spring Security提供的基类,并在这里注册到Spring Security当中
*
* @author icyfenix@gmail.com
* @date 2020/3/7 19:41
**/
@Configuration
@EnableWebSecurity
public class AuthenticationServerConfiguration extends WebSecurityConfiguration {
@Autowired
private AuthenticAccountDetailsService authenticAccountDetailsService;
@Autowired
private UsernamePasswordAuthenticationProvider userProvider;
@Autowired
private PreAuthenticatedAuthenticationProvider preProvider;
@Autowired
private PasswordEncoder encoder;
/**
* 需要把AuthenticationManager主动暴漏出来
* 以便在授权服务器{@link AuthorizationServerConfiguration}中可以使用它来完成用户名、密码的认证
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 配置Spring Security的安全认证服务
* Spring Security的Web安全设置,将在资源服务器配置{@link ResourceServerConfiguration}中完成
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(authenticAccountDetailsService).passwordEncoder(encoder);
auth.authenticationProvider(userProvider);
auth.authenticationProvider(preProvider);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/AuthorizationServerConfiguration.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.infrastructure.configuration;
import com.github.fenixsoft.bookstore.domain.auth.service.AuthenticAccountDetailsService;
import com.github.fenixsoft.bookstore.domain.auth.service.JWTAccessTokenService;
import com.github.fenixsoft.bookstore.domain.auth.service.OAuthClientDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
/**
* Spring Security OAuth2 授权服务器配置
* <p>
* 在该配置中,设置了授权服务Endpoint的相关信息(端点的位置、请求方法、使用怎样的令牌、支持怎样的客户端)
* 以及针对OAuth2的密码模式所需要的用户身份认证服务和用户详情查询服务
*
* @author icyfenix@gmail.com
* @date 2020/3/7 17:38
**/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
/**
* 令牌服务
*/
@Autowired
private JWTAccessTokenService tokenService;
/**
* OAuth2客户端信息服务
*/
@Autowired
private OAuthClientDetailsService clientService;
/**
* 认证服务管理器
* <p>
* 一个认证服务管理器里面包含着多个可以从事不同认证类型的认证提供者(Provider)
* 认证服务由认证服务器{@link AuthenticationServerConfiguration}定义并提供注入源
*/
@Autowired
private AuthenticationManager authenticationManager;
/**
* 用户信息服务
*/
@Autowired
private AuthenticAccountDetailsService accountService;
/**
* 配置客户端详情服务
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientService);
}
/**
* 配置授权的服务Endpoint
* <p>
* Spring Security OAuth2会根据配置的认证服务、用户详情服务、令牌服务自动生成以下端点:
* /oauth/authorize:授权端点
* /oauth/token:令牌端点
* /oauth/confirm_access:用户确认授权提交端点
* /oauth/error:授权服务错误信息端点
* /oauth/check_token:用于资源服务访问的令牌解析端点
* /oauth/token_key:提供公有密匙的端点,如果JWT采用的是非对称加密加密算法,则资源服务其在鉴权时就需要这个公钥来解码
* 如有必要,这些端点可以使用pathMapping()方法来修改它们的位置,使用prefix()方法来设置路径前缀
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoint) {
endpoint.authenticationManager(authenticationManager)
.userDetailsService(accountService)
.tokenServices(tokenService)
//控制TokenEndpoint端点请求访问的类型,默认HttpMethod.POST
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
/**
* 配置OAuth2发布出来的Endpoint本身的安全约束
* <p>
* 这些端点的默认访问规则原本是:
* 1. 端点开启了HTTP Basic Authentication,通过allowFormAuthenticationForClients()关闭,即允许通过表单来验证
* 2. 端点的访问均为denyAll(),可以在这里通过SpringEL表达式来改变为permitAll()
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.allowFormAuthenticationForClients().tokenKeyAccess("permitAll()").checkTokenAccess("permitAll()");
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/JerseyConfiguration.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.infrastructure.configuration;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.Path;
import javax.ws.rs.ext.Provider;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* Jersey服务器配置
* <p>
* 使用Jersey来提供对JAX-RS(JSR 370:Java API for Restful Web Services)的支持
* 这里设置了所有服务的前缀路径“restful”和restful服务资源的包路径
*
* @author icyfenix@gmail.com
* @date 2020/3/6 21:10
**/
@Configuration
@ApplicationPath("/restful")
public class JerseyConfiguration extends ResourceConfig {
public JerseyConfiguration() {
scanPackages("com.github.fenixsoft.bookstore.resource");
scanPackages("com.github.fenixsoft.bookstore.infrastructure.jaxrs");
}
/**
* Jersey的packages()方法在Jar形式运行下有问题,这里修理一下
*/
private void scanPackages(String scanPackage) {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(Path.class));
scanner.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
this.registerClasses(scanner.findCandidateComponents(scanPackage).stream()
.map(beanDefinition -> ClassUtils.resolveClassName(Objects.requireNonNull(beanDefinition.getBeanClassName()), this.getClassLoader()))
.collect(Collectors.toSet()));
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/ResourceServerConfiguration.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.infrastructure.configuration;
import com.github.fenixsoft.bookstore.domain.auth.service.JWTAccessTokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import javax.annotation.security.RolesAllowed;
/**
* 资源服务器配置
* <p>
* 配置资源服务访问权限,主流有两种方式:
* 一是在这里通过{@link HttpSecurity}的<code>antMatchers</code>方法集中配置
* 二是启用全局方法级安全支持{@link EnableGlobalMethodSecurity} 在各个资源的访问方法前,通过注解来逐个配置,使用的注解包括有:
* JSR 250标准注解{@link RolesAllowed},可完整替代Spring的{@link Secured}功能
* 以及可以使用EL表达式的Spring注解{@link PreAuthorize}、{@link PostAuthorize}
*
* @author icyfenix@gmail.com
* @date 2020/3/7 19:43
**/
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Autowired
private JWTAccessTokenService tokenService;
/**
* 配置HTTP访问相关的安全选项
*/
public void configure(HttpSecurity http) throws Exception {
// 基于JWT来绑定用户状态,所以服务端可以是无状态的
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 关闭CSRF(Cross Site Request Forgery)跨站请求伪造的防御
// 因为需要状态存储CSRF Token才能开启该功能
http.csrf().disable();
// 关闭HTTP Header中的X-Frame-Options选项,允许页面在frame标签中打开
http.headers().frameOptions().disable();
// 设置服务的安全规则
http.authorizeRequests().antMatchers("/oauth/**").permitAll();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.tokenServices(tokenService);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/WebSecurityConfiguration.java
================================================
package com.github.fenixsoft.bookstore.infrastructure.configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Spring Security安全配置
* <p>
* 移除静态资源目录的安全控制,避免Spring Security默认禁止HTTP缓存的行为
*
* @author icyfenix@gmail.com
* @date 2020/4/8 0:09
**/
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().cacheControl().disable();
}
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/static/**");
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/AccessDeniedExceptionMapper.java
================================================
package com.github.fenixsoft.bookstore.infrastructure.jaxrs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
/**
* 用于统一处理在Resource中由于Spring Security授权访问产生的异常信息
*
* @author icyfenix@gmail.com
* @date 2020/4/7 0:09
**/
@Provider
public class AccessDeniedExceptionMapper implements ExceptionMapper<AccessDeniedException> {
private static final Logger log = LoggerFactory.getLogger(AccessDeniedExceptionMapper.class);
@Context
private HttpServletRequest request;
@Override
public Response toResponse(AccessDeniedException exception) {
log.warn("越权访问被禁止 {}: {}", request.getMethod(), request.getPathInfo());
return CommonResponse.send(Response.Status.FORBIDDEN, exception.getMessage());
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/BaseExceptionMapper.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.infrastructure.jaxrs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
/**
* 用于兜底的全局处理器,如果其他所有的Mapper都不合适,将由此处理把错误带到前端
*
* @author icyfenix@gmail.c
* @date 2020/3/12 16:43
**/
@Provider
public class BaseExceptionMapper implements ExceptionMapper<Throwable> {
private static final Logger log = LoggerFactory.getLogger(BaseExceptionMapper.class);
@Override
public Response toResponse(Throwable exception) {
log.error(exception.getMessage(), exception);
return CommonResponse.failure(exception.getMessage());
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/CodedMessage.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.infrastructure.jaxrs;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* 带编码的实体容器
* <p>
* 一般来说REST服务应采用HTTP Status Code带回错误信息编码
* 但很多前端开发都习惯以JSON-RPC的风格处理异常,所以仍然保留这个编码容器
* 用于返回给客户端以形式为“{code,message,data}”的对象格式
*
* @author icyfenix@gmail.com
* @date 2020/3/6 15:34
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CodedMessage {
/**
* 约定的成功标志
*/
public static final Integer CODE_SUCCESS = 0;
/**
* 默认的失败标志,其他失败含义可以自定义
*/
public static final Integer CODE_DEFAULT_FAILURE = 1;
private Integer code;
private String message;
private Object data;
public CodedMessage(Integer code, String message) {
setCode(code);
setMessage(message);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/CommonResponse.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.infrastructure.jaxrs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.function.Consumer;
/**
* 为了简化编码而设计的HTTP Response对象包装类和工具集
* <p>
* 带有服务状态编码的(带有Code字段的)JavaBean领域对象包装类
* Code字段的通常用于服务消费者判定该请求的业务处理是否成功。
* <p>
* 统一约定:
* - 当服务调用正常完成,返回Code一律以0表示
* - 当服务调用产生异常,可自定义不为0的Code值,此时Message字段作为返回客户端的详细信息
*
* @author icyfenix@gmail.com
* @date 2020/3/6 15:46
**/
public abstract class CommonResponse {
private static final Logger log = LoggerFactory.getLogger(CommonResponse.class);
/**
* 向客户端发送自定义操作信息
*/
public static Response send(Response.Status status, String message) {
Integer code = status.getFamily() == Response.Status.Family.SUCCESSFUL ? CodedMessage.CODE_SUCCESS : CodedMessage.CODE_DEFAULT_FAILURE;
return Response.status(status).type(MediaType.APPLICATION_JSON).entity(new CodedMessage(code, message)).build();
}
/**
* 向客户端发送操作失败的信息
*/
public static Response failure(String message) {
return send(Response.Status.INTERNAL_SERVER_ERROR, message);
}
/**
* 向客户端发送操作成功的信息
*/
public static Response success(String message) {
return send(Response.Status.OK, message);
}
/**
* 向客户端发送操作成功的信息
*/
public static Response success() {
return send(Response.Status.OK, "操作已成功");
}
/**
* 执行操作,并根据操作是否成功返回给客户端相应信息
* 封装了在服务端接口中很常见的执行操作,成功返回成功标志、失败返回失败标志的通用操作,用于简化编码
*/
public static Response op(Runnable executor) {
return op(executor, e -> log.error(e.getMessage(), e));
}
/**
* 执行操作(带自定义的失败处理),并根据操作是否成功返回给客户端相应信息
* 封装了在服务端接口中很常见的执行操作,成功返回成功标志、失败返回失败标志的通用操作,用于简化编码
*/
public static Response op(Runnable executor, Consumer<Exception> exceptionConsumer) {
try {
executor.run();
return CommonResponse.success();
} catch (Exception e) {
exceptionConsumer.accept(e);
return CommonResponse.failure(e.getMessage());
}
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/ViolationExceptionMapper.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.infrastructure.jaxrs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import java.util.stream.Collectors;
/**
* 用于统一处理在Resource中由于验证器验证失败而带回客户端的错误信息
*
* @author icyfenix@gmail.com
* @date 2020/3/10 23:37
**/
@Provider
public class ViolationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
private static final Logger log = LoggerFactory.getLogger(ViolationExceptionMapper.class);
@Override
public Response toResponse(ConstraintViolationException exception) {
log.warn("客户端传入了校验结果为非法的数据", exception);
String msg = exception.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
return CommonResponse.send(Response.Status.BAD_REQUEST, msg);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/utility/Encryption.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.infrastructure.utility;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.inject.Named;
import java.util.Optional;
/**
* 默认的加密工具
*
* @author icyfenix@gmail.com
* @date 2020/3/10 18:02
**/
@Named
public class Encryption {
/**
* 配置认证使用的密码加密算法:BCrypt
* 由于在Spring Security很多验证器中都要用到{@link PasswordEncoder}的加密,所以这里要添加@Bean注解发布出去
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 使用默认加密算法进行编码
*/
public String encode(CharSequence rawPassword) {
return passwordEncoder().encode(Optional.ofNullable(rawPassword).orElse(""));
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/resource/AccountResource.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.resource;
import com.github.fenixsoft.bookstore.applicaiton.AccountApplicationService;
import com.github.fenixsoft.bookstore.domain.account.Account;
import com.github.fenixsoft.bookstore.domain.account.validation.AuthenticatedAccount;
import com.github.fenixsoft.bookstore.domain.account.validation.NotConflictAccount;
import com.github.fenixsoft.bookstore.domain.account.validation.UniqueAccount;
import com.github.fenixsoft.bookstore.infrastructure.jaxrs.CommonResponse;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
import javax.validation.Valid;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* 用户资源
* <p>
* 对客户端以Restful形式暴露资源,提供对用户资源{@link Account}的管理入口
*
* @author icyfenix@gmail.com
* @date 2020/3/6 20:52
**/
@Path("/accounts")
@Component
@CacheConfig(cacheNames = "resource.account")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class AccountResource {
@Inject
private AccountApplicationService service;
/**
* 根据用户名称获取用户详情
*/
@GET
@Path("/{username}")
@Cacheable(key = "#username")
public Account getUser(@PathParam("username") String username) {
return service.findAccountByUsername(username);
}
/**
* 创建新的用户
*/
@POST
@CacheEvict(key = "#user.username")
public Response createUser(@Valid @UniqueAccount Account user) {
return CommonResponse.op(() -> service.createAccount(user));
}
/**
* 更新用户信息
*/
@PUT
@CacheEvict(key = "#user.username")
public Response updateUser(@Valid @AuthenticatedAccount @NotConflictAccount Account user) {
return CommonResponse.op(() -> service.updateAccount(user));
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/resource/AdvertisementResource.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.resource;
import com.github.fenixsoft.bookstore.domain.warehouse.Advertisement;
import com.github.fenixsoft.bookstore.domain.warehouse.AdvertisementRepository;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
/**
* 广告相关的资源
*
* @author icyfenix@gmail.com
* @date 2020/3/7 10:48
**/
@Path("/advertisements")
@Component
@Produces(MediaType.APPLICATION_JSON)
public class AdvertisementResource {
@Inject
AdvertisementRepository repository;
@GET
@Cacheable("resource.advertisements")
public Iterable<Advertisement> getAllAdvertisements() {
return repository.findAll();
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/resource/PaymentResource.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.resource;
import com.github.fenixsoft.bookstore.applicaiton.payment.PaymentApplicationService;
import com.github.fenixsoft.bookstore.domain.account.Account;
import com.github.fenixsoft.bookstore.domain.auth.AuthenticAccount;
import com.github.fenixsoft.bookstore.domain.auth.Role;
import com.github.fenixsoft.bookstore.domain.payment.Payment;
import com.github.fenixsoft.bookstore.domain.payment.Stockpile;
import com.github.fenixsoft.bookstore.infrastructure.jaxrs.CommonResponse;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* 支付单相关的资源
*
* @author icyfenix@gmail.com
* @date 2020/3/13 12:52
**/
@Path("/pay")
@Component
@Produces(MediaType.APPLICATION_JSON)
public class PaymentResource {
@Inject
private PaymentApplicationService service;
/**
* 修改支付单据的状态
*/
@PATCH
@Path("/{payId}")
@RolesAllowed(Role.USER)
public Response updatePaymentState(@PathParam("payId") String payId, @QueryParam("state") Payment.State state) {
Account account = (Account) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return updatePaymentStateAlias(payId, account.getId(), state);
}
/**
* 修改支付单状态的GET方法别名
* 考虑到该动作要由二维码扫描来触发,只能进行GET请求,所以增加一个别名以便通过二维码调用
* 这个方法原本应该作为银行支付接口的回调,不控制调用权限(谁付款都行),但都认为是购买用户付的款
*/
@GET
@Path("/modify/{payId}")
public Response updatePaymentStateAlias(@PathParam("payId") String payId, @QueryParam("accountId") Integer accountId, @QueryParam("state") Payment.State state) {
if (state == Payment.State.PAYED) {
return CommonResponse.op(() -> service.accomplishPayment(accountId, payId));
} else {
return CommonResponse.op(() -> service.cancelPayment(payId));
}
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/resource/ProductResource.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.resource;
import com.github.fenixsoft.bookstore.applicaiton.ProductApplicationService;
import com.github.fenixsoft.bookstore.domain.auth.Role;
import com.github.fenixsoft.bookstore.domain.payment.Stockpile;
import com.github.fenixsoft.bookstore.domain.warehouse.Product;
import com.github.fenixsoft.bookstore.infrastructure.jaxrs.CommonResponse;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Component;
import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.validation.Valid;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* 产品相关的资源
*
* @author icyfenix@gmail.com
* @date 2020/3/6 20:52
**/
@Path("/products")
@Component
@CacheConfig(cacheNames = "resource.product")
@Produces(MediaType.APPLICATION_JSON)
public class ProductResource {
@Inject
ProductApplicationService service;
/**
* 获取仓库中所有的货物信息
*/
@GET
@Cacheable(key = "'ALL_PRODUCT'")
public Iterable<Product> getAllProducts() {
return service.getAllProducts();
}
/**
* 获取仓库中指定的货物信息
*/
@GET
@Path("/{id}")
@Cacheable(key = "#id")
public Product getProduct(@PathParam("id") Integer id) {
return service.getProduct(id);
}
/**
* 更新产品信息
*/
@PUT
@Caching(evict = {
@CacheEvict(key = "#product.id"),
@CacheEvict(key = "'ALL_PRODUCT'")
})
@RolesAllowed(Role.ADMIN)
public Response updateProduct(@Valid Product product) {
return CommonResponse.op(() -> service.saveProduct(product));
}
/**
* 创建新的产品
*/
@POST
@Caching(evict = {
@CacheEvict(key = "#product.id"),
@CacheEvict(key = "'ALL_PRODUCT'")
})
@RolesAllowed(Role.ADMIN)
public Product createProduct(@Valid Product product) {
return service.saveProduct(product);
}
/**
* 删除新的产品
*/
@DELETE
@Path("/{id}")
@Caching(evict = {
@CacheEvict(key = "#id"),
@CacheEvict(key = "'ALL_PRODUCT'")
})
@RolesAllowed(Role.ADMIN)
public Response removeProduct(@PathParam("id") Integer id) {
return CommonResponse.op(() -> service.removeProduct(id));
}
/**
* 将指定的产品库存调整为指定数额
*/
@PATCH
@Path("/stockpile/{productId}")
@RolesAllowed(Role.ADMIN)
public Response updateStockpile(@PathParam("productId") Integer productId, @QueryParam("amount") Integer amount) {
return CommonResponse.op(() -> service.setStockpileAmountByProductId(productId, amount));
}
/**
* 根据产品查询库存
*/
@GET
@Path("/stockpile/{productId}")
@RolesAllowed(Role.ADMIN)
public Stockpile queryStockpile(@PathParam("productId") Integer productId) {
return service.getStockpile(productId);
}
}
================================================
FILE: src/main/java/com/github/fenixsoft/bookstore/resource/SettlementResource.java
================================================
/*
* Copyright 2012-2020. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. More information from:
*
* https://github.com/fenixsoft
*/
package com.github.fenixsoft.bookstore.resource;
import com.github.fenixsoft.bookstore.applicaiton.payment.PaymentApplicationService;
import com.github.fenixsoft.bookstore.applicaiton.payment.dto.Settlement;
import com.github.fenixsoft.bookstore.domain.auth.Role;
import com.github.fenixsoft.bookstore.domain.payment.Payment;
import com.github.fenixsoft.bookstore.domain.payment.validation.SufficientStock;
import org.springframework.stereotype.Component;
import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
/**
* 结算清单相关的资源
*
* @author icyfenix@gmail.com
* @date 2020/3/12 11:23
**/
@Path("/settlements")
@Component
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class SettlementResource {
@Inject
private PaymentApplicationService service;
/**
* 提交一张交易结算单,根据结算单中的物品,生成支付单
*/
@POST
@RolesAllowed(Role.USER)
public Payment executeSettlement(@Valid @SufficientStock Settlement settlement) {
return service.executeBySettlement(settlement);
}
}
================================================
FILE: src/main/resources/application-mysql.yml
================================================
#请在启动参数中加入--spring.profiles.active=mysql以激活本配置文件
database: mysql
spring:
datasource:
url: "jdbc:mysql://mysql_lan:3306/bookstore?useUnicode=true&characterEncoding=utf-8"
username: "root"
password: "[4321qwer]"
initialization-mode: always
================================================
FILE: src/main/resources/application-test.yml
================================================
database: hsqldb
spring:
datasource:
schema: "classpath:db/${database}/schema.sql"
data: "classpath:db/${database}/data.sql"
sql-script-encoding: UTF-8
jpa:
show-sql: true
hibernate:
ddl-auto: none
logging:
pattern:
console: "%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(-){faint} %clr([%t]){faint} %clr(%-40logger{39}){cyan}[%line]%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}"
level:
root: INFO
com.github.fenixsoft: DEBUG
================================================
FILE: src/main/resources/application.yml
================================================
database: hsqldb
spring:
datasource:
schema: "classpath:db/${database}/schema.sql"
data: "classpath:db/${database}/data.sql"
sql-script-encoding: UTF-8
jpa:
show-sql: false
hibernate:
ddl-auto: none
open-in-view: false
resources:
chain:
compressed: true
cache: true
cache:
period: 86400
logging:
pattern:
console: "%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(-){faint} %clr([%t]){faint} %clr(%-40logger{39}){cyan}[%line]%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}"
level:
root: INFO
================================================
FILE: src/main/resources/banner.txt
================================================
______ _ ____ __ _____ __
/ ____/__ ____ (_) __ / __ )____ ____ / /_/ ___// /_____ ________
/ /_ / _ \/ __ \/ / |/_/ / __ / __ \/ __ \/ __/\__ \/ __/ __ \/ ___/ _ \
/ __/ / __/ / / / /> < / /_/ / /_/ / /_/ / /_ ___/ / /_/ /_/ / / / __/
/_/ \___/_/ /_/_/_/|_| /_____/\____/\____/\__//____/\__/\____/_/ \___/
https://icyfenix.cn
================================================
FILE: src/main/resources/db/hsqldb/data.sql
================================================
INSERT INTO product VALUES (8, '凤凰架构:构建可靠的大型分布式系统', 0, 0, '<p>这是一部以“如何构建一套可靠的分布式大型软件系统”为叙事主线的开源文档,是一幅帮助开发人员整理现代软件架构各条分支中繁多知识点的技能地图。文章《<a href="https://icyfenix.cn/introduction/about-the-fenix-project.html" target=_blank>什么是“凤凰架构”</a>》详细阐述了这部文档的主旨、目标与名字的来由,文章《<a href="https://icyfenix.cn/exploration/guide/quick-start.html" target=_blank>如何开始</a>》简述了文档每章讨论的主要话题与内容详略分布</p>','/static/cover/fenix.png','/static/desc/fenix.jpg');
INSERT INTO product VALUES (1, '深入理解Java虚拟机(第3版)', 129, 9.6, '<p>这是一部从工作原理和工程实践两个维度深入剖析JVM的著作,是计算机领域公认的经典,繁体版在台湾也颇受欢迎。</p><p>自2011年上市以来,前两个版本累计印刷36次,销量超过30万册,两家主要网络书店的评论近90000条,内容上近乎零差评,是原创计算机图书领域不可逾越的丰碑,第3版在第2版的基础上做了重大修订,内容更丰富、实战性更强:根据新版JDK对内容进行了全方位的修订和升级,围绕新技术和生产实践新增逾10万字,包含近50%的全新内容,并对第2版中含糊、瑕疵和错误内容进行了修正。</p><p>全书一共13章,分为五大部分:</p><p>第一部分(第1章)走近Java</p><p>系统介绍了Java的技术体系、发展历程、虚拟机家族,以及动手编译JDK,了解这部分内容能对学习JVM提供良好的指引。</p><p>第二部分(第2~5章)自动内存管理</p><p>详细讲解了Java的内存区域与内存溢出、垃圾收集器与内存分配策略、虚拟机性能监控与故障排除等与自动内存管理相关的内容,以及10余个经典的性能优化案例和优化方法;</p><p>第三部分(第6~9章)虚拟机执行子系统</p><p>深入分析了虚拟机执行子系统,包括类文件结构、虚拟机类加载机制、虚拟机字节码执行引擎,以及多个类加载及其执行子系统的实战案例;</p><p>第四部分(第10~11章)程序编译与代码优化</p><p>详细讲解了程序的前、后端编译与优化,包括前端的易用性优化措施,如泛型、主动装箱拆箱、条件编译等的内容的深入分析;以及后端的性能优化措施,如虚拟机的热点探测方法、HotSpot 的即时编译器、提前编译器,以及各种常见的编译期优化技术;</p><p>第五部分(第12~13章)高效并发</p><p>主要讲解了Java实现高并发的原理,包括Java的内存模型、线程与协程,以及线程安全和锁优化。</p><p>全书以实战为导向,通过大量与实际生产环境相结合的案例分析和展示了解决各种Java技术难题的方案和技巧。</p>','/static/cover/jvm3.jpg','/static/desc/jvm3.jpg');
INSERT INTO product VALUES (2, '智慧的疆界', 69, 9.1, '<p>这是一部对人工智能充满敬畏之心的匠心之作,由《深入理解Java虚拟机》作者耗时一年完成,它将带你从奠基人物、历史事件、学术理论、研究成果、技术应用等5个维度全面读懂人工智能。</p>\n<p>本书以时间为主线,用专业的知识、通俗的语言、巧妙的内容组织方式,详细讲解了人工智能这个学科的全貌、能解决什么问题、面临怎样的困难、尝试过哪些努力、取得过多少成绩、未来将向何方发展,尽可能消除人工智能的神秘感,把阳春白雪的人工智能从科学的殿堂推向公众面前。</p>','/static/cover/ai.jpg','/static/desc/ai.jpg');
INSERT INTO product VALUES (3, 'Java虚拟机规范(Java SE 8)', 79, 7.7, '<p>本书完整而准确地阐释了Java虚拟机各方面的细节,围绕Java虚拟机整体架构、编译器、class文件格式、加载、链接与初始化、指令集等核心主题对Java虚拟机进行全面而深入的分析,深刻揭示Java虚拟机的工作原理。同时,书中不仅完整地讲述了由Java SE 8所引入的新特性,例如对包含默认实现代码的接口方法所做的调用,还讲述了为支持类型注解及方法参数注解而对class文件格式所做的扩展,并阐明了class文件中各属性的含义,以及字节码验证的规则。</p>','/static/cover/jvms8.jpg','');
INSERT INTO product VALUES (4, '深入理解Java虚拟机(第2版)', 79, 9.0, '<p>《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》内容简介:第1版两年内印刷近10次,4家网上书店的评论近4?000条,98%以上的评论全部为5星级的好评,是整个Java图书领域公认的经典著作和超级畅销书,繁体版在台湾也十分受欢迎。第2版在第1版的基础上做了很大的改进:根据最新的JDK 1.7对全书内容进行了全面的升级和补充;增加了大量处理各种常见JVM问题的技巧和最佳实践;增加了若干与生产环境相结合的实战案例;对第1版中的错误和不足之处的修正;等等。第2版不仅技术更新、内容更丰富,而且实战性更强。</p><p>《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》共分为五大部分,围绕内存管理、执行子系统、程序编译与优化、高效并发等核心主题对JVM进行了全面而深入的分析,深刻揭示了JVM的工作原理。</p><p>第一部分从宏观的角度介绍了整个Java技术体系、Java和JVM的发展历程、模块化,以及JDK的编译,这对理解书中后面内容有重要帮助。</p><p>第二部分讲解了JVM的自动内存管理,包括虚拟机内存区域的划分原理以及各种内存溢出异常产生的原因;常见的垃圾收集算法以及垃圾收集器的特点和工作原理;常见虚拟机监控与故障处理工具的原理和使用方法。</p><p>第三部分分析了虚拟机的执行子系统,包括类文件结构、虚拟机类加载机制、虚拟机字节码执行引擎。</p><p>第四部分讲解了程序的编译与代码的优化,阐述了泛型、自动装箱拆箱、条件编译等语法糖的原理;讲解了虚拟机的热点探测方法、HotSpot的即时编译器、编译触发条件,以及如何从虚拟机外部观察和分析JIT编译的数据和结果;</p><p>第五部分探讨了Java实现高效并发的原理,包括JVM内存模型的结构和操作;原子性、可见性和有序性在Java内存模型中的体现;先行发生原则的规则和使用;线程在Java语言中的实现原理;虚拟机实现高效并发所做的一系列锁优化措施。</p>','/static/cover/jvm2.jpg','/static/desc/jvm2.jpg');
INSERT INTO product VALUES (5, 'Java虚拟机规范(Java SE 7)', 69, 8.9, '<p>本书整合了自1999年《Java虚拟机规范(第2版)》发布以来Java世界所出现的技术变化。另外,还修正了第2版中的许多错误,以及对目前主流Java虚拟机实现来说已经过时的内容。最后还处理了一些Java虚拟机和Java语言概念的模糊之处。</p><p>2004年发布的Java SE 5.0版为Java语言带来了翻天覆地的变化,但是对Java虚拟机设计的影响则相对较小。在Java SE 7这个版本中,我们扩充了class文件格式以便支持新的Java语言特性,譬如泛型和变长参数方法等。</p>','/static/cover/jvms.jpg','/static/desc/jvms.jpg');
INSERT INTO product VALUES (6, '深入理解OSGi', 79, 7.7, '<p>本书是原创Java技术图书领域继《深入理解Java虚拟机》后的又一实力之作,也是全球首本基于最新OSGi R5.0规范的著作。理论方面,既全面解读了OSGi规范,深刻揭示了OSGi原理,详细讲解了OSGi服务,又系统地介绍了Equinox框架的使用方法,并通过源码分析了该框架的工作机制;实践方面,不仅包含一些典型的案例,还总结了大量的最佳实践,极具实践指导意义。</p><p>全书共14章,分4个部分。第一部分(第1章):走近OSGi,主要介绍了什么是OSGi以及为什么要使用OSGi。第二部分(第2~4章):OSGi规范与原理,对最新的OSGi R5.0中的核心规范进行了全面的解读,首先讲解了OSGi模块的建立、描述、依赖关系的处理,然后讲解了Bundle的启动原理和调度管理,最后讲解了与本地及远程服务相关的内容。第三部分:OSGi服务与Equinox应用实践(第5~11章),不仅详细讲解了OSGi服务纲要规范和企业级规范中最常用的几个子规范和服务的技术细节,还通过一个基于Equinox的BBS案例演示了Equinox的使用方法,最重要的是还通过源码分析了Equinox关键功能的实现机制和原理。第四部分:最佳实践(第12~14章),总结了大量关于OSGi的最佳实践,包括从Bundle如何命名、模块划分、依赖关系处理到保持OSGi动态性、管理程序启动顺序、使用API基线管理模块版本等各方面的实践技巧,此外还介绍了Spring DM的原理以及如何在OSGi环节中进行程序测试。</p>','/static/cover/osgi.jpg','/static/desc/OSGi.jpg');
INSERT INTO product VALUES (7, '深入理解Java虚拟机', 69, 8.6, '<p>作为一位Java程序员,你是否也曾经想深入理解Java虚拟机,但是却被它的复杂和深奥拒之门外?没关系,本书极尽化繁为简之妙,能带领你在轻松中领略Java虚拟机的奥秘。本书是近年来国内出版的唯一一本与Java虚拟机相关的专著,也是唯一一本同时从核心理论和实际运用这两个角度去探讨Java虚拟机的著作,不仅理论分析得透彻,而且书中包含的典型案例和最佳实践也极具现实指导意义。</p><p>全书共分为五大部分。第一部分从宏观的角度介绍了整个Java技术体系的过去、现在和未来,以及如何独立地编译一个OpenJDK7,这对理解后面的内容很有帮助。第二部分讲解了JVM的自动内存管理,包括虚拟机内存区域的划分原理以及各种内存溢出异常产生的原因;常见的垃圾收集算法以及垃圾收集器的特点和工作原理;常见的虚拟机的监控与调试工具的原理和使用方法。第三部分分析了虚拟机的执行子系统,包括Class的文件结构以及如何存储和访问Class中的数据;虚拟机的类创建机制以及类加载器的工作原理和它对虚拟机的意义;虚拟机字节码的执行引擎以及它在实行代码时涉及的内存结构。第四部分讲解了程序的编译与代码的优化,阐述了泛型、自动装箱拆箱、条件编译等语法糖的原理;讲解了虚拟机的热点探测方法、HotSpot的即时编译器、编译触发条件,以及如何从虚拟机外部观察和分析JIT编译的数据和结果。第五部分探讨了Java实现高效并发的原理,包括JVM内存模型的结构和操作;原子性、可见性和有序性在Java内存模型中的体现;先行发生原则的规则和使用;线程在Java语言中的实现原理;虚拟机实现高效并发所做的一系列锁优化措施。</p>','/static/cover/jvm1.jpg','');
INSERT INTO specification VALUES (1, '作者','周志明',1);
INSERT INTO specification VALUES (2, '副标题','JVM高级特性与最佳实践',1);
INSERT INTO specification VALUES (3, 'ISBN','9787111641247',1);
INSERT INTO specification VALUES (4, '书名','深入理解Java虚拟机(第3版)',1);
INSERT INTO specification VALUES (5, '页数', '540',1);
INSERT INTO specification VALUES (6, '丛书','华章原创精品',1);
INSERT INTO specification VALUES (7, '出版社','机械工业出版社',1);
INSERT INTO specification VALUES (8, '出版年','2019-12',1);
INSERT INTO specification VALUES (9, '装帧','平装',1);
INSERT INTO specification VALUES (10, '作者','周志明',2);
INSERT INTO specification VALUES (11, 'ISBN','9787111610496',2);
INSERT INTO specification VALUES (12, '书名','智慧的疆界',2);
INSERT INTO specification VALUES (13, '副标题','从图灵机到人工智能',2);
INSERT INTO specification VALUES (14, '页数','413',2);
INSERT INTO specification VALUES (15, '出版社','机械工业出版社',2);
INSERT INTO specification VALUES (16, '出版年','2018-1-1',2);
INSERT INTO specification VALUES (17, '装帧','平装',2);
INSERT INTO specification VALUES (18, '作者','Tim Lindholm / Frank Yellin 等',3);
INSERT INTO specification VALUES (19, '译者','爱飞翔 / 周志明 / 等 ',3);
INSERT INTO specification VALUES (20, '原作名','The Java Virtual Machine Specification, Java SE 8 Edition',3);
INSERT INTO specification VALUES (21, '丛书','Java核心技术系列',3);
INSERT INTO specification VALUES (22, 'ISBN','9787111501596',3);
INSERT INTO specification VALUES (23, '页数','330',3);
INSERT INTO specification VALUES (24, '出版社','机械工业出版社',3);
INSERT INTO specification VALUES (25, '出版年','2015-6',3);
INSERT INTO specification VALUES (26, '装帧','平装',3)
INSERT INTO specification VALUES (27, '作者','周志明',4);
INSERT INTO specification VALUES (28, '副标题','JVM高级特性与最佳实践',4);
INSERT INTO specification VALUES (29, 'ISBN','9787111421900',4);
INSERT INTO specification VALUES (30, '书名','深入理解Java虚拟机(第2版)',4);
INSERT INTO specification VALUES (31, '页数', '433',4);
INSERT INTO specification VALUES (32, '丛书','华章原创精品',4);
INSERT INTO specification VALUES (33, '出版社','机械工业出版社',4);
INSERT INTO specification VALUES (34, '出版年','2013-9-1',4);
INSERT INTO specification VALUES (35, '装帧','平装',4);
INSERT INTO specification VALUES (36, '作者','Tim Lindholm / Frank Yellin 等',5);
INSERT INTO specification VALUES (37, '译者','周志明 / 薛笛 / 吴璞渊 / 冶秀刚',5);
INSERT INTO specification VALUES (38, '原作名','The Java Virtual Machine Specification, Java SE 7 Edition',5);
INSERT INTO specification VALUES (39, '副标题','从图灵机到人工智能',5);
INSERT INTO specification VALUES (40, 'ISBN','9787111445159',5);
INSERT INTO specification VALUES (41, '页数','316',5);
INSERT INTO specification VALUES (42, '出版社','机械工业出版社',5);
INSERT INTO specification VALUES (43, '丛书','Java核心技术系列',5);
INSERT INTO specification VALUES (44, '出版年','2014-1',5);
INSERT INTO specification VALUES (45, '装帧','平装',5);
INSERT INTO specification VALUES (46, '作者','周志明 / 谢小明 ',6);
INSERT INTO specification VALUES (47, '副标题','Equinox原理、应用与最佳实践',6);
INSERT INTO specification VALUES (48, 'ISBN','9787111408871',6);
INSERT INTO specification VALUES (49, '书名','智慧的疆界',6);
INSERT INTO specification VALUES (50, '丛书','华章原创精品',6);
INSERT INTO specification VALUES (51, '页数','432',6);
INSERT INTO specification VALUES (52, '出版社','机械工业出版社',6);
INSERT INTO specification VALUES (53, '出版年','2013-2-25',6);
INSERT INTO specification VALUES (54, '装帧','平装',6);
INSERT INTO specification VALUES (55, '作者','周志明',7);
INSERT INTO specification VALUES (56, '副标题','JVM高级特性与最佳实践',7);
INSERT INTO specification VALUES (57, 'ISBN','9787111349662',7);
INSERT INTO specification VALUES (58, '书名','深入理解Java虚拟机',7);
INSERT INTO specification VALUES (59, '页数','387',7);
INSERT INTO specification VALUES (60, '出版社','机械工业出版社',7);
INSERT INTO specification VALUES (61, '出版年','2011-6',7);
INSERT INTO specification VALUES (62, '装帧','平装',7);
INSERT INTO specification VALUES (63, '作者','周志明',8);
INSERT INTO specification VALUES (64, 'ISBN','9787111349662',8);
INSERT INTO specification VALUES (65, '书名','凤凰架构',8);
INSERT INTO specification VALUES (70, '副标题', '构建可靠的大型分布式系统',8);
INSERT INTO specification VALUES (66, '页数','409',8);
INSERT INTO specification VALUES (67, '出版社','机械工业出版社',8);
INSERT INTO specification VALUES (68, '出版年','2020-6',8);
INSERT INTO specification VALUES (69, '装帧','在线',8);
INSERT INTO advertisement VALUES (1, '/static/carousel/fenix2.png',8);
INSERT INTO advertisement VALUES (2, '/static/carousel/ai.png',2);
INSERT INTO advertisement VALUES (3, '/static/carousel/jvm3.png',1);
INSERT INTO stockpile VALUES (1, 30, 0, 1);
INSERT INTO stockpile VALUES (2, 30, 0, 2);
INSERT INTO stockpile VALUES (3, 30, 0, 3);
INSERT INTO stockpile VALUES (4, 30, 0, 4);
INSERT INTO stockpile VALUES (5, 30, 0, 5);
INSERT INTO stockpile VALUES (6, 30, 0, 6);
INSERT INTO stockpile VALUES (7, 30, 0, 7);
INSERT INTO stockpile VALUES (8, 30, 0, 8);
INSERT INTO account VALUES (1, 'icyfenix', '$2a$10$iIim4LtpT2yjxU2YVNDuO.yb1Z2lq86vYBZleAeuIh2aFXjyoMCM.' , '周志明', '', '18888888888', 'icyfenix@gmail.com', '唐家湾港湾大道科技一路3号远光软件股份有限公司');
INSERT INTO wallet VALUES (1, 300, 1);
================================================
FILE: src/main/resources/db/hsqldb/schema.sql
================================================
DROP TABLE wallet IF EXISTS;
DROP TABLE account IF EXISTS;
DROP TABLE specification IF EXISTS;
DROP TABLE advertisement IF EXISTS;
DROP TABLE stockpile IF EXISTS;
DROP TABLE product IF EXISTS;
DROP TABLE payment IF EXISTS;
CREATE TABLE account
(
id INTEGER IDENTITY PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(100),
name VARCHAR(50),
avatar VARCHAR(100),
telephone VARCHAR(20),
email VARCHAR(100),
location VARCHAR(100)
);
CREATE UNIQUE INDEX account_user ON account (username);
CREATE UNIQUE INDEX account_telephone ON account (telephone);
CREATE UNIQUE INDEX account_email ON account (email);
CREATE TABLE wallet
(
id INTEGER IDENTITY PRIMARY KEY,
money DECIMAL,
account_id INTEGER
);
ALTER TABLE wallet
ADD CONSTRAINT fk_wallet_account FOREIGN KEY (account_id) REFERENCES account (id) ON DELETE CASCADE;
CREATE TABLE product
(
id INTEGER IDENTITY PRIMARY KEY,
title VARCHAR(50),
price DECIMAL,
rate FLOAT,
description VARCHAR(8000),
cover VARCHAR(100),
detail VARCHAR(100)
);
CREATE INDEX product_title ON product (title);
CREATE TABLE stockpile
(
id INTEGER IDENTITY PRIMARY KEY,
amount INTEGER,
frozen INTEGER,
product_id INTEGER
);
ALTER TABLE stockpile
ADD CONSTRAINT fk_stockpile_product FOREIGN KEY (product_id) REFERENCES product (id) ON DELETE CASCADE;
CREATE TABLE specification
(
id INTEGER IDENTITY PRIMARY KEY,
item VARCHAR(50),
value VARCHAR(100),
product_id INTEGER
);
ALTER TABLE specification
ADD CONSTRAINT fk_specification_product FOREIGN KEY (product_id) REFERENCES product (id) ON DELETE CASCADE;
CREATE TABLE advertisement
(
id INTEGER IDENTITY PRIMARY KEY,
image VARCHAR(100),
product_id INTEGER
);
ALTER TABLE advertisement
ADD CONSTRAINT fk_advertisement_product FOREIGN KEY (product_id) REFERENCES product (id) ON DELETE CASCADE;
CREATE TABLE payment
(
id INTEGER IDENTITY PRIMARY KEY,
pay_id VARCHAR(100),
create_time DATETIME,
total_price DECIMAL,
expires INTEGER NOT NULL,
payment_link VARCHAR(300),
pay_state VARCHAR(20)
);
================================================
FILE: src/main/resources/db/mysql/data.sql
================================================
INSERT INTO product
VALUES (8, '凤凰架构:构建可靠的大型分布式系统', 0, 0,
'<p>这是一部以“如何构建一套可靠的分布式大型软件系统”为叙事主线的开源文档,是一幅帮助开发人员整理现代软件架构各条分支中繁多知识点的技能地图。文章《<a href="https://icyfenix.cn/introduction/about-the-fenix-project.html" target=_blank>什么是“凤凰架构”</a>》详细阐述了这部文档的主旨、目标与名字的来由,文章《<a href="https://icyfenix.cn/exploration/guide/quick-start.html" target=_blank>如何开始</a>》简述了文档每章讨论的主要话题与内容详略分布</p>',
'/static/cover/fenix.png', '/static/desc/fenix.jpg');
INSERT INTO product
VALUES (1, '深入理解Java虚拟机(第3版)', 129, 9.6,
'<p>这是一部从工作原理和工程实践两个维度深入剖析JVM的著作,是计算机领域公认的经典,繁体版在台湾也颇受欢迎。</p><p>自2011年上市以来,前两个版本累计印刷36次,销量超过30万册,两家主要网络书店的评论近90000条,内容上近乎零差评,是原创计算机图书领域不可逾越的丰碑,第3版在第2版的基础上做了重大修订,内容更丰富、实战性更强:根据新版JDK对内容进行了全方位的修订和升级,围绕新技术和生产实践新增逾10万字,包含近50%的全新内容,并对第2版中含糊、瑕疵和错误内容进行了修正。</p><p>全书一共13章,分为五大部分:</p><p>第一部分(第1章)走近Java</p><p>系统介绍了Java的技术体系、发展历程、虚拟机家族,以及动手编译JDK,了解这部分内容能对学习JVM提供良好的指引。</p><p>第二部分(第2~5章)自动内存管理</p><p>详细讲解了Java的内存区域与内存溢出、垃圾收集器与内存分配策略、虚拟机性能监控与故障排除等与自动内存管理相关的内容,以及10余个经典的性能优化案例和优化方法;</p><p>第三部分(第6~9章)虚拟机执行子系统</p><p>深入分析了虚拟机执行子系统,包括类文件结构、虚拟机类加载机制、虚拟机字节码执行引擎,以及多个类加载及其执行子系统的实战案例;</p><p>第四部分(第10~11章)程序编译与代码优化</p><p>详细讲解了程序的前、后端编译与优化,包括前端的易用性优化措施,如泛型、主动装箱拆箱、条件编译等的内容的深入分析;以及后端的性能优化措施,如虚拟机的热点探测方法、HotSpot 的即时编译器、提前编译器,以及各种常见的编译期优化技术;</p><p>第五部分(第12~13章)高效并发</p><p>主要讲解了Java实现高并发的原理,包括Java的内存模型、线程与协程,以及线程安全和锁优化。</p><p>全书以实战为导向,通过大量与实际生产环境相结合的案例分析和展示了解决各种Java技术难题的方案和技巧。</p>',
'/static/cover/jvm3.jpg', '/static/desc/jvm3.jpg');
INSERT INTO product
VALUES (2, '智慧的疆界', 69, 9.1,
'<p>这是一部对人工智能充满敬畏之心的匠心之作,由《深入理解Java虚拟机》作者耗时一年完成,它将带你从奠基人物、历史事件、学术理论、研究成果、技术应用等5个维度全面读懂人工智能。</p>\n<p>本书以时间为主线,用专业的知识、通俗的语言、巧妙的内容组织方式,详细讲解了人工智能这个学科的全貌、能解决什么问题、面临怎样的困难、尝试过哪些努力、取得过多少成绩、未来将向何方发展,尽可能消除人工智能的神秘感,把阳春白雪的人工智能从科学的殿堂推向公众面前。</p>',
'/static/cover/ai.jpg', '/static/desc/ai.jpg');
INSERT INTO product
VALUES (3, 'Java虚拟机规范(Java SE 8)', 79, 7.7,
'<p>本书完整而准确地阐释了Java虚拟机各方面的细节,围绕Java虚拟机整体架构、编译器、class文件格式、加载、链接与初始化、指令集等核心主题对Java虚拟机进行全面而深入的分析,深刻揭示Java虚拟机的工作原理。同时,书中不仅完整地讲述了由Java SE 8所引入的新特性,例如对包含默认实现代码的接口方法所做的调用,还讲述了为支持类型注解及方法参数注解而对class文件格式所做的扩展,并阐明了class文件中各属性的含义,以及字节码验证的规则。</p>',
'/static/cover/jvms8.jpg', '');
INSERT INTO product
VALUES (4, '深入理解Java虚拟机(第2版)', 79, 9.0,
'<p>《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》内容简介:第1版两年内印刷近10次,4家网上书店的评论近4?000条,98%以上的评论全部为5星级的好评,是整个Java图书领域公认的经典著作和超级畅销书,繁体版在台湾也十分受欢迎。第2版在第1版的基础上做了很大的改进:根据最新的JDK 1.7对全书内容进行了全面的升级和补充;增加了大量处理各种常见JVM问题的技巧和最佳实践;增加了若干与生产环境相结合的实战案例;对第1版中的错误和不足之处的修正;等等。第2版不仅技术更新、内容更丰富,而且实战性更强。</p><p>《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》共分为五大部分,围绕内存管理、执行子系统、程序编译与优化、高效并发等核心主题对JVM进行了全面而深入的分析,深刻揭示了JVM的工作原理。</p><p>第一部分从宏观的角度介绍了整个Java技术体系、Java和JVM的发展历程、模块化,以及JDK的编译,这对理解书中后面内容有重要帮助。</p><p>第二部分讲解了JVM的自动内存管理,包括虚拟机内存区域的划分原理以及各种内存溢出异常产生的原因;常见的垃圾收集算法以及垃圾收集器的特点和工作原理;常见虚拟机监控与故障处理工具的原理和使用方法。</p><p>第三部分分析了虚拟机的执行子系统,包括类文件结构、虚拟机类加载机制、虚拟机字节码执行引擎。</p><p>第四部分讲解了程序的编译与代码的优化,阐述了泛型、自动装箱拆箱、条件编译等语法糖的原理;讲解了虚拟机的热点探测方法、HotSpot的即时编译器、编译触发条件,以及如何从虚拟机外部观察和分析JIT编译的数据和结果;</p><p>第五部分探讨了Java实现高效并发的原理,包括JVM内存模型的结构和操作;原子性、可见性和有序性在Java内存模型中的体现;先行发生原则的规则和使用;线程在Java语言中的实现原理;虚拟机实现高效并发所做的一系列锁优化措施。</p>',
'/static/cover/jvm2.jpg', '/static/desc/jvm2.jpg');
INSERT INTO product
VALUES (5, 'Java虚拟机规范(Java SE 7)', 69, 8.9,
'<p>本书整合了自1999年《Java虚拟机规范(第2版)》发布以来Java世界所出现的技术变化。另外,还修正了第2版中的许多错误,以及对目前主流Java虚拟机实现来说已经过时的内容。最后还处理了一些Java虚拟机和Java语言概念的模糊之处。</p><p>2004年发布的Java SE 5.0版为Java语言带来了翻天覆地的变化,但是对Java虚拟机设计的影响则相对较小。在Java SE 7这个版本中,我们扩充了class文件格式以便支持新的Java语言特性,譬如泛型和变长参数方法等。</p>',
'/static/cover/jvms.jpg', '/static/desc/jvms.jpg');
INSERT INTO product
VALUES (6, '深入理解OSGi', 79, 7.7,
'<p>本书是原创Java技术图书领域继《深入理解Java虚拟机》后的又一实力之作,也是全球首本基于最新OSGi R5.0规范的著作。理论方面,既全面解读了OSGi规范,深刻揭示了OSGi原理,详细讲解了OSGi服务,又系统地介绍了Equinox框架的使用方法,并通过源码分析了该框架的工作机制;实践方面,不仅包含一些典型的案例,还总结了大量的最佳实践,极具实践指导意义。</p><p>全书共14章,分4个部分。第一部分(第1章):走近OSGi,主要介绍了什么是OSGi以及为什么要使用OSGi。第
gitextract_udx1wp_1/ ├── .gitignore ├── .mvn/ │ └── wrapper/ │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── github/ │ │ │ └── fenixsoft/ │ │ │ └── bookstore/ │ │ │ ├── BookstoreApplication.java │ │ │ ├── applicaiton/ │ │ │ │ ├── AccountApplicationService.java │ │ │ │ ├── ProductApplicationService.java │ │ │ │ └── payment/ │ │ │ │ ├── PaymentApplicationService.java │ │ │ │ └── dto/ │ │ │ │ └── Settlement.java │ │ │ ├── domain/ │ │ │ │ ├── BaseEntity.java │ │ │ │ ├── account/ │ │ │ │ │ ├── Account.java │ │ │ │ │ ├── AccountRepository.java │ │ │ │ │ └── validation/ │ │ │ │ │ ├── AccountValidation.java │ │ │ │ │ ├── AuthenticatedAccount.java │ │ │ │ │ ├── ExistsAccount.java │ │ │ │ │ ├── NotConflictAccount.java │ │ │ │ │ └── UniqueAccount.java │ │ │ │ ├── auth/ │ │ │ │ │ ├── AuthenticAccount.java │ │ │ │ │ ├── AuthenticAccountRepository.java │ │ │ │ │ ├── Role.java │ │ │ │ │ ├── provider/ │ │ │ │ │ │ ├── PreAuthenticatedAuthenticationProvider.java │ │ │ │ │ │ └── UsernamePasswordAuthenticationProvider.java │ │ │ │ │ └── service/ │ │ │ │ │ ├── AuthenticAccountDetailsService.java │ │ │ │ │ ├── JWTAccessToken.java │ │ │ │ │ ├── JWTAccessTokenService.java │ │ │ │ │ └── OAuthClientDetailsService.java │ │ │ │ ├── payment/ │ │ │ │ │ ├── Payment.java │ │ │ │ │ ├── PaymentRepository.java │ │ │ │ │ ├── PaymentService.java │ │ │ │ │ ├── Stockpile.java │ │ │ │ │ ├── StockpileRepository.java │ │ │ │ │ ├── StockpileService.java │ │ │ │ │ ├── Wallet.java │ │ │ │ │ ├── WalletRepository.java │ │ │ │ │ ├── WalletService.java │ │ │ │ │ └── validation/ │ │ │ │ │ ├── SettlementValidator.java │ │ │ │ │ └── SufficientStock.java │ │ │ │ └── warehouse/ │ │ │ │ ├── Advertisement.java │ │ │ │ ├── AdvertisementRepository.java │ │ │ │ ├── Product.java │ │ │ │ ├── ProductRepository.java │ │ │ │ ├── ProductService.java │ │ │ │ └── Specification.java │ │ │ ├── infrastructure/ │ │ │ │ ├── cache/ │ │ │ │ │ └── CacheConfiguration.java │ │ │ │ ├── configuration/ │ │ │ │ │ ├── AuthenticationServerConfiguration.java │ │ │ │ │ ├── AuthorizationServerConfiguration.java │ │ │ │ │ ├── JerseyConfiguration.java │ │ │ │ │ ├── ResourceServerConfiguration.java │ │ │ │ │ └── WebSecurityConfiguration.java │ │ │ │ ├── jaxrs/ │ │ │ │ │ ├── AccessDeniedExceptionMapper.java │ │ │ │ │ ├── BaseExceptionMapper.java │ │ │ │ │ ├── CodedMessage.java │ │ │ │ │ ├── CommonResponse.java │ │ │ │ │ └── ViolationExceptionMapper.java │ │ │ │ └── utility/ │ │ │ │ └── Encryption.java │ │ │ └── resource/ │ │ │ ├── AccountResource.java │ │ │ ├── AdvertisementResource.java │ │ │ ├── PaymentResource.java │ │ │ ├── ProductResource.java │ │ │ └── SettlementResource.java │ │ └── resources/ │ │ ├── application-mysql.yml │ │ ├── application-test.yml │ │ ├── application.yml │ │ ├── banner.txt │ │ ├── db/ │ │ │ ├── hsqldb/ │ │ │ │ ├── data.sql │ │ │ │ └── schema.sql │ │ │ └── mysql/ │ │ │ ├── data.sql │ │ │ ├── schema.sql │ │ │ └── user.sql │ │ └── static/ │ │ ├── index.html │ │ └── static/ │ │ ├── board/ │ │ │ ├── gitalk.css │ │ │ ├── gitalk.html │ │ │ └── gitalk.min.js │ │ ├── css/ │ │ │ └── app.13440f960e43a3574b009b7352447f18.css │ │ └── js/ │ │ ├── 0.c178f427b3d08777c70f.js │ │ ├── 1.a33faf036923758c7965.js │ │ ├── 2.626ed94f3752555e21f0.js │ │ ├── 3.bc7f0b2154007257c317.js │ │ ├── 4.b4e48a42cf742af20851.js │ │ ├── 5.d375cbd6c7e1463cdbed.js │ │ ├── 6.68562501db5734ef1531.js │ │ ├── 7.184a5e39cc0c624f6a6d.js │ │ ├── 8.176f9455c3442c06ebf6.js │ │ ├── 9.527be297aba1594ffe0d.js │ │ ├── app.ea66dc0be78c3ed2ae63.js │ │ ├── manifest.0437a7f02d3154ee1abb.js │ │ └── vendor.c2f13a2146485051ae24.js │ └── test/ │ └── java/ │ └── com/ │ └── github/ │ └── fenixsoft/ │ └── bookstore/ │ ├── DBRollbackBase.java │ └── resource/ │ ├── AccountResourceTest.java │ ├── AdvertisementResourceTest.java │ ├── AuthResourceTest.java │ ├── JAXRSResourceBase.java │ ├── PaymentResourceTest.java │ └── ProductResourceTest.java └── travis_docker_push.sh
SYMBOL INDEX (1496 symbols across 69 files)
FILE: .mvn/wrapper/MavenWrapperDownloader.java
class MavenWrapperDownloader (line 22) | public class MavenWrapperDownloader {
method main (line 49) | public static void main(String args[]) {
method downloadFileFromURL (line 98) | private static void downloadFileFromURL(String urlString, File destina...
FILE: src/main/java/com/github/fenixsoft/bookstore/BookstoreApplication.java
class BookstoreApplication (line 26) | @SpringBootApplication
method main (line 30) | public static void main(String[] args) {
FILE: src/main/java/com/github/fenixsoft/bookstore/applicaiton/AccountApplicationService.java
class AccountApplicationService (line 35) | @Named
method createAccount (line 45) | public void createAccount(Account account) {
method findAccountByUsername (line 50) | public Account findAccountByUsername(String username) {
method updateAccount (line 54) | public void updateAccount(Account account) {
FILE: src/main/java/com/github/fenixsoft/bookstore/applicaiton/ProductApplicationService.java
class ProductApplicationService (line 36) | @Named
method getAllProducts (line 49) | public Iterable<Product> getAllProducts() {
method getProduct (line 56) | public Product getProduct(Integer id) {
method saveProduct (line 63) | public Product saveProduct(Product product) {
method removeProduct (line 70) | public void removeProduct(Integer id) {
method getStockpile (line 78) | public Stockpile getStockpile(Integer productId) {
method setStockpileAmountByProductId (line 85) | public void setStockpileAmountByProductId(Integer productId, Integer a...
FILE: src/main/java/com/github/fenixsoft/bookstore/applicaiton/payment/PaymentApplicationService.java
class PaymentApplicationService (line 37) | @Named
method executeBySettlement (line 56) | public Payment executeBySettlement(Settlement bill) {
method accomplishPayment (line 70) | public void accomplishPayment(Integer accountId, String payId) {
method cancelPayment (line 83) | public void cancelPayment(String payId) {
FILE: src/main/java/com/github/fenixsoft/bookstore/applicaiton/payment/dto/Settlement.java
class Settlement (line 37) | public class Settlement {
method getItems (line 51) | public Collection<Item> getItems() {
method setItems (line 55) | public void setItems(Collection<Item> items) {
method getPurchase (line 59) | public Purchase getPurchase() {
method setPurchase (line 63) | public void setPurchase(Purchase purchase) {
class Item (line 70) | public static class Item {
method getAmount (line 79) | public Integer getAmount() {
method setAmount (line 83) | public void setAmount(Integer amount) {
method getProductId (line 87) | public Integer getProductId() {
method setProductId (line 91) | public void setProductId(Integer productId) {
class Purchase (line 99) | public static class Purchase {
method getDelivery (line 115) | public Boolean getDelivery() {
method setDelivery (line 119) | public void setDelivery(Boolean delivery) {
method getPay (line 123) | public String getPay() {
method setPay (line 127) | public void setPay(String pay) {
method getName (line 131) | public String getName() {
method setName (line 135) | public void setName(String name) {
method getTelephone (line 139) | public String getTelephone() {
method setTelephone (line 143) | public void setTelephone(String telephone) {
method getLocation (line 147) | public String getLocation() {
method setLocation (line 151) | public void setLocation(String location) {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/BaseEntity.java
class BaseEntity (line 33) | @MappedSuperclass
method getId (line 40) | public Integer getId() {
method setId (line 44) | public void setId(Integer id) {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/account/Account.java
class Account (line 36) | @Entity
method getUsername (line 62) | public String getUsername() {
method setUsername (line 66) | public void setUsername(String username) {
method getPassword (line 70) | public String getPassword() {
method setPassword (line 74) | public void setPassword(String password) {
method getName (line 78) | public String getName() {
method setName (line 82) | public void setName(String name) {
method getAvatar (line 86) | public String getAvatar() {
method setAvatar (line 90) | public void setAvatar(String avatar) {
method getTelephone (line 94) | public String getTelephone() {
method setTelephone (line 98) | public void setTelephone(String telephone) {
method getEmail (line 102) | public String getEmail() {
method setEmail (line 106) | public void setEmail(String email) {
method getLocation (line 110) | public String getLocation() {
method setLocation (line 114) | public void setLocation(String location) {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/account/AccountRepository.java
type AccountRepository (line 36) | @CacheConfig(cacheNames = "repository.account")
method findAll (line 39) | @Override
method findByUsername (line 43) | @Cacheable(key = "#username")
method existsByUsernameOrEmailOrTelephone (line 49) | boolean existsByUsernameOrEmailOrTelephone(String username, String ema...
method findByUsernameOrEmailOrTelephone (line 54) | Collection<Account> findByUsernameOrEmailOrTelephone(String username, ...
method existsByUsername (line 59) | @Cacheable(key = "#username")
method save (line 65) | @Caching(evict = {
method saveAll (line 71) | @CacheEvict
method findById (line 74) | @Cacheable(key = "#id")
method existsById (line 77) | @Cacheable(key = "#id")
method deleteById (line 80) | @CacheEvict(key = "#id")
method delete (line 83) | @CacheEvict(key = "#entity.id")
method deleteAll (line 86) | @CacheEvict(allEntries = true)
method deleteAll (line 89) | @CacheEvict(allEntries = true)
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/account/validation/AccountValidation.java
class AccountValidation (line 41) | public class AccountValidation<T extends Annotation> implements Constrai...
method isValid (line 48) | @Override
class ExistsAccountValidator (line 58) | public static class ExistsAccountValidator extends AccountValidation<E...
method initialize (line 59) | public void initialize(ExistsAccount constraintAnnotation) {
class AuthenticatedAccountValidator (line 64) | public static class AuthenticatedAccountValidator extends AccountValid...
method initialize (line 65) | public void initialize(AuthenticatedAccount constraintAnnotation) {
class UniqueAccountValidator (line 78) | public static class UniqueAccountValidator extends AccountValidation<U...
method initialize (line 79) | public void initialize(UniqueAccount constraintAnnotation) {
class NotConflictAccountValidator (line 84) | public static class NotConflictAccountValidator extends AccountValidat...
method initialize (line 85) | public void initialize(NotConflictAccount constraintAnnotation) {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/AuthenticAccount.java
class AuthenticAccount (line 39) | public class AuthenticAccount extends Account implements UserDetails {
method AuthenticAccount (line 41) | public AuthenticAccount() {
method AuthenticAccount (line 46) | public AuthenticAccount(Account origin) {
method getAuthorities (line 60) | @Override
method setAuthorities (line 65) | public void setAuthorities(Collection<GrantedAuthority> authorities) {
method isAccountNonExpired (line 72) | @Override
method isAccountNonLocked (line 80) | @Override
method isCredentialsNonExpired (line 88) | @Override
method isEnabled (line 96) | @Override
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/AuthenticAccountRepository.java
class AuthenticAccountRepository (line 35) | @Component
method findByUsername (line 41) | public AuthenticAccount findByUsername(String username) {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/Role.java
type Role (line 27) | public interface Role {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/provider/PreAuthenticatedAuthenticationProvider.java
class PreAuthenticatedAuthenticationProvider (line 43) | @Named
method authenticate (line 46) | @Override
method supports (line 65) | @Override
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/provider/UsernamePasswordAuthenticationProvider.java
class UsernamePasswordAuthenticationProvider (line 42) | @Named
method authenticate (line 58) | @Override
method supports (line 74) | @Override
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/service/AuthenticAccountDetailsService.java
class AuthenticAccountDetailsService (line 35) | @Named
method loadUserByUsername (line 45) | @Override
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/service/JWTAccessToken.java
class JWTAccessToken (line 51) | @Named
method JWTAccessToken (line 58) | @Inject
method enhance (line 75) | @Override
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/service/JWTAccessTokenService.java
class JWTAccessTokenService (line 37) | @Named
method JWTAccessTokenService (line 43) | @Inject
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/auth/service/OAuthClientDetailsService.java
class OAuthClientDetailsService (line 43) | @Named
method init (line 72) | @PostConstruct
method loadClientByClientId (line 86) | @Override
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/Payment.java
class Payment (line 37) | @Entity
type State (line 43) | public enum State {
method Payment (line 62) | public Payment() {
method Payment (line 65) | public Payment(Double totalPrice, Long expires) {
method getPayId (line 89) | public String getPayId() {
method setPayId (line 93) | public void setPayId(String payId) {
method getCreateTime (line 97) | public Date getCreateTime() {
method setCreateTime (line 101) | public void setCreateTime(Date createTime) {
method getExpires (line 105) | public Long getExpires() {
method setExpires (line 109) | public void setExpires(Long expires) {
method getPaymentLink (line 113) | public String getPaymentLink() {
method setPaymentLink (line 117) | public void setPaymentLink(String paymentLink) {
method getTotalPrice (line 121) | public Double getTotalPrice() {
method setTotalPrice (line 125) | public void setTotalPrice(Double totalPrice) {
method getPayState (line 129) | public State getPayState() {
method setPayState (line 133) | public void setPayState(State payState) {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/PaymentRepository.java
type PaymentRepository (line 31) | public interface PaymentRepository extends CrudRepository<Payment, Integ...
method getByPayId (line 33) | Payment getByPayId(String payId);
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/PaymentService.java
class PaymentService (line 41) | @Named
method producePayment (line 67) | public Payment producePayment(Settlement bill) {
method accomplish (line 85) | public double accomplish(String payId) {
method cancel (line 106) | public void cancel(String payId) {
method setupAutoThawedTrigger (line 133) | public void setupAutoThawedTrigger(Payment payment) {
method accomplishSettlement (line 151) | private void accomplishSettlement(Payment.State endState, String payId) {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/Stockpile.java
class Stockpile (line 35) | @Entity
method getAmount (line 46) | public Integer getAmount() {
method setAmount (line 50) | public void setAmount(Integer amount) {
method frozen (line 54) | public void frozen(Integer number) {
method thawed (line 59) | public void thawed(Integer number) {
method decrease (line 63) | public void decrease(Integer number) {
method increase (line 67) | public void increase(Integer number) {
method getProduct (line 71) | public Product getProduct() {
method setProduct (line 75) | public void setProduct(Product product) {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/StockpileRepository.java
type StockpileRepository (line 29) | public interface StockpileRepository extends CrudRepository<Stockpile, I...
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/StockpileService.java
class StockpileService (line 34) | @Named
method getByProductId (line 45) | public Stockpile getByProductId(Integer productId) {
method decrease (line 53) | public void decrease(Integer productId, Integer amount) {
method increase (line 64) | public void increase(Integer productId, Integer amount) {
method frozen (line 76) | public void frozen(Integer productId, Integer amount) {
method thawed (line 87) | public void thawed(Integer productId, Integer amount) {
method set (line 97) | public void set(Integer productId, Integer amount) {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/Wallet.java
class Wallet (line 36) | @Entity
method getMoney (line 46) | public Double getMoney() {
method setMoney (line 50) | public void setMoney(Double money) {
method getAccount (line 54) | public Account getAccount() {
method setAccount (line 58) | public void setAccount(Account account) {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/WalletRepository.java
type WalletRepository (line 31) | public interface WalletRepository extends CrudRepository<Wallet, Integer> {
method findByAccountId (line 33) | Optional<Wallet> findByAccountId(Integer accountId);
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/WalletService.java
class WalletService (line 36) | @Named
method decrease (line 47) | public void decrease(Integer accountId, Double amount) {
method increase (line 69) | public void increase(Integer accountId, Double amount) {
method frozen (line 78) | public void frozen(Integer accountId, Double amount) {
method thawed (line 85) | public void thawed(Integer accountId, Double amount) {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/payment/validation/SettlementValidator.java
class SettlementValidator (line 40) | public class SettlementValidator implements ConstraintValidator<Sufficie...
method isValid (line 45) | @Override
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/Advertisement.java
class Advertisement (line 34) | @Entity
method getImage (line 44) | public String getImage() {
method setImage (line 48) | public void setImage(String image) {
method getProductId (line 52) | public Integer getProductId() {
method setProductId (line 56) | public void setProductId(Integer productId) {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/AdvertisementRepository.java
type AdvertisementRepository (line 30) | public interface AdvertisementRepository extends CrudRepository<Advertis...
method findAll (line 31) | Iterable<Advertisement> findAll() throws DataAccessException;
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/Product.java
class Product (line 36) | @Entity
method getTitle (line 61) | public String getTitle() {
method setTitle (line 65) | public void setTitle(String title) {
method getPrice (line 69) | public Double getPrice() {
method setPrice (line 73) | public void setPrice(Double price) {
method getRate (line 77) | public Float getRate() {
method setRate (line 81) | public void setRate(Float rate) {
method getDescription (line 85) | public String getDescription() {
method setDescription (line 89) | public void setDescription(String description) {
method getCover (line 93) | public String getCover() {
method setCover (line 97) | public void setCover(String cover) {
method getDetail (line 101) | public String getDetail() {
method setDetail (line 105) | public void setDetail(String detail) {
method getSpecifications (line 109) | public Set<Specification> getSpecifications() {
method setSpecifications (line 113) | public void setSpecifications(Set<Specification> specifications) {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/ProductRepository.java
type ProductRepository (line 31) | public interface ProductRepository extends CrudRepository<Product, Integ...
method findByIdIn (line 33) | Collection<Product> findByIdIn(Collection<Integer> ids);
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/ProductService.java
class ProductService (line 35) | @Named
method replenishProductInformation (line 44) | public void replenishProductInformation(Settlement bill) {
method getAllProducts (line 52) | public Iterable<Product> getAllProducts() {
method getProduct (line 59) | public Product getProduct(Integer id) {
method saveProduct (line 66) | public Product saveProduct(Product product) {
method removeProduct (line 73) | public void removeProduct(Integer id) {
FILE: src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/Specification.java
class Specification (line 34) | @Entity
method getItem (line 47) | public String getItem() {
method setItem (line 51) | public void setItem(String item) {
method getValue (line 55) | public String getValue() {
method setValue (line 59) | public void setValue(String value) {
method getProductId (line 63) | public Integer getProductId() {
method setProductId (line 67) | public void setProductId(Integer productId) {
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/cache/CacheConfiguration.java
class CacheConfiguration (line 19) | @Configuration
method configCacheManager (line 28) | @Bean
method getSettlementTTLCache (line 35) | @Bean(name = "settlement")
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/AuthenticationServerConfiguration.java
class AuthenticationServerConfiguration (line 45) | @Configuration
method authenticationManagerBean (line 66) | @Bean
method configure (line 76) | @Override
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/AuthorizationServerConfiguration.java
class AuthorizationServerConfiguration (line 43) | @Configuration
method configure (line 78) | @Override
method configure (line 95) | @Override
method configure (line 111) | @Override
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/JerseyConfiguration.java
class JerseyConfiguration (line 42) | @Configuration
method JerseyConfiguration (line 45) | public JerseyConfiguration() {
method scanPackages (line 53) | private void scanPackages(String scanPackage) {
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/ResourceServerConfiguration.java
class ResourceServerConfiguration (line 48) | @Configuration
method configure (line 58) | public void configure(HttpSecurity http) throws Exception {
method configure (line 70) | @Override
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/WebSecurityConfiguration.java
class WebSecurityConfiguration (line 15) | public class WebSecurityConfiguration extends WebSecurityConfigurerAdapt...
method configure (line 17) | @Override
method configure (line 22) | @Override
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/AccessDeniedExceptionMapper.java
class AccessDeniedExceptionMapper (line 19) | @Provider
method toResponse (line 27) | @Override
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/BaseExceptionMapper.java
class BaseExceptionMapper (line 34) | @Provider
method toResponse (line 39) | @Override
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/CodedMessage.java
class CodedMessage (line 33) | @JsonInclude(JsonInclude.Include.NON_NULL)
method CodedMessage (line 48) | public CodedMessage(Integer code, String message) {
method getCode (line 53) | public Integer getCode() {
method setCode (line 57) | public void setCode(Integer code) {
method getMessage (line 61) | public String getMessage() {
method setMessage (line 65) | public void setMessage(String message) {
method getData (line 69) | public Object getData() {
method setData (line 73) | public void setData(Object data) {
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/CommonResponse.java
class CommonResponse (line 41) | public abstract class CommonResponse {
method send (line 48) | public static Response send(Response.Status status, String message) {
method failure (line 56) | public static Response failure(String message) {
method success (line 63) | public static Response success(String message) {
method success (line 70) | public static Response success() {
method op (line 78) | public static Response op(Runnable executor) {
method op (line 86) | public static Response op(Runnable executor, Consumer<Exception> excep...
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/ViolationExceptionMapper.java
class ViolationExceptionMapper (line 37) | @Provider
method toResponse (line 42) | @Override
FILE: src/main/java/com/github/fenixsoft/bookstore/infrastructure/utility/Encryption.java
class Encryption (line 34) | @Named
method passwordEncoder (line 41) | @Bean
method encode (line 50) | public String encode(CharSequence rawPassword) {
FILE: src/main/java/com/github/fenixsoft/bookstore/resource/AccountResource.java
class AccountResource (line 46) | @Path("/accounts")
method getUser (line 59) | @GET
method createUser (line 69) | @POST
method updateUser (line 78) | @PUT
FILE: src/main/java/com/github/fenixsoft/bookstore/resource/AdvertisementResource.java
class AdvertisementResource (line 38) | @Path("/advertisements")
method getAllAdvertisements (line 46) | @GET
FILE: src/main/java/com/github/fenixsoft/bookstore/resource/PaymentResource.java
class PaymentResource (line 43) | @Path("/pay")
method updatePaymentState (line 54) | @PATCH
method updatePaymentStateAlias (line 67) | @GET
FILE: src/main/java/com/github/fenixsoft/bookstore/resource/ProductResource.java
class ProductResource (line 46) | @Path("/products")
method getAllProducts (line 58) | @GET
method getProduct (line 67) | @GET
method updateProduct (line 77) | @PUT
method createProduct (line 90) | @POST
method removeProduct (line 103) | @DELETE
method updateStockpile (line 117) | @PATCH
method queryStockpile (line 127) | @GET
FILE: src/main/java/com/github/fenixsoft/bookstore/resource/SettlementResource.java
class SettlementResource (line 43) | @Path("/settlements")
method executeSettlement (line 55) | @POST
FILE: src/main/resources/db/hsqldb/schema.sql
type account (line 9) | CREATE TABLE account
type account_user (line 20) | CREATE UNIQUE INDEX account_user ON account (username)
type account_telephone (line 21) | CREATE UNIQUE INDEX account_telephone ON account (telephone)
type account_email (line 22) | CREATE UNIQUE INDEX account_email ON account (email)
type wallet (line 24) | CREATE TABLE wallet
type product (line 33) | CREATE TABLE product
type product_title (line 43) | CREATE INDEX product_title ON product (title)
type stockpile (line 45) | CREATE TABLE stockpile
type specification (line 55) | CREATE TABLE specification
type advertisement (line 65) | CREATE TABLE advertisement
type payment (line 74) | CREATE TABLE payment
FILE: src/main/resources/db/mysql/schema.sql
type account (line 9) | CREATE TABLE IF NOT EXISTS account
type wallet (line 22) | CREATE TABLE IF NOT EXISTS wallet
type product (line 30) | CREATE TABLE IF NOT EXISTS product
type stockpile (line 42) | CREATE TABLE IF NOT EXISTS stockpile
type specification (line 51) | CREATE TABLE IF NOT EXISTS specification
type advertisement (line 60) | CREATE TABLE IF NOT EXISTS advertisement
type payment (line 68) | CREATE TABLE IF NOT EXISTS payment
FILE: src/main/resources/static/static/board/gitalk.min.js
function t (line 1) | function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{...
function r (line 1) | function r(e){return"[object Array]"===C.call(e)}
function o (line 1) | function o(e){return void 0===e}
function i (line 1) | function i(e){return null!==e&&!o(e)&&null!==e.constructor&&!o(e.constru...
function a (line 1) | function a(e){return"[object ArrayBuffer]"===C.call(e)}
function u (line 1) | function u(e){return"undefined"!=typeof FormData&&e instanceof FormData}
function s (line 1) | function s(e){return"undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?...
function c (line 1) | function c(e){return"string"==typeof e}
function l (line 1) | function l(e){return"number"==typeof e}
function f (line 1) | function f(e){return null!==e&&"object"==typeof e}
function p (line 1) | function p(e){return"[object Date]"===C.call(e)}
function d (line 1) | function d(e){return"[object File]"===C.call(e)}
function h (line 1) | function h(e){return"[object Blob]"===C.call(e)}
function m (line 1) | function m(e){return"[object Function]"===C.call(e)}
function v (line 1) | function v(e){return f(e)&&m(e.pipe)}
function y (line 1) | function y(e){return"undefined"!=typeof URLSearchParams&&e instanceof UR...
function g (line 1) | function g(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}
function b (line 1) | function b(){return("undefined"==typeof navigator||"ReactNative"!==navig...
function w (line 1) | function w(e,t){if(null!==e&&void 0!==e)if("object"!=typeof e&&(e=[e]),r...
function _ (line 1) | function _(){function e(e,n){"object"==typeof t[n]&&"object"==typeof e?t...
function x (line 1) | function x(){function e(e,n){"object"==typeof t[n]&&"object"==typeof e?t...
function S (line 1) | function S(e,t,n){return w(t,function(t,r){e[r]=n&&"function"==typeof t?...
function r (line 1) | function r(){return null}
function o (line 1) | function o(e){var t=e.nodeName,n=e.attributes;e.attributes={},t.defaultP...
function i (line 1) | function i(e,t){var n,r,o;if(t){for(o in t)if(n=W.test(o))break;if(n){r=...
function a (line 1) | function a(e,t,n){var r=t&&t._preactCompatRendered&&t._preactCompatRende...
function u (line 1) | function u(e,t,n,r){var o=G.h(J,{context:e.context},t),i=a(o,n);return r...
function s (line 1) | function s(e){var t=e._preactCompatRendered&&e._preactCompatRendered.bas...
function c (line 1) | function c(e){return h.bind(null,e)}
function l (line 1) | function l(e,t){for(var n=t||0;n<e.length;n++){var r=e[n];Array.isArray(...
function f (line 1) | function f(e){return"function"==typeof e&&!(e.prototype&&e.prototype.ren...
function p (line 1) | function p(e){return C({displayName:e.displayName||e.name,render:functio...
function d (line 1) | function d(e){var t=e[$];return t?!0===t?e:t:(t=p(e),Object.defineProper...
function h (line 1) | function h(){for(var e=[],t=arguments.length;t--;)e[t]=arguments[t];retu...
function m (line 1) | function m(e){e.preactCompatNormalized=!0,w(e),f(e.nodeName)&&(e.nodeNam...
function v (line 1) | function v(e,t){for(var n=[],r=arguments.length-2;r-- >0;)n[r]=arguments...
function y (line 1) | function y(e){return e&&(e instanceof Y||e.$$typeof===H)}
function g (line 1) | function g(e,t){return t._refProxies[e]||(t._refProxies[e]=function(n){t...
function b (line 1) | function b(e){var t=e.nodeName,n=e.attributes;if(n&&"string"==typeof t){...
function w (line 1) | function w(e){var t=e.attributes;if(t){var n=t.className||t.class;n&&(t....
function _ (line 1) | function _(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}
function x (line 1) | function x(e,t){for(var n in e)if(!(n in t))return!0;for(var r in t)if(e...
function S (line 1) | function S(e){return e&&e.base||e}
function E (line 1) | function E(){}
function C (line 1) | function C(e){function t(e,t){k(this),I.call(this,e,t,V),T.call(this,e,t...
function N (line 1) | function N(e){for(var t={},n=0;n<e.length;n++){var r=e[n];for(var o in r...
function O (line 1) | function O(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=P(t[n].concat(...
function k (line 1) | function k(e){for(var t in e){var n=e[t];"function"!=typeof n||n.__bound...
function M (line 1) | function M(e,t,n){if("string"==typeof t&&(t=e.constructor.prototype[t]),...
function P (line 1) | function P(e,t){return function(){for(var n,r=arguments,o=this,i=0;i<e.l...
function T (line 1) | function T(e,t){A.call(this,e,t),this.componentWillReceiveProps=P([A,thi...
function A (line 1) | function A(e,t){if(e){var n=e.children;if(n&&Array.isArray(n)&&1===n.len...
function j (line 1) | function j(e){Z=this}
function D (line 1) | function D(){Z===this&&(Z=null)}
function I (line 1) | function I(e,t,n){G.Component.call(this,e,t),this.state=this.getInitialS...
function L (line 1) | function L(e,t){I.call(this,e,t)}
function n (line 1) | function n(){throw new Error("setTimeout has not been defined")}
function r (line 1) | function r(){throw new Error("clearTimeout has not been defined")}
function o (line 1) | function o(e){if(l===setTimeout)return setTimeout(e,0);if((l===n||!l)&&s...
function i (line 1) | function i(e){if(f===clearTimeout)return clearTimeout(e);if((f===r||!f)&...
function a (line 1) | function a(){m&&d&&(m=!1,d.length?h=d.concat(h):v=-1,h.length&&u())}
function u (line 1) | function u(){if(!m){var e=o(a);m=!0;for(var t=h.length;t;){for(d=h,h=[];...
function s (line 1) | function s(e,t){this.fun=e,this.array=t}
function c (line 1) | function c(){}
function r (line 1) | function r(e,t){if(l(e))return new Date(e.getTime());if("string"!=typeof...
function o (line 1) | function o(e){var t,n={},r=e.split(h);if(m.test(r[0])?(n.date=null,t=r[0...
function i (line 1) | function i(e,t){var n,r=y[t],o=b[t];if(n=g.exec(e)||o.exec(e)){var i=n[1...
function a (line 1) | function a(e,t){if(null===t)return null;var n,r,o,i;if(0===e.length)retu...
function u (line 1) | function u(e){var t,n,r;if(t=C.exec(e))return(n=parseFloat(t[1].replace(...
function s (line 1) | function s(e){var t,n;return(t=M.exec(e))?0:(t=P.exec(e))?(n=60*parseInt...
function c (line 1) | function c(e,t,n){t=t||0,n=n||0;var r=new Date(0);r.setUTCFullYear(e,0,4...
function e (line 1) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function r (line 1) | function r(e){var t,n;this.promise=new e(function(e,r){if(void 0!==t||vo...
function r (line 1) | function r(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[...
function o (line 1) | function o(e,t){if(e===t)return!0;var n=!Array.isArray(e)||!Array.isArra...
function r (line 1) | function r(e){return e&&e.__esModule?e:{default:e}}
function r (line 1) | function r(e){return e&&e.__esModule?e:{default:e}}
function r (line 1) | function r(e){return e&&e.__esModule?e:{default:e}}
function r (line 1) | function r(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(...
function r (line 1) | function r(e,t){!o.isUndefined(e)&&o.isUndefined(e["Content-Type"])&&(e[...
function r (line 1) | function r(e){this.message=e}
function r (line 1) | function r(e){return e&&e.__esModule?e:{default:e}}
function e (line 1) | function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0...
function r (line 1) | function r(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t...
function o (line 1) | function o(e){return r(e)===h}
function e (line 17) | function e(e){return"string"==typeof e||"function"==typeof e||e===b||e==...
function n (line 17) | function n(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t...
function r (line 17) | function r(e){return K||(K=!0,L(!1,"The ReactIs.isAsyncMode() alias has ...
function o (line 17) | function o(e){return n(e)===C}
function i (line 17) | function i(e){return n(e)===S}
function a (line 17) | function a(e){return n(e)===x}
function u (line 17) | function u(e){return"object"==typeof e&&null!==e&&e.$$typeof===y}
function s (line 17) | function s(e){return n(e)===N}
function c (line 17) | function c(e){return n(e)===b}
function l (line 17) | function l(e){return n(e)===P}
function f (line 17) | function f(e){return n(e)===M}
function p (line 17) | function p(e){return n(e)===g}
function d (line 17) | function d(e){return n(e)===_}
function h (line 17) | function h(e){return n(e)===w}
function m (line 17) | function m(e){return n(e)===O}
function r (line 17) | function r(){return null}
function l (line 17) | function l(e){var t=e&&(k&&e[k]||e[M]);if("function"==typeof t)return t}
function f (line 17) | function f(e,t){return e===t?0!==e||1/e==1/t:e!==e&&t!==t}
function p (line 17) | function p(e){this.message=e,this.stack=""}
function d (line 17) | function d(e){function r(r,u,s,l,f,d,h){if(l=l||P,d=d||s,h!==a){if(n){va...
function h (line 17) | function h(e){function t(t,n,r,o,i,a){var u=t[n];if(E(u)!==e)return new ...
function m (line 17) | function m(e){function t(t,n,r,o,i){if("function"!=typeof e)return new p...
function v (line 17) | function v(e){function t(t,n,r,o,i){if(!(t[n]instanceof e)){var a=e.name...
function y (line 17) | function y(e){function n(t,n,r,o,i){for(var a=t[n],u=0;u<e.length;u++)if...
function g (line 17) | function g(e){function t(t,n,r,o,i){if("function"!=typeof e)return new p...
function b (line 17) | function b(e){function n(t,n,r,o,i){for(var u=0;u<e.length;u++){if(null=...
function w (line 17) | function w(e){function t(t,n,r,o,i){var u=t[n],s=E(u);if("object"!==s)re...
function _ (line 17) | function _(e){function t(t,n,r,o,u){var s=t[n],c=E(s);if("object"!==c)re...
function x (line 17) | function x(t){switch(typeof t){case"number":case"string":case"undefined"...
function S (line 17) | function S(e,t){return"symbol"===e||!!t&&("Symbol"===t["@@toStringTag"]|...
function E (line 17) | function E(e){var t=typeof e;return Array.isArray(e)?"array":e instanceo...
function C (line 17) | function C(e){if(void 0===e||null===e)return""+e;var t=E(e);if("object"=...
function N (line 17) | function N(e){var t=C(e);switch(t){case"array":case"object":return"an "+...
function O (line 17) | function O(e){return e.constructor&&e.constructor.name?e.constructor.nam...
function t (line 17) | function t(t,n,r,o,i){var a=t[n];if(!e(a)){return new p("Invalid "+o+" `...
function e (line 17) | function e(e,t,n,r,i){var a=e[t];if(!o.isValidElementType(a)){return new...
function e (line 17) | function e(e,t,n,r,o){return x(e[t])?null:new p("Invalid "+r+" `"+o+"` s...
function r (line 17) | function r(e){if(null===e||void 0===e)throw new TypeError("Object.assign...
function r (line 22) | function r(e,n,r,s,c){if("production"!==t.env.NODE_ENV)for(var l in e)if...
function r (line 22) | function r(){}
function o (line 22) | function o(){}
function e (line 22) | function e(e,t,n,r,o,a){if(a!==i){var u=new Error("Calling PropTypes val...
function t (line 22) | function t(){return e}
function t (line 22) | function t(){}
function n (line 22) | function n(e,n){var r,o,i,a,u=j;for(a=arguments.length;a-- >2;)A.push(ar...
function r (line 22) | function r(e,t){for(var n in t)e[n]=t[n];return e}
function o (line 22) | function o(e,t){return n(e.nodeName,r(r({},e.attributes),t),arguments.le...
function i (line 22) | function i(e){!e.__d&&(e.__d=!0)&&1==I.push(e)&&(T.debounceRendering||se...
function a (line 22) | function a(){var e,t=I;for(I=[];e=t.pop();)e.__d&&N(e)}
function u (line 22) | function u(e,t,n){return"string"==typeof t||"number"==typeof t?void 0!==...
function s (line 22) | function s(e,t){return e.__n===t||e.nodeName.toLowerCase()===t.toLowerCa...
function c (line 22) | function c(e){var t=r({},e.attributes);t.children=e.children;var n=e.nod...
function l (line 22) | function l(e,t){var n=t?document.createElementNS("http://www.w3.org/2000...
function f (line 22) | function f(e){e.parentNode&&e.parentNode.removeChild(e)}
function p (line 22) | function p(e,t,n,r,o){if("className"===t&&(t="class"),"key"===t);else if...
function d (line 22) | function d(e,t,n){try{e[t]=n}catch(e){}}
function h (line 22) | function h(e){return this.__l[e.type](T.event&&T.event(e)||e)}
function m (line 22) | function m(){for(var e;e=L.pop();)T.afterMount&&T.afterMount(e),e.compon...
function v (line 22) | function v(e,t,n,r,o,i){R++||(F=null!=o&&void 0!==o.ownerSVGElement,G=nu...
function y (line 22) | function y(e,t,n,r,o){var i=e,a=F;if(null==t&&(t=""),"string"==typeof t)...
function g (line 22) | function g(e,t,n,r,o){var i,a,s,c,l=e.childNodes,p=[],d={},h=0,m=0,v=l.l...
function b (line 22) | function b(e,t){var n=e._component;n?k(n):(null!=e.__preactattr_&&e.__pr...
function w (line 22) | function w(e){for(e=e.lastChild;e;){var t=e.previousSibling;b(e,!0),e=t}}
function _ (line 22) | function _(e,t,n){var r;for(r in n)t&&null!=t[r]||null==n[r]||p(e,r,n[r]...
function x (line 22) | function x(e){var t=e.constructor.name;(B[t]||(B[t]=[])).push(e)}
function S (line 22) | function S(e,t,n){var r,o=B[e.name];if(e.prototype&&e.prototype.render?(...
function E (line 22) | function E(e,t,n){return this.constructor(e,n)}
function C (line 22) | function C(e,t,n,r,o){e.__x||(e.__x=!0,(e.__r=t.ref)&&delete t.ref,(e.__...
function N (line 22) | function N(e,t,n,o){if(!e.__x){var i,a,u,s=e.props,l=e.state,f=e.context...
function O (line 22) | function O(e,t,n,r){for(var o=e&&e._component,i=o,a=e,u=o&&e._componentC...
function k (line 22) | function k(e){T.beforeUnmount&&T.beforeUnmount(e);var t=e.base;e.__x=!0,...
function M (line 22) | function M(e,t){this.__d=!0,this.context=t,this.props=e,this.state=this....
function P (line 22) | function P(e,t,n){return v(n,e,{},!1,t,!1)}
function e (line 29) | function e(e){var t=typeof e;return null!==e&&("object"===t||"function"=...
function o (line 29) | function o(e){return"function"==typeof e}
function i (line 29) | function i(e){W=e}
function a (line 29) | function a(e){V=e}
function u (line 29) | function u(){return void 0!==U?function(){U(c)}:s()}
function s (line 29) | function s(){var e=setTimeout;return function(){return e(c,1)}}
function c (line 29) | function c(){for(var e=0;e<$;e+=2){(0,Z[e])(Z[e+1]),Z[e]=void 0,Z[e+1]=v...
function l (line 29) | function l(e,t){var n=arguments,r=this,o=new this.constructor(p);void 0=...
function f (line 29) | function f(e){var t=this;if(e&&"object"==typeof e&&e.constructor===t)ret...
function p (line 29) | function p(){}
function d (line 29) | function d(){return new TypeError("You cannot resolve a promise with its...
function h (line 29) | function h(){return new TypeError("A promises callback cannot return tha...
function m (line 29) | function m(e){try{return e.then}catch(e){return oe.error=e,oe}}
function v (line 29) | function v(e,t,n,r){try{e.call(t,n,r)}catch(e){return e}}
function y (line 29) | function y(e,t,n){V(function(e){var r=!1,o=v(n,t,function(n){r||(r=!0,t!...
function g (line 29) | function g(e,t){t._state===ne?x(e,t._result):t._state===re?S(e,t._result...
function b (line 29) | function b(e,t,n){t.constructor===e.constructor&&n===l&&t.constructor.re...
function w (line 29) | function w(t,n){t===n?S(t,d()):e(n)?b(t,n,m(n)):x(t,n)}
function _ (line 29) | function _(e){e._onerror&&e._onerror(e._result),C(e)}
function x (line 29) | function x(e,t){e._state===te&&(e._result=t,e._state=ne,0!==e._subscribe...
function S (line 29) | function S(e,t){e._state===te&&(e._state=re,e._result=t,V(_,e))}
function E (line 29) | function E(e,t,n,r){var o=e._subscribers,i=o.length;e._onerror=null,o[i]...
function C (line 29) | function C(e){var t=e._subscribers,n=e._state;if(0!==t.length){for(var r...
function N (line 29) | function N(){this.error=null}
function O (line 29) | function O(e,t){try{return e(t)}catch(e){return ie.error=e,ie}}
function k (line 29) | function k(e,t,n,r){var i=o(n),a=void 0,u=void 0,s=void 0,c=void 0;if(i)...
function M (line 29) | function M(e,t){try{t(function(t){w(e,t)},function(t){S(e,t)})}catch(t){...
function P (line 29) | function P(){return ae++}
function T (line 29) | function T(e){e[ee]=ae++,e._state=void 0,e._result=void 0,e._subscribers...
function A (line 29) | function A(e,t){this._instanceConstructor=e,this.promise=new e(p),this.p...
function j (line 29) | function j(){return new Error("Array Methods must be provided an Array")}
function D (line 29) | function D(e){return new A(this,e).promise}
function I (line 29) | function I(e){var t=this;return new t(H(e)?function(n,r){for(var o=e.len...
function L (line 29) | function L(e){var t=this,n=new t(p);return S(n,e),n}
function R (line 29) | function R(){throw new TypeError("You must pass a resolver function as t...
function F (line 29) | function F(){throw new TypeError("Failed to construct 'Promise': Please ...
function G (line 29) | function G(e){this[ee]=P(),this._result=this._state=void 0,this._subscri...
function B (line 29) | function B(){var e=void 0;if(void 0!==r)e=r;else if("undefined"!=typeof ...
function r (line 29) | function r(e){return e&&e.__esModule?e:{default:e}}
function t (line 29) | function t(e){(0,f.default)(this,t);var n=(0,m.default)(this,(t.__proto_...
function r (line 29) | function r(e){return e&&e.__esModule?e:{default:e}}
function o (line 29) | function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a ...
function i (line 29) | function i(e,t){if(!e)throw new ReferenceError("this hasn't been initial...
function a (line 29) | function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("S...
function u (line 29) | function u(e){return e.key||""}
function e (line 29) | function e(e,t){var n=[],r=!0,o=!1,i=void 0;try{for(var a,u=e[Symbol.ite...
function e (line 29) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function t (line 29) | function t(){var e,n,r,a;o(this,t);for(var l=arguments.length,p=Array(l)...
function o (line 29) | function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a ...
function i (line 29) | function i(e,t){if(!e)throw new ReferenceError("this hasn't been initial...
function a (line 29) | function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("S...
function u (line 29) | function u(e){var t,n;return n=t=function(t){function n(){return o(this,...
function e (line 29) | function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.en...
function r (line 29) | function r(e){var t=!1;return function(){t||(console.warn(e),t=!0)}}
function r (line 29) | function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enume...
function o (line 29) | function o(e){var t=e.domNode,n=e.styles;Object.keys(n).forEach(function...
function i (line 29) | function i(){var e={transition:"transitionend","-o-transition":"oTransit...
function n (line 34) | function n(e){function t(t){var n=e.style.width;e.style.width="0px",e.of...
function r (line 34) | function r(e){var t=i.get(e);t&&t.destroy()}
function o (line 34) | function o(e){var t=i.get(e);t&&t.update()}
function r (line 34) | function r(e){return e&&e.__esModule?e:{default:e}}
function t (line 34) | function t(e){e=e||{},this.phrases={},this.extend(e.phrases||{}),this.cu...
function n (line 34) | function n(e){var t,n,r,o={};for(t in e)if(e.hasOwnProperty(t)){n=e[t];f...
function r (line 34) | function r(e){var t=/^\s+|\s+$/g;return e.replace(t,"")}
function o (line 34) | function o(e,t,n){var o,i,u;return null!=n&&e?(i=e.split(l),u=i[a(t,n)]|...
function i (line 34) | function i(e){var t=n(p);return t[e]||t.en}
function a (line 34) | function a(e,t){return f[i(e)](t)}
function u (line 34) | function u(e,t){for(var n in t)"_"!==n&&t.hasOwnProperty(n)&&(e=e.replac...
function s (line 34) | function s(t){e.console&&e.console.warn&&e.console.warn("WARNING: "+t)}
function c (line 34) | function c(e){var t={};for(var n in e)t[n]=e[n];return t}
function r (line 34) | function r(e){return e&&e.__esModule?e:{default:e}}
function e (line 34) | function e(e,t){var n=[],r=!0,o=!1,i=void 0;try{for(var a,s=(0,u.default...
function r (line 34) | function r(e){var t=new a(e),n=i(a.prototype.request,t);return o.extend(...
function r (line 34) | function r(e){this.defaults=e,this.interceptors={request:new a,response:...
function r (line 34) | function r(){this.handlers=[]}
function r (line 34) | function r(e){e.cancelToken&&e.cancelToken.throwIfRequested()}
function e (line 34) | function e(e){var t=e;return n&&(o.setAttribute("href",t),t=o.href),o.se...
function r (line 34) | function r(e){if("function"!=typeof e)throw new TypeError("executor must...
function r (line 34) | function r(e){return e&&e.__esModule?e:{default:e}}
function t (line 34) | function t(){return(0,u.default)(this,t),(0,f.default)(this,(t.__proto__...
function r (line 34) | function r(e){return n(o(e))}
function o (line 34) | function o(e){var t=i[e];if(!(t+1))throw new Error("Cannot find module '...
function r (line 34) | function r(e,t){return o(Date.now(),e,t)}
function r (line 34) | function r(e,t,n){var r=n||{},d=o(e,t),h=r.locale,m=s.distanceInWords.lo...
function r (line 34) | function r(e,t){var n=o(e),r=n.getTime(),i=o(t),a=i.getTime();return r>a...
function n (line 34) | function n(e){return e instanceof Date}
function r (line 34) | function r(e,t){var n=o(e,t)/1e3;return n>0?Math.floor(n):Math.ceil(n)}
function r (line 34) | function r(e,t){var n=o(e),r=o(t);return n.getTime()-r.getTime()}
function r (line 34) | function r(e,t){var n=o(e),r=o(t),u=a(n,r),s=Math.abs(i(n,r));return n.s...
function r (line 34) | function r(e,t){var n=o(e),r=o(t);return 12*(n.getFullYear()-r.getFullYe...
function r (line 34) | function r(e,t){var n=o(e),r=n.getTime(),i=o(t),a=i.getTime();return r<a...
function n (line 34) | function n(){function e(e,n,r){r=r||{};var o;return o="string"==typeof t...
function r (line 34) | function r(){var e=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep...
function o (line 34) | function o(e){var t=e%100;if(t>20||t<10)switch(t%10){case 1:return e+"st...
function n (line 34) | function n(e){var t=[];for(var n in e)e.hasOwnProperty(n)&&t.push(n);var...
function n (line 34) | function n(){function e(e,n,r){r=r||{};var o;return o="string"==typeof t...
function n (line 34) | function n(){function e(e,n,r){r=r||{};var o;return o="string"==typeof t...
function n (line 34) | function n(){function e(e,n,r){r=r||{};var o;return o="string"==typeof t...
function n (line 34) | function n(){function e(e,n,r){r=r||{};var o;return o="string"==typeof t...
function n (line 34) | function n(e,t){if(void 0!==e.one&&1===t)return e.one;var n=t%10,r=t%100...
function r (line 34) | function r(e){return function(t,r){return r.addSuffix?r.comparison>0?e.f...
function o (line 34) | function o(){function e(e,n,r){return r=r||{},t[e](n,r)}var t={lessThanX...
function r (line 34) | function r(e){var t=this,n=this.options,r=n.owner,o=n.repo,s=n.perPage,c...
FILE: src/main/resources/static/static/js/1.a33faf036923758c7965.js
function t (line 10) | function t(){throw new Error("Dynamic requires are not currently support...
function o (line 10) | function o(s,u){if(!n[s]){if(!r[s]){var f=t;if(!u&&f)return f(s,!0);if(a...
function o (line 10) | function o(t){this.mode=n.ALPHANUMERIC,this.data=t}
function n (line 10) | function n(){this.buffer=[],this.length=0}
function i (line 10) | function i(t){if(!t||t<1)throw new Error("BitMatrix size must be defined...
function o (line 10) | function o(t){this.mode=i.BYTE,this.data=n.from(t)}
function o (line 10) | function o(t){this.mode=n.KANJI,this.data=t}
function i (line 10) | function i(t,e,n){switch(t){case r.Patterns.PATTERN000:return(e+n)%2==0;...
function i (line 10) | function i(t){this.mode=n.NUMERIC,this.data=t.toString()}
function v (line 10) | function v(t,e,r){var n,i,o=t.size,a=p.getEncodedBits(e,r);for(n=0;n<15;...
function m (line 10) | function m(t,e,r){var o=new a;r.forEach(function(e){o.put(e.mode.bit,4),...
function w (line 10) | function w(t,e,r,n){var o;if(y(t))o=d.fromArray(t);else{if("string"!=typ...
function a (line 10) | function a(t){this.genPoly=void 0,this.degree=t,this.degree&&this.initia...
function l (line 10) | function l(t){return unescape(encodeURIComponent(t)).length}
function h (line 10) | function h(t,e,r){for(var n,i=[];null!==(n=t.exec(r));)i.push({data:n[0]...
function g (line 10) | function g(t){var e,r,i=h(u.NUMERIC,n.NUMERIC,t),o=h(u.ALPHANUMERIC,n.AL...
function p (line 10) | function p(t,e){switch(e){case n.NUMERIC:return i.getBitsLength(t);case ...
function A (line 10) | function A(t,e){var r,u=n.getBestModeForData(t);if((r=n.from(e,u))!==n.B...
function c (line 10) | function c(t,e){return a.getCharCountIndicator(t,e)+4}
function l (line 10) | function l(t,e){var r=0;return t.forEach(function(t){var n=c(t.mode,e);r...
function s (line 10) | function s(t,e,r,o,a){var s=[].slice.call(arguments,1),u=s.length,f="fun...
function i (line 10) | function i(t,e){var r=t.a/255,n=e+'="'+t.hex+'"';return r<1?n+" "+e+'-op...
function o (line 10) | function o(t,e,r){var n=t+e;return void 0!==r&&(n+=" "+r),n}
function n (line 10) | function n(t){if("number"==typeof t&&(t=t.toString()),"string"!=typeof t...
function o (line 10) | function o(t,e,r){return o.TYPED_ARRAY_SUPPORT||this instanceof o?"numbe...
function a (line 10) | function a(t){if(t>=i)throw new RangeError("Attempt to allocate Buffer l...
function s (line 10) | function s(t,e){var r;return o.TYPED_ARRAY_SUPPORT?(r=new Uint8Array(e))...
function u (line 10) | function u(t,e){var r=s(t,e<0?0:0|a(e));if(!o.TYPED_ARRAY_SUPPORT)for(va...
function f (line 10) | function f(t,e){for(var r=e.length<0?0:0|a(e.length),n=s(t,r),i=0;i<r;i+...
function c (line 10) | function c(t,e){var r;e=e||1/0;for(var n=t.length,i=null,o=[],a=0;a<n;++...
function l (line 10) | function l(t){if(o.isBuffer(t))return t.length;if("undefined"!=typeof Ar...
function f (line 10) | function f(t){var e=t.length;if(e%4>0)throw new Error("Invalid string. L...
function c (line 10) | function c(t,e,r){for(var i,o,a=[],s=e;s<r;s+=3)i=(t[s]<<16&16711680)+(t...
function s (line 10) | function s(t){if(t>a)throw new RangeError('The value "'+t+'" is invalid ...
function u (line 10) | function u(t,e,r){if("number"==typeof t){if("string"==typeof e)throw new...
function f (line 10) | function f(t,e,r){if("string"==typeof t)return function(t,e){if("string"...
function c (line 10) | function c(t){if("number"!=typeof t)throw new TypeError('"size" argument...
function l (line 10) | function l(t){return c(t),s(t<0?0:0|g(t))}
function h (line 10) | function h(t){for(var e=t.length<0?0:0|g(t.length),r=s(e),n=0;n<e;n+=1)r...
function g (line 10) | function g(t){if(t>=a)throw new RangeError("Attempt to allocate Buffer l...
function p (line 10) | function p(t,e){if(u.isBuffer(t))return t.length;if(ArrayBuffer.isView(t...
function A (line 10) | function A(t,e,r){var n=t[e];t[e]=t[r],t[r]=n}
function d (line 10) | function d(t,e,r,n,i){if(0===t.length)return-1;if("string"==typeof r?(n=...
function y (line 10) | function y(t,e,r,n,i){var o,a=1,s=t.length,u=e.length;if(void 0!==n&&("u...
function v (line 10) | function v(t,e,r,n){r=Number(r)||0;var i=t.length-r;n?(n=Number(n))>i&&(...
function m (line 10) | function m(t,e,r,n){return O(Q(e,t.length-r),t,r,n)}
function w (line 10) | function w(t,e,r,n){return O(function(t){for(var e=[],r=0;r<t.length;++r...
function b (line 10) | function b(t,e,r,n){return w(t,e,r,n)}
function k (line 10) | function k(t,e,r,n){return O(N(e),t,r,n)}
function B (line 10) | function B(t,e,r,n){return O(function(t,e){for(var r,n,i,o=[],a=0;a<t.le...
function S (line 10) | function S(t,e,r){return 0===e&&r===t.length?n.fromByteArray(t):n.fromBy...
function E (line 10) | function E(t,e,r){r=Math.min(t.length,r);for(var n=[],i=e;i<r;){var o,a,...
function I (line 10) | function I(t,e,r){var n="";r=Math.min(t.length,r);for(var i=e;i<r;++i)n+...
function J (line 10) | function J(t,e,r){var n="";r=Math.min(t.length,r);for(var i=e;i<r;++i)n+...
function R (line 10) | function R(t,e,r){var n=t.length;(!e||e<0)&&(e=0),(!r||r<0||r>n)&&(r=n);...
function U (line 10) | function U(t,e,r){for(var n=t.slice(e,r),i="",o=0;o<n.length;o+=2)i+=Str...
function x (line 10) | function x(t,e,r){if(t%1!=0||t<0)throw new RangeError("offset is not uin...
function T (line 10) | function T(t,e,r,n,i,o){if(!u.isBuffer(t))throw new TypeError('"buffer" ...
function L (line 10) | function L(t,e,r,n,i,o){if(r+n>t.length)throw new RangeError("Index out ...
function M (line 10) | function M(t,e,r,n,o){return e=+e,r>>>=0,o||L(t,0,r,4),i.write(t,e,r,n,2...
function D (line 10) | function D(t,e,r,n,o){return e=+e,r>>>=0,o||L(t,0,r,8),i.write(t,e,r,n,5...
function Q (line 10) | function Q(t,e){var r;e=e||1/0;for(var n=t.length,i=null,o=[],a=0;a<n;++...
function N (line 10) | function N(t){return n.toByteArray(function(t){if((t=(t=t.split("=")[0])...
function O (line 10) | function O(t,e,r,n){for(var i=0;i<n&&!(i+r>=e.length||i>=t.length);++i)e...
function F (line 10) | function F(t,e){return t instanceof e||null!=t&&null!=t.constructor&&nul...
function K (line 10) | function K(t){return t!=t}
FILE: src/main/resources/static/static/js/2.626ed94f3752555e21f0.js
function t (line 10) | function t(){throw new Error("Dynamic requires are not currently support...
function i (line 10) | function i(u,s){if(!n[u]){if(!r[u]){var f=t;if(!s&&f)return f(u,!0);if(a...
function i (line 10) | function i(t){this.mode=n.ALPHANUMERIC,this.data=t}
function n (line 10) | function n(){this.buffer=[],this.length=0}
function o (line 10) | function o(t){if(!t||t<1)throw new Error("BitMatrix size must be defined...
function i (line 10) | function i(t){this.mode=o.BYTE,this.data=n.from(t)}
function i (line 10) | function i(t){this.mode=n.KANJI,this.data=t}
function o (line 10) | function o(t,e,n){switch(t){case r.Patterns.PATTERN000:return(e+n)%2==0;...
function o (line 10) | function o(t){this.mode=n.NUMERIC,this.data=t.toString()}
function m (line 10) | function m(t,e,r){var n,o,i=t.size,a=g.getEncodedBits(e,r);for(n=0;n<15;...
function w (line 10) | function w(t,e,r){var i=new a;r.forEach(function(e){i.put(e.mode.bit,4),...
function b (line 10) | function b(t,e,r,n){var i;if(v(t))i=y.fromArray(t);else{if("string"!=typ...
function a (line 10) | function a(t){this.genPoly=void 0,this.degree=t,this.degree&&this.initia...
function c (line 10) | function c(t){return unescape(encodeURIComponent(t)).length}
function l (line 10) | function l(t,e,r){for(var n,o=[];null!==(n=t.exec(r));)o.push({data:n[0]...
function p (line 10) | function p(t){var e,r,o=l(s.NUMERIC,n.NUMERIC,t),i=l(s.ALPHANUMERIC,n.AL...
function g (line 10) | function g(t,e){switch(e){case n.NUMERIC:return o.getBitsLength(t);case ...
function d (line 10) | function d(t,e){var r,s=n.getBestModeForData(t);if((r=n.from(e,s))!==n.B...
function h (line 10) | function h(t,e){return a.getCharCountIndicator(t,e)+4}
function c (line 10) | function c(t,e){var r=0;return t.forEach(function(t){var n=h(t.mode,e);r...
function u (line 10) | function u(t,e,r,i,a){var u=[].slice.call(arguments,1),s=u.length,f="fun...
function o (line 10) | function o(t,e){var r=t.a/255,n=e+'="'+t.hex+'"';return r<1?n+" "+e+'-op...
function i (line 10) | function i(t,e,r){var n=t+e;return void 0!==r&&(n+=" "+r),n}
function n (line 10) | function n(t){if("number"==typeof t&&(t=t.toString()),"string"!=typeof t...
function i (line 10) | function i(t,e,r){return i.TYPED_ARRAY_SUPPORT||this instanceof i?"numbe...
function a (line 10) | function a(t){if(t>=o)throw new RangeError("Attempt to allocate Buffer l...
function u (line 10) | function u(t,e){var r;return i.TYPED_ARRAY_SUPPORT?(r=new Uint8Array(e))...
function s (line 10) | function s(t,e){var r=u(t,e<0?0:0|a(e));if(!i.TYPED_ARRAY_SUPPORT)for(va...
function f (line 10) | function f(t,e){for(var r=e.length<0?0:0|a(e.length),n=u(t,r),o=0;o<r;o+...
function h (line 10) | function h(t,e){var r;e=e||1/0;for(var n=t.length,o=null,i=[],a=0;a<n;++...
function c (line 10) | function c(t){if(i.isBuffer(t))return t.length;if("undefined"!=typeof Ar...
function f (line 10) | function f(t){var e=t.length;if(e%4>0)throw new Error("Invalid string. L...
function h (line 10) | function h(t,e,r){for(var o,i,a=[],u=e;u<r;u+=3)o=(t[u]<<16&16711680)+(t...
function u (line 10) | function u(t){if(t>a)throw new RangeError('The value "'+t+'" is invalid ...
function s (line 10) | function s(t,e,r){if("number"==typeof t){if("string"==typeof e)throw new...
function f (line 10) | function f(t,e,r){if("string"==typeof t)return function(t,e){if("string"...
function h (line 10) | function h(t){if("number"!=typeof t)throw new TypeError('"size" argument...
function c (line 10) | function c(t){return h(t),u(t<0?0:0|p(t))}
function l (line 10) | function l(t){for(var e=t.length<0?0:0|p(t.length),r=u(e),n=0;n<e;n+=1)r...
function p (line 10) | function p(t){if(t>=a)throw new RangeError("Attempt to allocate Buffer l...
function g (line 10) | function g(t,e){if(s.isBuffer(t))return t.length;if(ArrayBuffer.isView(t...
function d (line 10) | function d(t,e,r){var n=t[e];t[e]=t[r],t[r]=n}
function y (line 10) | function y(t,e,r,n,o){if(0===t.length)return-1;if("string"==typeof r?(n=...
function v (line 10) | function v(t,e,r,n,o){var i,a=1,u=t.length,s=e.length;if(void 0!==n&&("u...
function m (line 10) | function m(t,e,r,n){r=Number(r)||0;var o=t.length-r;n?(n=Number(n))>o&&(...
function w (line 10) | function w(t,e,r,n){return j(O(e,t.length-r),t,r,n)}
function b (line 10) | function b(t,e,r,n){return j(function(t){for(var e=[],r=0;r<t.length;++r...
function E (line 10) | function E(t,e,r,n){return b(t,e,r,n)}
function A (line 10) | function A(t,e,r,n){return j(D(e),t,r,n)}
function B (line 10) | function B(t,e,r,n){return j(function(t,e){for(var r,n,o,i=[],a=0;a<t.le...
function P (line 10) | function P(t,e,r){return 0===e&&r===t.length?n.fromByteArray(t):n.fromBy...
function T (line 10) | function T(t,e,r){r=Math.min(t.length,r);for(var n=[],o=e;o<r;){var i,a,...
function R (line 10) | function R(t,e,r){var n="";r=Math.min(t.length,r);for(var o=e;o<r;++o)n+...
function I (line 10) | function I(t,e,r){var n="";r=Math.min(t.length,r);for(var o=e;o<r;++o)n+...
function _ (line 10) | function _(t,e,r){var n=t.length;(!e||e<0)&&(e=0),(!r||r<0||r>n)&&(r=n);...
function M (line 10) | function M(t,e,r){for(var n=t.slice(e,r),o="",i=0;i<n.length;i+=2)o+=Str...
function x (line 10) | function x(t,e,r){if(t%1!=0||t<0)throw new RangeError("offset is not uin...
function S (line 10) | function S(t,e,r,n,o,i){if(!s.isBuffer(t))throw new TypeError('"buffer" ...
function U (line 10) | function U(t,e,r,n,o,i){if(r+n>t.length)throw new RangeError("Index out ...
function N (line 10) | function N(t,e,r,n,i){return e=+e,r>>>=0,i||U(t,0,r,4),o.write(t,e,r,n,2...
function L (line 10) | function L(t,e,r,n,i){return e=+e,r>>>=0,i||U(t,0,r,8),o.write(t,e,r,n,5...
function O (line 10) | function O(t,e){var r;e=e||1/0;for(var n=t.length,o=null,i=[],a=0;a<n;++...
function D (line 10) | function D(t){return n.toByteArray(function(t){if((t=(t=t.split("=")[0])...
function j (line 10) | function j(t,e,r,n){for(var o=0;o<n&&!(o+r>=e.length||o>=t.length);++o)e...
function Y (line 10) | function Y(t,e){return t instanceof e||null!=t&&null!=t.constructor&&nul...
function F (line 10) | function F(t){return t!=t}
FILE: src/main/resources/static/static/js/6.68562501db5734ef1531.js
function r (line 1) | function r(i){if(t[i])return t[i].exports;var n=t[i]={i:i,l:!1,exports:{...
function i (line 1) | function i(e,t){for(var r=[],i={},n=0;n<t.length;n++){var s=t[n],a=s[0],...
function v (line 1) | function v(e,t,r,n){l=r,u=n||{};var a=i(e,t);return f(a),function(t){for...
function f (line 1) | function f(e){for(var t=0;t<e.length;t++){var r=e[t],i=s[r.id];if(i){i.r...
function y (line 1) | function y(){var e=document.createElement("style");return e.type="text/c...
function b (line 1) | function b(e){var t,r,i=document.querySelector("style["+p+'~="'+e.id+'"]...
function _ (line 1) | function _(e,t,r,i){var n=r?"":i.css;if(e.styleSheet)e.styleSheet.cssTex...
FILE: src/main/resources/static/static/js/7.184a5e39cc0c624f6a6d.js
function r (line 1) | function r(i){if(t[i])return t[i].exports;var s=t[i]={i:i,l:!1,exports:{...
function i (line 1) | function i(e,t){for(var r=[],i={},s=0;s<t.length;s++){var a=t[s],n=a[0],...
function v (line 1) | function v(e,t,r,s){l=r,d=s||{};var n=i(e,t);return f(n),function(t){for...
function f (line 1) | function f(e){for(var t=0;t<e.length;t++){var r=e[t],i=a[r.id];if(i){i.r...
function y (line 1) | function y(){var e=document.createElement("style");return e.type="text/c...
function b (line 1) | function b(e){var t,r,i=document.querySelector("style["+p+'~="'+e.id+'"]...
function g (line 1) | function g(e,t,r,i){var s=r?"":i.css;if(e.styleSheet)e.styleSheet.cssTex...
FILE: src/main/resources/static/static/js/app.ea66dc0be78c3ed2ae63.js
function I (line 1) | function I(e,a){j()(e).forEach(function(t){var c=e[t];e[t]=function(){fo...
FILE: src/main/resources/static/static/js/manifest.0437a7f02d3154ee1abb.js
function o (line 1) | function o(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{...
function i (line 1) | function i(){a.onerror=a.onload=null,clearTimeout(f);var n=t[e];0!==n&&(...
FILE: src/main/resources/static/static/js/vendor.c2f13a2146485051ae24.js
function b (line 1) | function b(){if(!g){g=!0;var e=navigator.userAgent,t=/(?:MSIE.(\d+\.\d+)...
function e (line 1) | function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Ca...
function Context (line 1) | function Context(){}
function u (line 1) | function u(e){if(a("ed25519"===e,"only tested with ed25519 so far"),!(th...
function u (line 1) | function u(e,t,n){s.call(this),this._cache=new h,this._last=void 0,this....
function h (line 1) | function h(){this.cache=r.allocUnsafe(0)}
function d (line 1) | function d(e,t,n){var s=o[e.toLowerCase()];if(!s)throw new TypeError("in...
function i (line 6) | function i(e,t){0}
function r (line 6) | function r(e){return Object.prototype.toString.call(e).indexOf("Error")>-1}
function o (line 6) | function o(e,t){return t instanceof e||t&&(t.name===e.name||t._name===e....
function a (line 6) | function a(e,t){for(var n in t)e[n]=t[n];return e}
function l (line 6) | function l(e,t,n,i){var r=t.props=function(e,t){switch(typeof t){case"un...
function p (line 6) | function p(e){var t={};return(e=e.trim().replace(/^(\?|#|&)/,""))?(e.spl...
function m (line 6) | function m(e){var t=e?Object.keys(e).map(function(t){var n=e[t];if(void ...
function g (line 6) | function g(e,t,n,i){var r=i&&i.options.stringifyQuery,o=t.query||{};try{...
function b (line 6) | function b(e){if(Array.isArray(e))return e.map(b);if(e&&"object"==typeof...
function _ (line 6) | function _(e,t){var n=e.path,i=e.query;void 0===i&&(i={});var r=e.hash;r...
function w (line 6) | function w(e,t){return t===y?e===t:!!t&&(e.path&&t.path?e.path.replace(v...
function x (line 6) | function x(e,t){if(void 0===e&&(e={}),void 0===t&&(t={}),!e||!t)return e...
function k (line 6) | function k(e,t,n){var i=e.charAt(0);if("/"===i)return e;if("?"===i||"#"=...
function C (line 6) | function C(e){return e.replace(/\/\//g,"/")}
function P (line 6) | function P(e,t){for(var n,i=[],r=0,o=0,a="",s=t&&t.delimiter||"/";null!=...
function A (line 6) | function A(e){return encodeURI(e).replace(/[\/?#]/g,function(e){return"%...
function I (line 6) | function I(e){for(var t=new Array(e.length),n=0;n<e.length;n++)"object"=...
function j (line 6) | function j(e){return e.replace(/([.+*?=^!:${}()[\]|\/\\])/g,"\\$1")}
function N (line 6) | function N(e){return e.replace(/([=!:$\/()])/g,"\\$1")}
function L (line 6) | function L(e,t){return e.keys=t,e}
function B (line 6) | function B(e){return e.sensitive?"":"i"}
function R (line 6) | function R(e,t,n){S(t)||(n=t||n,t=[]);for(var i=(n=n||{}).strict,r=!1!==...
function F (line 6) | function F(e,t,n){return S(t)||(n=t||n,t=[]),n=n||{},e instanceof RegExp...
function V (line 6) | function V(e,t,n){t=t||{};try{var i=z[e]||(z[e]=E.compile(e));return t.p...
function H (line 6) | function H(e,t,n,i){var r="string"==typeof e?{path:e}:e;if(r._normalized...
function G (line 6) | function G(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey||e.default...
function X (line 6) | function X(e){if(!X.installed||q!==e){X.installed=!0,q=e;var t=function(...
function Q (line 6) | function Q(e,t,n,i){var r=t||[],o=n||Object.create(null),a=i||Object.cre...
function Z (line 6) | function Z(e,t){var n=Q(e),i=n.pathList,r=n.pathMap,o=n.nameMap;function...
function ee (line 6) | function ee(e,t,n){var i=t.match(e);if(!i)return!1;if(!n)return!0;for(va...
function ne (line 6) | function ne(){return te.now().toFixed(3)}
function re (line 6) | function re(){return ie}
function oe (line 6) | function oe(e){return ie=e}
function se (line 6) | function se(){var e=window.location.protocol+"//"+window.location.host,t...
function le (line 6) | function le(e,t,n,i){if(e.app){var r=e.options.scrollBehavior;r&&e.app.$...
function ce (line 6) | function ce(){var e=re();e&&(ae[e]={x:window.pageXOffset,y:window.pageYO...
function ue (line 6) | function ue(e){return de(e.x)||de(e.y)}
function he (line 6) | function he(e){return{x:de(e.x)?e.x:window.pageXOffset,y:de(e.y)?e.y:win...
function de (line 6) | function de(e){return"number"==typeof e}
function pe (line 6) | function pe(e,t){var n,i="object"==typeof e;if(i&&"string"==typeof e.sel...
function ge (line 6) | function ge(e,t){ce();var n=window.history;try{if(t){var i=a({},n.state)...
function be (line 6) | function be(e){ge(e,!0)}
function ye (line 6) | function ye(e,t,n){var i=function(r){r>=e.length?n():e[r]?t(e[r],functio...
function _e (line 6) | function _e(e){return function(t,n,i){var o=!1,a=0,s=null;we(e,function(...
function we (line 6) | function we(e,t){return xe(e.map(function(e){return Object.keys(e.compon...
function xe (line 6) | function xe(e){return Array.prototype.concat.apply([],e)}
function Ce (line 6) | function Ce(e){var t=!1;return function(){for(var n=[],i=arguments.lengt...
function t (line 6) | function t(t){e.call(this),this.name=this._name="NavigationDuplicated",t...
function Oe (line 6) | function Oe(e,t,n,i){var r=we(e,function(e,i,r,o){var a=function(e,t){"f...
function Me (line 6) | function Me(e,t){if(t)return function(){return e.apply(t,arguments)}}
function t (line 6) | function t(t,n){var i=this;e.call(this,t,n);var r=t.options.scrollBehavi...
function Te (line 6) | function Te(e){var t=decodeURI(window.location.pathname);return e&&0===t...
function t (line 6) | function t(t,n,i){e.call(this,t,n),i&&function(e){var t=Te(e);if(!/^\/#/...
function Pe (line 6) | function Pe(){var e=Ae();return"/"===e.charAt(0)||(Ne("/"+e),!1)}
function Ae (line 6) | function Ae(){var e=window.location.href,t=e.indexOf("#");if(t<0)return"...
function Ie (line 6) | function Ie(e){var t=window.location.href,n=t.indexOf("#");return(n>=0?t...
function je (line 6) | function je(e){ve?ge(Ie(e)):window.location.hash=e}
function Ne (line 6) | function Ne(e){ve?be(Ie(e)):window.location.replace(Ie(e))}
function t (line 6) | function t(t,n){e.call(this,t,n),this.stack=[],this.index=-1}
function Fe (line 6) | function Fe(e,t){return e.push(t),function(){var n=e.indexOf(t);n>-1&&e....
function u (line 6) | function u(e,t,n,a){o.call(this);var l=r.alloc(4,0);this._cipher=new i.A...
function n (line 6) | function n(e,t){if(!e)throw new Error(t||"Assertion failed")}
function n (line 6) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function i (line 6) | function i(e,t,n,i,r,o,a,s){var l,c="function"==typeof e?e.options:e;if(...
function r (line 6) | function r(e){this._reporterState={obj:null,path:[],options:e||{},errors...
function o (line 6) | function o(e,t){this.path=e,this.rethrow(t)}
function o (line 6) | function o(e,t){return 55296==(64512&e.charCodeAt(t))&&(!(t<0||t+1>=e.le...
function a (line 6) | function a(e){return(e>>>24|e>>>8&65280|e<<8&16711680|(255&e)<<24)>>>0}
function s (line 6) | function s(e){return 1===e.length?"0"+e:e}
function l (line 6) | function l(e){return 7===e.length?"0"+e:6===e.length?"00"+e:5===e.length...
function l (line 6) | function l(e){this.twisted=1!=(0|e.a),this.mOneA=this.twisted&&-1==(0|e....
function c (line 6) | function c(e,t,n,i,o){a.BasePoint.call(this,e,"projective"),null===t&&nu...
function i (line 6) | function i(e,n){if("string"!=typeof e&&!t.isBuffer(e))throw new TypeErro...
function p (line 6) | function p(e,t){if(!e||!t)return!1;if(-1!==t.indexOf(" "))throw new Erro...
function m (line 6) | function m(){if(null!==p)return p;var e=[];e[0]=2;for(var t=1,n=3;n<1048...
function v (line 6) | function v(e){for(var t=m(),n=0;n<t.length;n++)if(0===e.modn(t[n]))retur...
function g (line 6) | function g(e){var t=r.mont(e);return 0===l.toRed(t).redPow(e.subn(1)).fr...
function b (line 6) | function b(e,t){if(e<16)return new r(2===t||5===t?[140,123]:[140,39]);va...
function o (line 6) | function o(e,t,n){if(!(this instanceof o))return new o(e,t,n);this.Hash=...
function r (line 6) | function r(e,t,n){return e&t^~e&n}
function o (line 6) | function o(e,t,n){return e&t^e&n^t&n}
function a (line 6) | function a(e,t,n){return e^t^n}
function r (line 6) | function r(){throw new Error("secure random number generation not suppor...
function h (line 6) | function h(e,t){if("number"!=typeof e||e!=e)throw new TypeError("offset ...
function d (line 6) | function d(e,t,n){if("number"!=typeof e||e!=e)throw new TypeError("size ...
function f (line 6) | function f(e,t,n,r){if(i.browser){var o=e.buffer,s=new Uint8Array(o,t,n)...
function a (line 6) | function a(e,t,n,a){o.call(this),this._cipher=new i.AES(t),this._prev=r....
function i (line 12) | function i(e){return void 0===e||null===e}
function r (line 12) | function r(e){return void 0!==e&&null!==e}
function o (line 12) | function o(e){return!0===e}
function a (line 12) | function a(e){return"string"==typeof e||"number"==typeof e||"symbol"==ty...
function s (line 12) | function s(e){return null!==e&&"object"==typeof e}
function c (line 12) | function c(e){return"[object Object]"===l.call(e)}
function u (line 12) | function u(e){return"[object RegExp]"===l.call(e)}
function h (line 12) | function h(e){var t=parseFloat(String(e));return t>=0&&Math.floor(t)===t...
function d (line 12) | function d(e){return r(e)&&"function"==typeof e.then&&"function"==typeof...
function f (line 12) | function f(e){return null==e?"":Array.isArray(e)||c(e)&&e.toString===l?J...
function p (line 12) | function p(e){var t=parseFloat(e);return isNaN(t)?e:t}
function m (line 12) | function m(e,t){for(var n=Object.create(null),i=e.split(","),r=0;r<i.len...
function b (line 12) | function b(e,t){if(e.length){var n=e.indexOf(t);if(n>-1)return e.splice(...
function _ (line 12) | function _(e,t){return y.call(e,t)}
function w (line 12) | function w(e){var t=Object.create(null);return function(n){return t[n]||...
function n (line 12) | function n(n){var i=arguments.length;return i?i>1?e.apply(t,arguments):e...
function M (line 12) | function M(e,t){t=t||0;for(var n=e.length-t,i=new Array(n);n--;)i[n]=e[n...
function D (line 12) | function D(e,t){for(var n in t)e[n]=t[n];return e}
function T (line 12) | function T(e){for(var t={},n=0;n<e.length;n++)e[n]&&D(t,e[n]);return t}
function $ (line 12) | function $(e,t,n){}
function I (line 12) | function I(e,t){if(e===t)return!0;var n=s(e),i=s(t);if(!n||!i)return!n&&...
function j (line 12) | function j(e,t){for(var n=0;n<e.length;n++)if(I(e[n],t))return n;return-1}
function N (line 12) | function N(e){var t=!1;return function(){t||(t=!0,e.apply(this,arguments...
function V (line 12) | function V(e){var t=(e+"").charCodeAt(0);return 36===t||95===t}
function H (line 12) | function H(e,t,n,i){Object.defineProperty(e,t,{value:n,enumerable:!!i,wr...
function se (line 12) | function se(e){return"function"==typeof e&&/native code/.test(e.toString...
function e (line 12) | function e(){this.set=Object.create(null)}
function pe (line 12) | function pe(e){fe.push(e),de.target=e}
function me (line 12) | function me(){fe.pop(),de.target=fe[fe.length-1]}
function ye (line 12) | function ye(e){return new ve(void 0,void 0,void 0,String(e))}
function _e (line 12) | function _e(e){var t=new ve(e.tag,e.data,e.children&&e.children.slice(),...
function Se (line 12) | function Se(e){Ce=e}
function Oe (line 12) | function Oe(e,t){var n;if(s(e)&&!(e instanceof ve))return _(e,"__ob__")&...
function Me (line 12) | function Me(e,t,n,i,r){var o=new de,a=Object.getOwnPropertyDescriptor(e,...
function De (line 12) | function De(e,t,n){if(Array.isArray(e)&&h(t))return e.length=Math.max(e....
function Te (line 12) | function Te(e,t){if(Array.isArray(e)&&h(t))e.splice(t,1);else{var n=e.__...
function Pe (line 12) | function Pe(e,t){if(!t)return e;for(var n,i,r,o=ce?Reflect.ownKeys(t):Ob...
function Ae (line 12) | function Ae(e,t,n){return n?function(){var i="function"==typeof t?t.call...
function Ie (line 12) | function Ie(e,t){var n=t?e?e.concat(t):Array.isArray(t)?t:[t]:e;return n...
function je (line 12) | function je(e,t,n,i){var r=Object.create(e||null);return t?D(r,t):r}
function Le (line 12) | function Le(e,t,n){if("function"==typeof t&&(t=t.options),function(e,t){...
function Be (line 12) | function Be(e,t,n,i){if("string"==typeof n){var r=e[t];if(_(r,n))return ...
function Re (line 12) | function Re(e,t,n,i){var r=t[e],o=!_(n,e),a=n[e],s=Ve(Boolean,r.type);if...
function Fe (line 12) | function Fe(e){var t=e&&e.toString().match(/^\s*function (\w+)/);return ...
function ze (line 12) | function ze(e,t){return Fe(e)===Fe(t)}
function Ve (line 12) | function Ve(e,t){if(!Array.isArray(t))return ze(t,e)?0:-1;for(var n=0,i=...
function He (line 12) | function He(e,t,n){pe();try{if(t)for(var i=t;i=i.$parent;){var r=i.$opti...
function qe (line 12) | function qe(e,t,n,i,r){var o;try{(o=n?e.apply(t,n):e.call(t))&&!o._isVue...
function We (line 12) | function We(e,t,n){if(F.errorHandler)try{return F.errorHandler.call(null...
function Ue (line 12) | function Ue(e,t,n){if(!K&&!Y||"undefined"==typeof console)throw e;consol...
function Je (line 12) | function Je(){Xe=!1;var e=Ge.slice(0);Ge.length=0;for(var t=0;t<e.length...
function nt (line 12) | function nt(e,t){var n;if(Ge.push(function(){if(e)try{e.call(t)}catch(e)...
function rt (line 12) | function rt(e){!function e(t,n){var i,r;var o=Array.isArray(t);if(!o&&!s...
function at (line 12) | function at(e,t){function n(){var e=arguments,i=n.fns;if(!Array.isArray(...
function st (line 12) | function st(e,t,n,r,a,s){var l,c,u,h;for(l in e)c=e[l],u=t[l],h=ot(l),i(...
function lt (line 12) | function lt(e,t,n){var a;e instanceof ve&&(e=e.data.hook||(e.data.hook={...
function ct (line 12) | function ct(e,t,n,i,o){if(r(t)){if(_(t,n))return e[n]=t[n],o||delete t[n...
function ut (line 12) | function ut(e){return a(e)?[ye(e)]:Array.isArray(e)?function e(t,n){var ...
function ht (line 12) | function ht(e){return r(e)&&r(e.text)&&!1===e.isComment}
function dt (line 12) | function dt(e,t){if(e){for(var n=Object.create(null),i=ce?Reflect.ownKey...
function ft (line 12) | function ft(e,t){if(!e||!e.length)return{};for(var n={},i=0,r=e.length;i...
function pt (line 12) | function pt(e){return e.isComment&&!e.asyncFactory||" "===e.text}
function mt (line 12) | function mt(e,t,i){var r,o=Object.keys(t).length>0,a=e?!!e.$stable:!o,s=...
function vt (line 12) | function vt(e,t,n){var i=function(){var e=arguments.length?n.apply(null,...
function gt (line 12) | function gt(e,t){return function(){return e[t]}}
function bt (line 12) | function bt(e,t){var n,i,o,a,l;if(Array.isArray(e)||"string"==typeof e)f...
function yt (line 12) | function yt(e,t,n,i){var r,o=this.$scopedSlots[e];o?(n=n||{},i&&(n=D(D({...
function _t (line 12) | function _t(e){return Be(this.$options,"filters",e)||A}
function wt (line 12) | function wt(e,t){return Array.isArray(e)?-1===e.indexOf(t):e!==t}
function xt (line 12) | function xt(e,t,n,i,r){var o=F.keyCodes[t]||n;return r&&i&&!F.keyCodes[t...
function kt (line 12) | function kt(e,t,n,i,r){if(n)if(s(n)){var o;Array.isArray(n)&&(n=T(n));va...
function Ct (line 12) | function Ct(e,t){var n=this._staticTrees||(this._staticTrees=[]),i=n[e];...
function St (line 12) | function St(e,t,n){return Et(e,"__once__"+t+(n?"_"+n:""),!0),e}
function Et (line 12) | function Et(e,t,n){if(Array.isArray(e))for(var i=0;i<e.length;i++)e[i]&&...
function Ot (line 12) | function Ot(e,t,n){e.isStatic=!0,e.key=t,e.isOnce=n}
function Mt (line 12) | function Mt(e,t){if(t)if(c(t)){var n=e.on=e.on?D({},e.on):{};for(var i i...
function Dt (line 12) | function Dt(e,t,n,i){t=t||{$stable:!n};for(var r=0;r<e.length;r++){var o...
function Tt (line 12) | function Tt(e,t){for(var n=0;n<t.length;n+=2){var i=t[n];"string"==typeo...
function $t (line 12) | function $t(e,t){return"string"==typeof e?t+e:e}
function Pt (line 12) | function Pt(e){e._o=St,e._n=p,e._s=f,e._l=bt,e._t=yt,e._q=I,e._i=j,e._m=...
function At (line 12) | function At(e,t,i,r,a){var s,l=this,c=a.options;_(r,"_uid")?(s=Object.cr...
function It (line 12) | function It(e,t,n,i,r){var o=_e(e);return o.fnContext=n,o.fnOptions=i,t....
function jt (line 12) | function jt(e,t){for(var n in t)e[k(n)]=t[n]}
function Bt (line 12) | function Bt(e,t,a,l,c){if(!i(e)){var u=a.$options._base;if(s(e)&&(e=u.ex...
function Rt (line 12) | function Rt(e,t){var n=function(n,i){e(n,i),t(n,i)};return n._merged=!0,n}
function Vt (line 12) | function Vt(e,t,n,l,c,u){return(Array.isArray(n)||a(n))&&(c=l,l=n,n=void...
function Wt (line 12) | function Wt(e,t){return(e.__esModule||ce&&"Module"===e[Symbol.toStringTa...
function Ut (line 12) | function Ut(e){return e.isComment&&e.asyncFactory}
function Kt (line 12) | function Kt(e){if(Array.isArray(e))for(var t=0;t<e.length;t++){var n=e[t...
function Yt (line 12) | function Yt(e,t){Ht.$on(e,t)}
function Gt (line 12) | function Gt(e,t){Ht.$off(e,t)}
function Xt (line 12) | function Xt(e,t){var n=Ht;return function i(){null!==t.apply(null,argume...
function Jt (line 12) | function Jt(e,t,n){Ht=e,st(t,n||{},Yt,Gt,Xt,e),Ht=void 0}
function Zt (line 12) | function Zt(e){var t=Qt;return Qt=e,function(){Qt=t}}
function en (line 12) | function en(e){for(;e&&(e=e.$parent);)if(e._inactive)return!0;return!1}
function tn (line 12) | function tn(e,t){if(t){if(e._directInactive=!1,en(e))return}else if(e._d...
function nn (line 12) | function nn(e,t){pe();var n=e.$options[t],i=t+" hook";if(n)for(var r=0,o...
function fn (line 12) | function fn(){var e,t;for(un=hn(),ln=!0,rn.sort(function(e,t){return e.i...
function gn (line 12) | function gn(e,t,n){vn.get=function(){return this[t][n]},vn.set=function(...
function bn (line 12) | function bn(e){e._watchers=[];var t=e.$options;t.props&&function(e,t){va...
function _n (line 12) | function _n(e,t,n){var i=!oe();"function"==typeof n?(vn.get=i?wn(t):xn(n...
function wn (line 12) | function wn(e){return function(){var t=this._computedWatchers&&this._com...
function xn (line 12) | function xn(e){return function(){return e.call(this,this)}}
function kn (line 12) | function kn(e,t,n,i){return c(n)&&(i=n,n=n.handler),"string"==typeof n&&...
function Sn (line 12) | function Sn(e){var t=e.options;if(e.super){var n=Sn(e.super);if(n!==e.su...
function En (line 12) | function En(e){this._init(e)}
function On (line 12) | function On(e){e.cid=0;var t=1;e.extend=function(e){e=e||{};var n=this,i...
function Mn (line 12) | function Mn(e){return e&&(e.Ctor.options.name||e.tag)}
function Dn (line 12) | function Dn(e,t){return Array.isArray(e)?e.indexOf(t)>-1:"string"==typeo...
function Tn (line 12) | function Tn(e,t){var n=e.cache,i=e.keys,r=e._vnode;for(var o in n){var a...
function $n (line 12) | function $n(e,t,n,i){var r=e[t];!r||i&&r.tag===i.tag||r.componentInstanc...
function i (line 12) | function i(){n.$off(e,i),t.apply(n,arguments)}
function Wn (line 12) | function Wn(e){for(var t=e.data,n=e,i=e;r(i.componentInstance);)(i=i.com...
function Un (line 12) | function Un(e,t){return{staticClass:Kn(e.staticClass,t.staticClass),clas...
function Kn (line 12) | function Kn(e,t){return e?t?e+" "+t:e:t||""}
function Yn (line 12) | function Yn(e){return Array.isArray(e)?function(e){for(var t,n="",i=0,o=...
function Zn (line 12) | function Zn(e){return Jn(e)?"svg":"math"===e?"math":void 0}
function ni (line 12) | function ni(e){if("string"==typeof e){var t=document.querySelector(e);re...
function oi (line 12) | function oi(e,t){var n=e.data.ref;if(r(n)){var i=e.context,o=e.component...
function li (line 12) | function li(e,t){return e.key===t.key&&(e.tag===t.tag&&e.isComment===t.i...
function ci (line 12) | function ci(e,t,n){var i,o,a={};for(i=t;i<=n;++i)r(o=e[i].key)&&(a[o]=i)...
function hi (line 12) | function hi(e,t){(e.data.directives||t.data.directives)&&function(e,t){v...
function fi (line 12) | function fi(e,t){var n,i,r=Object.create(null);if(!e)return r;for(n=0;n<...
function pi (line 12) | function pi(e){return e.rawName||e.name+"."+Object.keys(e.modifiers||{})...
function mi (line 12) | function mi(e,t,n,i,r){var o=e.def&&e.def[t];if(o)try{o(n.elm,e,n,i,r)}c...
function gi (line 12) | function gi(e,t){var n=t.componentOptions;if(!(r(n)&&!1===n.Ctor.options...
function bi (line 12) | function bi(e,t,n){e.tagName.indexOf("-")>-1?yi(e,t,n):Fn(t)?qn(n)?e.rem...
function yi (line 12) | function yi(e,t,n){if(qn(n))e.removeAttribute(t);else{if(J&&!Q&&"TEXTARE...
function wi (line 12) | function wi(e,t){var n=t.elm,o=t.data,a=e.data;if(!(i(o.staticClass)&&i(...
function Ti (line 12) | function Ti(e){var t,n,i,r,o,a=!1,s=!1,l=!1,c=!1,u=0,h=0,d=0,f=0;for(i=0...
function $i (line 12) | function $i(e,t){var n=t.indexOf("(");if(n<0)return'_f("'+t+'")('+e+")";...
function Pi (line 12) | function Pi(e,t){console.error("[Vue compiler]: "+e)}
function Ai (line 12) | function Ai(e,t){return e?e.map(function(e){return e[t]}).filter(functio...
function Ii (line 12) | function Ii(e,t,n,i,r){(e.props||(e.props=[])).push(qi({name:t,value:n,d...
function ji (line 12) | function ji(e,t,n,i,r){(r?e.dynamicAttrs||(e.dynamicAttrs=[]):e.attrs||(...
function Ni (line 12) | function Ni(e,t,n,i){e.attrsMap[t]=n,e.attrsList.push(qi({name:t,value:n...
function Li (line 12) | function Li(e,t,n,i,r,o,a,s){(e.directives||(e.directives=[])).push(qi({...
function Bi (line 12) | function Bi(e,t,n){return n?"_p("+t+',"'+e+'")':e+t}
function Ri (line 12) | function Ri(e,t,i,r,o,a,s,l){var c;(r=r||n).right?l?t="("+t+")==='click'...
function Fi (line 12) | function Fi(e,t){return e.rawAttrsMap[":"+t]||e.rawAttrsMap["v-bind:"+t]...
function zi (line 12) | function zi(e,t,n){var i=Vi(e,":"+t)||Vi(e,"v-bind:"+t);if(null!=i)retur...
function Vi (line 12) | function Vi(e,t,n){var i;if(null!=(i=e.attrsMap[t]))for(var r=e.attrsLis...
function Hi (line 12) | function Hi(e,t){for(var n=e.attrsList,i=0,r=n.length;i<r;i++){var o=n[i...
function qi (line 12) | function qi(e,t){return t&&(null!=t.start&&(e.start=t.start),null!=t.end...
function Wi (line 12) | function Wi(e,t,n){var i=n||{},r=i.number,o="$$v";i.trim&&(o="(typeof $$...
function Ui (line 12) | function Ui(e,t){var n=function(e){if(e=e.trim(),xi=e.length,e.indexOf("...
function Ki (line 12) | function Ki(){return ki.charCodeAt(++Si)}
function Yi (line 12) | function Yi(){return Si>=xi}
function Gi (line 12) | function Gi(e){return 34===e||39===e}
function Xi (line 12) | function Xi(e){var t=1;for(Ei=Si;!Yi();)if(Gi(e=Ki()))Ji(e);else if(91==...
function Ji (line 12) | function Ji(e){for(var t=e;!Yi()&&(e=Ki())!==t;);}
function tr (line 12) | function tr(e,t,n){var i=Qi;return function r(){null!==t.apply(null,argu...
function ir (line 12) | function ir(e,t,n,i){if(nr){var r=un,o=t;t=o._wrapper=function(e){if(e.t...
function rr (line 12) | function rr(e,t,n,i){(i||Qi).removeEventListener(e,t._wrapper||t,n)}
function or (line 12) | function or(e,t){if(!i(e.data.on)||!i(t.data.on)){var n=t.data.on||{},o=...
function lr (line 12) | function lr(e,t){if(!i(e.data.domProps)||!i(t.data.domProps)){var n,o,a=...
function cr (line 12) | function cr(e,t){return!e.composing&&("OPTION"===e.tagName||function(e,t...
function dr (line 12) | function dr(e){var t=fr(e.style);return e.staticStyle?D(e.staticStyle,t):t}
function fr (line 12) | function fr(e){return Array.isArray(e)?T(e):"string"==typeof e?hr(e):e}
function _r (line 12) | function _r(e,t){var n=t.data,o=e.data;if(!(i(n.staticStyle)&&i(n.style)...
function kr (line 12) | function kr(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.s...
function Cr (line 12) | function Cr(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.s...
function Sr (line 12) | function Sr(e){if(e){if("object"==typeof e){var t={};return!1!==e.css&&D...
function jr (line 12) | function jr(e){Ir(function(){Ir(e)})}
function Nr (line 12) | function Nr(e,t){var n=e._transitionClasses||(e._transitionClasses=[]);n...
function Lr (line 12) | function Lr(e,t){e._transitionClasses&&b(e._transitionClasses,t),Cr(e,t)}
function Br (line 12) | function Br(e,t,n){var i=Fr(e,t),r=i.type,o=i.timeout,a=i.propCount;if(!...
function Fr (line 12) | function Fr(e,t){var n,i=window.getComputedStyle(e),r=(i[Tr+"Delay"]||""...
function zr (line 12) | function zr(e,t){for(;e.length<t.length;)e=e.concat(e);return Math.max.a...
function Vr (line 12) | function Vr(e){return 1e3*Number(e.slice(0,-1).replace(",","."))}
function Hr (line 12) | function Hr(e,t){var n=e.elm;r(n._leaveCb)&&(n._leaveCb.cancelled=!0,n._...
function qr (line 12) | function qr(e,t){var n=e.elm;r(n._enterCb)&&(n._enterCb.cancelled=!0,n._...
function Wr (line 12) | function Wr(e){return"number"==typeof e&&!isNaN(e)}
function Ur (line 12) | function Ur(e){if(i(e))return!1;var t=e.fns;return r(t)?Ur(Array.isArray...
function Kr (line 12) | function Kr(e,t){!0!==t.data.show&&Hr(t)}
function u (line 12) | function u(e){var t=c.parentNode(e);r(t)&&c.removeChild(t,e)}
function h (line 12) | function h(e,t,n,i,a,l,u){if(r(e.elm)&&r(l)&&(e=l[u]=_e(e)),e.isRootInse...
function d (line 12) | function d(e,t){r(e.data.pendingInsert)&&(t.push.apply(t,e.data.pendingI...
function f (line 12) | function f(e,t,n){r(e)&&(r(n)?c.parentNode(n)===e&&c.insertBefore(e,t,n)...
function p (line 12) | function p(e,t,n){if(Array.isArray(t))for(var i=0;i<t.length;++i)h(t[i],...
function v (line 12) | function v(e){for(;e.componentInstance;)e=e.componentInstance._vnode;ret...
function g (line 12) | function g(e,n){for(var i=0;i<s.create.length;++i)s.create[i](ai,e);r(t=...
function b (line 12) | function b(e){var t;if(r(t=e.fnScopeId))c.setStyleScope(e.elm,t);else fo...
function y (line 12) | function y(e,t,n,i,r,o){for(;i<=r;++i)h(n[i],o,e,t,!1,n,i)}
function _ (line 12) | function _(e){var t,n,i=e.data;if(r(i))for(r(t=i.hook)&&r(t=t.destroy)&&...
function w (line 12) | function w(e,t,n){for(;t<=n;++t){var i=e[t];r(i)&&(r(i.tag)?(x(i),_(i)):...
function x (line 12) | function x(e,t){if(r(t)||r(e.data)){var n,i=s.remove.length+1;for(r(t)?t...
function k (line 12) | function k(e,t,n,i){for(var o=n;o<i;o++){var a=t[o];if(r(a)&&li(e,a))ret...
function C (line 12) | function C(e,t,n,a,l,u){if(e!==t){r(t.elm)&&r(a)&&(t=a[l]=_e(t));var d=t...
function S (line 12) | function S(e,t,n){if(o(n)&&r(e.parent))e.parent.data.pendingInsert=t;els...
function O (line 12) | function O(e,t,n,i){var a,s=t.tag,l=t.data,c=t.children;if(i=i||l&&l.pre...
function Xr (line 12) | function Xr(e,t,n){Jr(e,t,n),(J||Z)&&setTimeout(function(){Jr(e,t,n)},0)}
function Jr (line 12) | function Jr(e,t,n){var i=t.value,r=e.multiple;if(!r||Array.isArray(i)){f...
function Qr (line 12) | function Qr(e,t){return t.every(function(t){return!I(t,e)})}
function Zr (line 12) | function Zr(e){return"_value"in e?e._value:e.value}
function eo (line 12) | function eo(e){e.target.composing=!0}
function to (line 12) | function to(e){e.target.composing&&(e.target.composing=!1,no(e.target,"i...
function no (line 12) | function no(e,t){var n=document.createEvent("HTMLEvents");n.initEvent(t,...
function io (line 12) | function io(e){return!e.componentInstance||e.data&&e.data.transition?e:i...
function ao (line 12) | function ao(e){var t=e&&e.componentOptions;return t&&t.Ctor.options.abst...
function so (line 12) | function so(e){var t={},n=e.$options;for(var i in n.propsData)t[i]=e[i];...
function lo (line 12) | function lo(e,t){if(/\d-keep-alive$/.test(t.tag))return e("keep-alive",{...
function po (line 12) | function po(e){e.elm._moveCb&&e.elm._moveCb(),e.elm._enterCb&&e.elm._ent...
function mo (line 12) | function mo(e){e.data.newPos=e.elm.getBoundingClientRect()}
function vo (line 12) | function vo(e){var t=e.data.pos,n=e.data.newPos,i=t.left-n.left,r=t.top-...
function wo (line 12) | function wo(e,t){var n=t?_o(t):bo;if(n.test(e)){for(var i,r,o,a=[],s=[],...
function Uo (line 12) | function Uo(e,t){var n=t?Ho:Vo;return e.replace(n,function(e){return zo[...
function ma (line 12) | function ma(e,t,n){return{type:1,tag:e,attrsList:t,attrsMap:function(e){...
function va (line 12) | function va(e,t){Ko=t.warn||Pi,Qo=t.isPreTag||P,Zo=t.mustUseProp||P,ea=t...
function ga (line 12) | function ga(e,t){var n,i;!function(e){var t=zi(e,"key");if(t){e.key=t}}(...
function ba (line 12) | function ba(e){var t;if(t=Vi(e,"v-for")){var n=function(e){var t=e.match...
function ya (line 12) | function ya(e,t){e.ifConditions||(e.ifConditions=[]),e.ifConditions.push...
function _a (line 12) | function _a(e){var t=e.name.replace(ua,"");return t||"#"!==e.name[0]&&(t...
function wa (line 12) | function wa(e){var t=e.match(ca);if(t){var n={};return t.forEach(functio...
function Ca (line 12) | function Ca(e){return ma(e.tag,e.attrsList.slice(),e.parent)}
function Ta (line 12) | function Ta(e,t){e&&(Ea=Da(t.staticKeys||""),Oa=t.isReservedTag||P,funct...
function Ba (line 12) | function Ba(e,t){var n=t?"nativeOn:":"on:",i="",r="";for(var o in e){var...
function Ra (line 12) | function Ra(e){if(!e)return"function(){}";if(Array.isArray(e))return"["+...
function Fa (line 12) | function Fa(e){var t=parseInt(e,10);if(t)return"$event.keyCode!=="+t;var...
function Ha (line 12) | function Ha(e,t){var n=new Va(t);return{render:"with(this){return "+(e?q...
function qa (line 12) | function qa(e,t){if(e.parent&&(e.pre=e.pre||e.parent.pre),e.staticRoot&&...
function Wa (line 12) | function Wa(e,t){e.staticProcessed=!0;var n=t.pre;return e.pre&&(t.pre=e...
function Ua (line 12) | function Ua(e,t){if(e.onceProcessed=!0,e.if&&!e.ifProcessed)return Ka(e,...
function Ka (line 12) | function Ka(e,t,n,i){return e.ifProcessed=!0,function e(t,n,i,r){if(!t.l...
function Ya (line 12) | function Ya(e,t,n,i){var r=e.for,o=e.alias,a=e.iterator1?","+e.iterator1...
function Ga (line 12) | function Ga(e,t){var n="{",i=function(e,t){var n=e.directives;if(!n)retu...
function Xa (line 12) | function Xa(e){return 1===e.type&&("slot"===e.tag||e.children.some(Xa))}
function Ja (line 12) | function Ja(e,t){var n=e.attrsMap["slot-scope"];if(e.if&&!e.ifProcessed&...
function Qa (line 12) | function Qa(e,t,n,i,r){var o=e.children;if(o.length){var a=o[0];if(1===o...
function Za (line 12) | function Za(e){return void 0!==e.for||"template"===e.tag||"slot"===e.tag}
function es (line 12) | function es(e,t){return 1===e.type?qa(e,t):3===e.type&&e.isComment?(i=e,...
function ts (line 12) | function ts(e){for(var t="",n="",i=0;i<e.length;i++){var r=e[i],o=ns(r.v...
function ns (line 12) | function ns(e){return e.replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"...
function is (line 12) | function is(e,t){try{return new Function(e)}catch(n){return t.push({err:...
function rs (line 12) | function rs(e){var t=Object.create(null);return function(n,i,r){(i=D({},...
function t (line 12) | function t(t,n){var i=Object.create(e),r=[],o=[],a=function(e,t,n){(n?o:...
function cs (line 12) | function cs(e){return(as=as||document.createElement("div")).innerHTML=e?...
function l (line 12) | function l(e){return e&&e.__esModule?e:{default:e}}
function o (line 12) | function o(e){var t=this;this.next=null,this.entry=null,this.finish=func...
function m (line 12) | function m(){}
function v (line 12) | function v(e,t){a=a||n("DsFX"),e=e||{};var i=t instanceof a;this.objectM...
function g (line 12) | function g(e){if(a=a||n("DsFX"),!(f.call(g,this)||this instanceof a))ret...
function b (line 12) | function b(e,t,n,i,r,o,a){t.writelen=i,t.writecb=a,t.writing=!0,t.sync=!...
function y (line 12) | function y(e,t,n,i){n||function(e,t){0===t.length&&t.needDrain&&(t.needD...
function _ (line 12) | function _(e,t){t.bufferProcessing=!0;var n=t.bufferedRequest;if(e._writ...
function w (line 12) | function w(e){return e.ending&&0===e.length&&null===e.bufferedRequest&&!...
function x (line 12) | function x(e,t){e._final(function(n){t.pendingcb--,n&&e.emit("error",n),...
function k (line 12) | function k(e,t){var n=w(t);return n&&(!function(e,t){t.prefinished||t.fi...
function y (line 12) | function y(){if(!(this instanceof y))return new y;g.call(this),this.h=[1...
function _ (line 12) | function _(e,t,n,i,r){var o=e&n^~e&r;return o<0&&(o+=4294967296),o}
function w (line 12) | function w(e,t,n,i,r,o){var a=t&i^~t&o;return a<0&&(a+=4294967296),a}
function x (line 12) | function x(e,t,n,i,r){var o=e&n^e&r^n&r;return o<0&&(o+=4294967296),o}
function k (line 12) | function k(e,t,n,i,r,o){var a=t&i^t&o^i&o;return a<0&&(a+=4294967296),a}
function C (line 12) | function C(e,t){var n=a(e,t,28)^a(t,e,2)^a(t,e,7);return n<0&&(n+=429496...
function S (line 12) | function S(e,t){var n=s(e,t,28)^s(t,e,2)^s(t,e,7);return n<0&&(n+=429496...
function E (line 12) | function E(e,t){var n=a(e,t,14)^a(e,t,18)^a(t,e,9);return n<0&&(n+=42949...
function O (line 12) | function O(e,t){var n=s(e,t,14)^s(e,t,18)^s(t,e,9);return n<0&&(n+=42949...
function M (line 12) | function M(e,t){var n=a(e,t,1)^a(e,t,8)^l(e,t,7);return n<0&&(n+=4294967...
function D (line 12) | function D(e,t){var n=s(e,t,1)^s(e,t,8)^c(e,t,7);return n<0&&(n+=4294967...
function T (line 12) | function T(e,t){var n=a(e,t,19)^a(t,e,29)^l(e,t,6);return n<0&&(n+=42949...
function $ (line 12) | function $(e,t){var n=s(e,t,19)^s(t,e,29)^c(e,t,6);return n<0&&(n+=42949...
function o (line 12) | function o(e,t,n){var o=t.length,a=r(t,e._cache);return e._cache=e._cach...
function r (line 12) | function r(){i.call(this)}
function r (line 12) | function r(t){e.writable&&!1===e.write(t)&&n.pause&&n.pause()}
function o (line 12) | function o(){n.readable&&n.resume&&n.resume()}
function s (line 12) | function s(){a||(a=!0,e.end())}
function l (line 12) | function l(){a||(a=!0,"function"==typeof e.destroy&&e.destroy())}
function c (line 12) | function c(e){if(u(),0===i.listenerCount(this,"error"))throw e}
function u (line 12) | function u(){n.removeListener("data",r),e.removeListener("drain",o),n.re...
function m (line 12) | function m(e,t,n,i){for(var r=t;r<n;r++)e[r]=i}
function h (line 12) | function h(e,t){o.call(this,"digest"),"string"==typeof t&&(t=a.from(t));...
function r (line 12) | function r(e){this.options=e,this.type=this.options.type,this.blockSize=...
function l (line 12) | function l(e,t){this.type=e,this.p=new i(t.p,16),this.red=t.prime?i.red(...
function c (line 12) | function c(e,t){this.curve=e,this.type=t,this.precomputed=null}
function r (line 12) | function r(e){i.isBuffer(e)||(e=i.from(e));for(var t=e.length/4|0,n=new ...
function o (line 12) | function o(e){for(;0<e.length;e++)e[0]=0}
function a (line 12) | function a(e,t,n,i,r){for(var o,a,s,l,c=n[0],u=n[1],h=n[2],d=n[3],f=e[0]...
function c (line 12) | function c(e){this._key=r(e),this._reset()}
function l (line 12) | function l(e){s.call(this,"digest"),this._hash=e}
function l (line 12) | function l(){this.init(),this._w=s,r.call(this,128,112)}
function c (line 12) | function c(e,t,n){return n^e&(t^n)}
function u (line 12) | function u(e,t,n){return e&t|n&(e|t)}
function h (line 12) | function h(e,t){return(e>>>28|t<<4)^(t>>>2|e<<30)^(t>>>7|e<<25)}
function d (line 12) | function d(e,t){return(e>>>14|t<<18)^(e>>>18|t<<14)^(t>>>9|e<<23)}
function f (line 12) | function f(e,t){return(e>>>1|t<<31)^(e>>>8|t<<24)^e>>>7}
function p (line 12) | function p(e,t){return(e>>>1|t<<31)^(e>>>8|t<<24)^(e>>>7|t<<25)}
function m (line 12) | function m(e,t){return(e>>>19|t<<13)^(t>>>29|e<<3)^e>>>6}
function v (line 12) | function v(e,t){return(e>>>19|t<<13)^(t>>>29|e<<3)^(e>>>6|t<<26)}
function g (line 12) | function g(e,t){return e>>>0<t>>>0?1:0}
function t (line 12) | function t(t,n,i){e.writeInt32BE(t,i),e.writeInt32BE(n,i+4)}
function u (line 12) | function u(){if(!(this instanceof u))return new u;c.call(this),this.h=[1...
function h (line 12) | function h(e,t,n,i){return e<=15?t^n^i:e<=31?t&n|~t&i:e<=47?(t|~n)^i:e<=...
function d (line 12) | function d(e){return e<=15?0:e<=31?1518500249:e<=47?1859775393:e<=63?240...
function f (line 12) | function f(e){return e<=15?1352829926:e<=31?1548603684:e<=47?1836072691:...
function o (line 12) | function o(e){var t=r.allocUnsafe(4);return t.writeUInt32BE(e,0),t}
function r (line 12) | function r(e,t){this._block=i.alloc(e),this._finalSize=t,this._blockSize...
function o (line 12) | function o(e){if(!(this instanceof o))return new o(e);i.call(this,e),thi...
function a (line 12) | function a(){var e=this;"function"==typeof this._flush?this._flush(funct...
function s (line 12) | function s(e,t,n){if(t)return e.emit("error",t);if(null!=n&&e.push(n),e....
function r (line 12) | function r(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(...
function h (line 12) | function h(e){if(!(this instanceof h))return new h(e);a.call(this,e),s.c...
function d (line 12) | function d(){this.allowHalfOpen||this._writableState.ended||i.nextTick(f...
function f (line 12) | function f(e){e.end()}
function o (line 12) | function o(){if(!(this instanceof o))return new o;r.call(this),this.h=[3...
function n (line 12) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function i (line 12) | function i(e,t,n,i,r,o,a,s){var l,c="function"==typeof e?e.options:e;if(...
function c (line 12) | function c(e){var t=e.length;if(t%4>0)throw new Error("Invalid string. L...
function u (line 12) | function u(e,t,n){for(var r,o,a=[],s=t;s<n;s+=3)r=(e[s]<<16&16711680)+(e...
function t (line 12) | function t(i){if(n[i])return n[i].exports;var r=n[i]={i:i,l:!1,exports:{...
function a (line 19) | function a(){return l.TYPED_ARRAY_SUPPORT?2147483647:1073741823}
function s (line 19) | function s(e,t){if(a()<t)throw new RangeError("Invalid typed array lengt...
function l (line 19) | function l(e,t,n){if(!(l.TYPED_ARRAY_SUPPORT||this instanceof l))return ...
function c (line 19) | function c(e,t,n,i){if("number"==typeof t)throw new TypeError('"value" a...
function u (line 19) | function u(e){if("number"!=typeof e)throw new TypeError('"size" argument...
function h (line 19) | function h(e,t){if(u(t),e=s(e,t<0?0:0|f(t)),!l.TYPED_ARRAY_SUPPORT)for(v...
function d (line 19) | function d(e,t){var n=t.length<0?0:0|f(t.length);e=s(e,n);for(var i=0;i<...
function f (line 19) | function f(e){if(e>=a())throw new RangeError("Attempt to allocate Buffer...
function p (line 19) | function p(e,t){if(l.isBuffer(e))return e.length;if("undefined"!=typeof ...
function m (line 19) | function m(e,t,n){var i=e[t];e[t]=e[n],e[n]=i}
function v (line 19) | function v(e,t,n,i,r){if(0===e.length)return-1;if("string"==typeof n?(i=...
function g (line 19) | function g(e,t,n,i,r){var o,a=1,s=e.length,l=t.length;if(void 0!==i&&("u...
function b (line 19) | function b(e,t,n,i){n=Number(n)||0;var r=e.length-n;i?(i=Number(i))>r&&(...
function y (line 19) | function y(e,t,n,i){return V(F(t,e.length-n),e,n,i)}
function _ (line 19) | function _(e,t,n,i){return V(function(e){for(var t=[],n=0;n<e.length;++n...
function w (line 19) | function w(e,t,n,i){return _(e,t,n,i)}
function x (line 19) | function x(e,t,n,i){return V(z(t),e,n,i)}
function k (line 19) | function k(e,t,n,i){return V(function(e,t){for(var n,i,r,o=[],a=0;a<e.le...
function C (line 19) | function C(e,t,n){return 0===t&&n===e.length?i.fromByteArray(e):i.fromBy...
function S (line 19) | function S(e,t,n){n=Math.min(e.length,n);for(var i=[],r=t;r<n;){var o,a,...
function O (line 19) | function O(e,t,n){var i="";n=Math.min(e.length,n);for(var r=t;r<n;++r)i+...
function M (line 19) | function M(e,t,n){var i="";n=Math.min(e.length,n);for(var r=t;r<n;++r)i+...
function D (line 19) | function D(e,t,n){var i=e.length;(!t||t<0)&&(t=0),(!n||n<0||n>i)&&(n=i);...
function T (line 19) | function T(e,t,n){for(var i=e.slice(t,n),r="",o=0;o<i.length;o+=2)r+=Str...
function $ (line 19) | function $(e,t,n){if(e%1!=0||e<0)throw new RangeError("offset is not uin...
function P (line 19) | function P(e,t,n,i,r,o){if(!l.isBuffer(e))throw new TypeError('"buffer" ...
function A (line 19) | function A(e,t,n,i){t<0&&(t=65535+t+1);for(var r=0,o=Math.min(e.length-n...
function I (line 19) | function I(e,t,n,i){t<0&&(t=4294967295+t+1);for(var r=0,o=Math.min(e.len...
function j (line 19) | function j(e,t,n,i,r,o){if(n+i>e.length)throw new RangeError("Index out ...
function N (line 19) | function N(e,t,n,i,o){return o||j(e,0,n,4),r.write(e,t,n,i,23,4),n+4}
function L (line 19) | function L(e,t,n,i,o){return o||j(e,0,n,8),r.write(e,t,n,i,52,8),n+8}
function R (line 19) | function R(e){return e<16?"0"+e.toString(16):e.toString(16)}
function F (line 19) | function F(e,t){var n;t=t||1/0;for(var i=e.length,r=null,o=[],a=0;a<i;++...
function z (line 19) | function z(e){return i.toByteArray(function(e){if((e=function(e){return ...
function V (line 19) | function V(e,t,n,i){for(var r=0;r<i&&!(r+n>=t.length||r>=e.length);++r)t...
function h (line 19) | function h(e){if(!(this instanceof h))return new h(e);"string"==typeof e...
function r (line 19) | function r(e){var i=e;return t&&(n.setAttribute("href",i),i=n.href),n.se...
function d (line 19) | function d(e,t,n,i,r){return c.importKey("raw",e,{name:"PBKDF2"},!1,["de...
function n (line 19) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function i (line 19) | function i(e,t,n,i,r,o,a,s){var l,c="function"==typeof e?e.options:e;if(...
function r (line 19) | function r(e){return e._prev=e._cipher.encryptBlock(e._prev),e._prev}
function n (line 19) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function i (line 19) | function i(e,t,n,i,r,o,a,s){var l,c="function"==typeof e?e.options:e;if(...
function h (line 19) | function h(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[...
function a (line 19) | function a(e){if(!(this instanceof a))return new a(e);this.hash=e.hash,t...
function l (line 19) | function l(e){i.call(this);var t,n=e.mode.toLowerCase(),r=s[n];t=e.decry...
function h (line 19) | function h(e,t,n){return function(){var i=arguments.length>0&&void 0!==a...
function s (line 19) | function s(e){a.call(this,e);var t=new function(){this.tmp=new Array(2),...
function a (line 19) | function a(e,t){!i.isUndefined(e)&&i.isUndefined(e["Content-Type"])&&(e[...
function s (line 19) | function s(e,t){if(e.cmpn(0)<=0)throw new Error("invalid sig");if(e.cmp(...
function l (line 19) | function l(){this.init(),this._w=s,r.call(this,64,56)}
function c (line 19) | function c(e){return e<<5|e>>>27}
function u (line 19) | function u(e){return e<<30|e>>>2}
function h (line 19) | function h(e,t,n,i){return 0===e?t&n|~t&i:2===e?t&n|t&i|n&i:t^n^i}
function c (line 19) | function c(e){r.Writable.call(this);var t=l[e];if(!t)throw new Error("Un...
function u (line 19) | function u(e){r.Writable.call(this);var t=l[e];if(!t)throw new Error("Un...
function h (line 19) | function h(e){return new c(e)}
function d (line 19) | function d(e){return new u(e)}
function f (line 19) | function f(){o.call(this,64),this._a=1732584193,this._b=4023233417,this....
function p (line 19) | function p(e,t){return e<<t|e>>>32-t}
function m (line 19) | function m(e,t,n,i,r,o,a,s){return p(e+(t^n^i)+o+a|0,s)+r|0}
function v (line 19) | function v(e,t,n,i,r,o,a,s){return p(e+(t&n|~t&i)+o+a|0,s)+r|0}
function g (line 19) | function g(e,t,n,i,r,o,a,s){return p(e+((t|~n)^i)+o+a|0,s)+r|0}
function b (line 19) | function b(e,t,n,i,r,o,a,s){return p(e+(t&i|n&~i)+o+a|0,s)+r|0}
function y (line 19) | function y(e,t,n,i,r,o,a,s){return p(e+(t^(n|~i))+o+a|0,s)+r|0}
function l (line 19) | function l(){this.init(),this._w=s,r.call(this,64,56)}
function c (line 19) | function c(e){return e<<30|e>>>2}
function u (line 19) | function u(e,t,n,i){return 0===e?t&n|~t&i:2===e?t&n|t&i|n&i:t^n^i}
function a (line 19) | function a(e,t){if(e instanceof a)return e;this._importDER(e,t)||(o(e.r&...
function s (line 19) | function s(e,t){var n=e[t.place++];if(!(128&n))return n;for(var i=15&n,r...
function l (line 19) | function l(e){for(var t=0,n=e.length-1;!e[t]&&!(128&e[t+1])&&t<n;)t++;re...
function c (line 19) | function c(e,t){if(t<128)e.push(t);else{var n=1+(Math.log(t)/Math.LN2>>>...
function n (line 19) | function n(e,n,i){this._reference=e.jquery?e[0]:e,this.state={};var r=vo...
function i (line 19) | function i(t){var n=t.style.display,i=t.style.visibility;t.style.display...
function r (line 19) | function r(e){var t={left:"right",right:"left",bottom:"top",top:"bottom"...
function o (line 19) | function o(e){var t=Object.assign({},e);return t.right=t.left+t.width,t....
function a (line 19) | function a(e,t){var n,i=0;for(n in e){if(e[n]===t)return i;i++}return null}
function s (line 19) | function s(t,n){return e.getComputedStyle(t,null)[n]}
function l (line 19) | function l(t){var n=t.offsetParent;return n!==e.document.body&&n?n:e.doc...
function c (line 19) | function c(t){var n=t.parentNode;return n?n===e.document?e.document.body...
function u (line 19) | function u(e,t){Object.keys(t).forEach(function(n){var i,r="";-1!==["wid...
function h (line 19) | function h(e){var t={width:e.offsetWidth,height:e.offsetHeight,left:e.of...
function d (line 19) | function d(e){var t=e.getBoundingClientRect(),n=-1!=navigator.userAgent....
function f (line 19) | function f(t){for(var n=["","ms","webkit","moz","o"],i=0;i<n.length;i++)...
function s (line 19) | function s(e,t){t.forEach(function(t){e.classList.add(t)})}
function l (line 19) | function l(e,t){t.forEach(function(t){e.setAttribute(t.split(":")[0],t.s...
function r (line 19) | function r(e,t){Object.keys(e).forEach(function(n){return t(e[n],n)})}
function o (line 19) | function o(e){return null!==e&&"object"==typeof e}
function d (line 19) | function d(e,t){return t.indexOf(e)<0&&t.push(e),function(){var n=t.inde...
function f (line 19) | function f(e,t){e._actions=Object.create(null),e._mutations=Object.creat...
function p (line 19) | function p(e,t,n){var i=e._vm;e.getters={},e._makeLocalGettersCache=Obje...
function m (line 19) | function m(e,t,n,i,r){var o=!n.length,a=e._modules.getNamespace(n);if(i....
function v (line 19) | function v(e,t){return t.length?t.reduce(function(e,t){return e[t]},e):e}
function g (line 19) | function g(e,t,n){return o(e)&&e.type&&(n=t,t=e,e=e.type),{type:e,payloa...
function b (line 19) | function b(e){c&&e===c||
function k (line 25) | function k(e){return function(e){return Array.isArray(e)||o(e)}(e)?Array...
function C (line 25) | function C(e){return function(t,n){return"string"!=typeof t?(n=t,t=""):"...
function S (line 25) | function S(e,t,n){return e._modulesNamespaceMap[n]}
function b (line 25) | function b(){if(!(this instanceof b))return new b;v.call(this),this.h=[1...
function n (line 25) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function i (line 25) | function i(e,t,n,i,r,o,a,s){var l,c="function"==typeof e?e.options:e;if(...
function b (line 25) | function b(e,t){o=o||n("DsFX"),e=e||{};var i=t instanceof o;this.objectM...
function y (line 25) | function y(e){if(o=o||n("DsFX"),!(this instanceof y))return new y(e);thi...
function _ (line 25) | function _(e,t,n,i,r){var o,a=e._readableState;null===t?(a.reading=!1,fu...
function w (line 25) | function w(e,t,n,i){t.flowing&&0===t.length&&!t.sync?(e.emit("data",n),e...
function k (line 25) | function k(e,t){return e<=0||0===t.length&&t.ended?0:t.objectMode?1:e!=e...
function C (line 25) | function C(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable...
function S (line 25) | function S(e){f("emit readable"),e.emit("readable"),T(e)}
function E (line 25) | function E(e,t){t.readingMore||(t.readingMore=!0,r.nextTick(O,e,t))}
function O (line 25) | function O(e,t){for(var n=t.length;!t.reading&&!t.flowing&&!t.ended&&t.l...
function M (line 25) | function M(e){f("readable nexttick read 0"),e.read(0)}
function D (line 25) | function D(e,t){t.reading||(f("resume read 0"),e.read(0)),t.resumeSchedu...
function T (line 25) | function T(e){var t=e._readableState;for(f("flow",t.flowing);t.flowing&&...
function $ (line 25) | function $(e,t){return 0===t.length?null:(t.objectMode?n=t.buffer.shift(...
function P (line 25) | function P(e){var t=e._readableState;if(t.length>0)throw new Error('"end...
function A (line 25) | function A(e,t){e.endEmitted||0!==e.length||(e.endEmitted=!0,t.readable=...
function I (line 25) | function I(e,t){for(var n=0,i=e.length;n<i;n++)if(e[n]===t)return n;retu...
function c (line 25) | function c(t,i){f("onunpipe"),t===n&&i&&!1===i.hasUnpiped&&(i.hasUnpiped...
function u (line 25) | function u(){f("onend"),e.end()}
function m (line 25) | function m(t){f("ondata"),p=!1,!1!==e.write(t)||p||((1===o.pipesCount&&o...
function v (line 25) | function v(t){f("onerror",t),y(),e.removeListener("error",v),0===s(e,"er...
function g (line 25) | function g(){e.removeListener("finish",b),y()}
function b (line 25) | function b(){f("onfinish"),e.removeListener("close",g),y()}
function y (line 25) | function y(){f("unpipe"),n.unpipe(e)}
function s (line 25) | function s(e,t){this.eddsa=e,this._secret=o(t.secret),e.isPoint(t.pub)?t...
function n (line 25) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function i (line 25) | function i(e,t,n,i,r,o,a,s){var l,c="function"==typeof e?e.options:e;if(...
function n (line 25) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function i (line 25) | function i(e,t,n,i,r,o,a,s){var l,c="function"==typeof e?e.options:e;if(...
function i (line 25) | function i(i,r){return s.type="throw",s.arg=e,t.next=i,r&&(t.method="nex...
function _ (line 25) | function _(e,t,n,i){var r=t&&t.prototype instanceof x?t:x,o=Object.creat...
function w (line 25) | function w(e,t,n){try{return{type:"normal",arg:e.call(t,n)}}catch(e){ret...
function x (line 25) | function x(){}
function k (line 25) | function k(){}
function C (line 25) | function C(){}
function S (line 25) | function S(e){["next","throw","return"].forEach(function(t){e[t]=functio...
function E (line 25) | function E(e){var t;this._invoke=function(n,i){function o(){return new P...
function O (line 25) | function O(e,t){var i=e.iterator[t.method];if(i===n){if(t.delegate=null,...
function M (line 25) | function M(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.f...
function D (line 25) | function D(e){var t=e.completion||{};t.type="normal",delete t.arg,e.comp...
function T (line 25) | function T(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(M,this),this.r...
function $ (line 25) | function $(e){if(e){var t=e[a];if(t)return t.call(e);if("function"==type...
function P (line 25) | function P(){return{value:n,done:!0}}
function a (line 25) | function a(e){var t=e._cipher.encryptBlockRaw(e._prev);return o(e._prev),t}
function o (line 25) | function o(e){var t=i.allocUnsafe(16);return t.writeUInt32BE(e[0]>>>0,0)...
function a (line 25) | function a(e){this.h=e,this.state=i.alloc(16,0),this.cache=i.allocUnsafe...
function l (line 25) | function l(e,t,n){if(e=e.toLowerCase(),o[e])return r.createCipheriv(e,t,...
function c (line 25) | function c(e,t,n){if(e=e.toLowerCase(),o[e])return r.createDecipheriv(e,...
function o (line 25) | function o(e){r.call(this,e),this.enc="pem"}
function o (line 25) | function o(){throw new Error("setTimeout has not been defined")}
function a (line 25) | function a(){throw new Error("clearTimeout has not been defined")}
function s (line 25) | function s(e){if(n===setTimeout)return setTimeout(e,0);if((n===o||!n)&&s...
function d (line 25) | function d(){u&&l&&(u=!1,l.length?c=l.concat(c):h=-1,c.length&&f())}
function f (line 25) | function f(){if(!u){var e=s(d);u=!0;for(var t=c.length;t;){for(l=c,c=[];...
function p (line 25) | function p(e,t){this.fun=e,this.array=t}
function m (line 25) | function m(){}
function o (line 25) | function o(e,t){for(var n in e)t[n]=e[n]}
function a (line 25) | function a(e,t,n){return r(e,t,n)}
function o (line 25) | function o(e){var t;switch(this.encoding=function(e){var t=function(e){i...
function a (line 25) | function a(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1...
function s (line 25) | function s(e){var t=this.lastTotal-this.lastNeed,n=function(e,t,n){if(12...
function l (line 25) | function l(e,t){if((e.length-t)%2==0){var n=e.toString("utf16le",t);if(n...
function c (line 25) | function c(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n...
function u (line 25) | function u(e,t){var n=(e.length-t)%3;return 0===n?e.toString("base64",t)...
function h (line 25) | function h(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+...
function d (line 25) | function d(e){return e.toString(this.encoding)}
function f (line 25) | function f(e){return e&&e.length?this.write(e):""}
function l (line 25) | function l(e){this.defaults=e,this.interceptors={request:new o,response:...
function l (line 39) | function l(e){var t=0,n=0,i=0,r=0;return"detail"in e&&(n=e.detail),"whee...
function l (line 39) | function l(e,t){o.call(this,"digest"),"string"==typeof t&&(t=r.from(t)),...
function o (line 39) | function o(){this.pending=null,this.pendingTotal=0,this.blockSize=this.c...
function s (line 39) | function s(e){o.call(this,e);var t=new function(e,t){i.equal(t.length,24...
function r (line 39) | function r(e,t,n){var r=e._cipher.encryptBlock(e._prev)[0]^t;return e._p...
function e (line 39) | function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Ca...
function h (line 39) | function h(e,t,n){var a=function(e){return"rmd160"===e||"ripemd160"===e?...
function d (line 39) | function d(e,n){return n=n||"utf8",t.isBuffer(e)||(e=new t(e,n)),this._p...
function f (line 39) | function f(e,n){return n=n||"utf8",t.isBuffer(e)||(e=new t(e,n)),this._p...
function m (line 39) | function m(e,t,n){this.setGenerator(t),this.__prime=new i(e),this._prime...
function v (line 39) | function v(e,n){var i=new t(e.toArray());return n?i.toString(n):i}
function o (line 39) | function o(e){this.rand=e||new r.Rand}
function n (line 39) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function l (line 39) | function l(){this.init(),this._w=s,o.call(this,128,112)}
function t (line 39) | function t(t,n,i){e.writeInt32BE(t,i),e.writeInt32BE(n,i+4)}
function d (line 39) | function d(){if(!(this instanceof d))return new d;u.call(this),this.h=[1...
function u (line 39) | function u(e,t,n){s.call(this),this._cache=new d,this._cipher=new l.AES(...
function d (line 39) | function d(){this.cache=o.allocUnsafe(0)}
function f (line 39) | function f(e,t,n){var s=i[e.toLowerCase()];if(!s)throw new TypeError("in...
function o (line 39) | function o(e){return"[object Array]"===r.call(e)}
function a (line 39) | function a(e){return void 0===e}
function s (line 39) | function s(e){return null!==e&&"object"==typeof e}
function l (line 39) | function l(e){return"[object Function]"===r.call(e)}
function c (line 39) | function c(e,t){if(null!==e&&void 0!==e)if("object"!=typeof e&&(e=[e]),o...
function n (line 39) | function n(n,i){"object"==typeof t[i]&&"object"==typeof n?t[i]=e(t[i],n)...
function n (line 39) | function n(n,i){"object"==typeof t[i]&&"object"==typeof n?t[i]=e(t[i],n)...
function r (line 39) | function r(e){if("function"!=typeof e)throw new TypeError("executor must...
function i (line 39) | function i(e){this.message=e}
function n (line 39) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function i (line 39) | function i(e,t,n,i,r,o,a,s){var l,c="function"==typeof e?e.options:e;if(...
function s (line 39) | function s(){r.call(this,64),this._a=1732584193,this._b=4023233417,this....
function l (line 39) | function l(e,t){return e<<t|e>>>32-t}
function c (line 39) | function c(e,t,n,i,r,o,a){return l(e+(t&n|~t&i)+r+o|0,a)+t|0}
function u (line 39) | function u(e,t,n,i,r,o,a){return l(e+(t&i|n&~i)+r+o|0,a)+t|0}
function h (line 39) | function h(e,t,n,i,r,o,a){return l(e+(t^n^i)+r+o|0,a)+t|0}
function d (line 39) | function d(e,t,n,i,r,o,a){return l(e+(n^(t|~i))+r+o|0,a)+t|0}
function u (line 39) | function u(e,t){for(var n=[],i=0,r=e.length;i<r;i++)n.push(e[i].substr(0...
function h (line 39) | function h(e){return function(t,n,i){var r=i[e].indexOf(n.charAt(0).toUp...
function d (line 39) | function d(e,t){for(e=String(e),t=t||2;e.length<t;)e="0"+e;return e}
function o (line 39) | function o(e){if(!(this instanceof o))return new o(e);i.call(this,e)}
function n (line 39) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function o (line 39) | function o(){if(!(this instanceof o))return new o;r.call(this),this.h=[3...
function r (line 39) | function r(){this.handlers=[]}
function i (line 39) | function i(e,t){if(!e)throw new Error(t||"Assertion failed")}
function r (line 39) | function r(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e....
function o (line 39) | function o(e,t,n){if(o.isBN(e))return e;this.negative=0,this.words=null,...
function s (line 39) | function s(e,t,n){for(var i=0,r=Math.min(e.length,n),o=t;o<r;o++){var a=...
function l (line 39) | function l(e,t,n,i){for(var r=0,o=Math.min(e.length,n),a=t;a<o;a++){var ...
function d (line 39) | function d(e,t,n){n.negative=t.negative^e.negative;var i=e.length+t.leng...
function p (line 39) | function p(e,t,n){return(new m).mulp(e,t,n)}
function m (line 39) | function m(e,t){this.x=e,this.y=t}
function g (line 39) | function g(e,t){this.name=e,this.p=new o(t,16),this.n=this.p.bitLength()...
function b (line 39) | function b(){g.call(this,"k256","ffffffff ffffffff ffffffff ffffffff fff...
function y (line 39) | function y(){g.call(this,"p224","ffffffff ffffffff ffffffff ffffffff 000...
function _ (line 39) | function _(){g.call(this,"p192","ffffffff ffffffff ffffffff fffffffe fff...
function w (line 39) | function w(){g.call(this,"25519","7fffffffffffffff ffffffffffffffff ffff...
function x (line 39) | function x(e){if("string"==typeof e){var t=o._prime(e);this.m=t.p,this.p...
function k (line 39) | function k(e){x.call(this,e),this.shift=this.m.bitLength(),this.shift%26...
function a (line 39) | function a(e){this.curveType=o[e],this.curveType||(this.curveType={name:...
function s (line 39) | function s(e,n,i){Array.isArray(e)||(e=e.toArray());var r=new t(e);if(i&...
function l (line 39) | function l(e){"short"===e.type?this.curve=new a.short(e):"edwards"===e.t...
function c (line 39) | function c(e,t){Object.defineProperty(r,e,{configurable:!0,enumerable:!0...
function l (line 39) | function l(e,t){this.eddsa=e,"object"!=typeof t&&(t=s(t)),Array.isArray(...
function o (line 39) | function o(e,t){var n;return t&&!0===t.clone&&i(e)?s((n=e,Array.isArray(...
function a (line 39) | function a(e,t,n){var r=e.slice();return t.forEach(function(t,a){void 0=...
function s (line 39) | function s(e,t,n){var r=Array.isArray(t);return r===Array.isArray(e)?r?(...
function n (line 39) | function n(e){try{if(!t.localStorage)return!1}catch(e){return!1}var n=t....
function a (line 39) | function a(e,t){r.call(this,t),o.isBuffer(e)?(this.base=e,this.offset=0,...
function s (line 39) | function s(e,t){if(Array.isArray(e))this.length=0,this.value=e.map(funct...
function n (line 39) | function n(e){return Object.prototype.toString.call(e)}
function o (line 39) | function o(e,n){var r=function(e){var t=a(e);return{blinder:t.toRed(i.mo...
function a (line 39) | function a(e){for(var t=e.modulus.byteLength(),n=new i(r(t));n.cmp(e.mod...
function c (line 39) | function c(e){var t;"object"!=typeof e||l.isBuffer(e)||(t=e.passphrase,e...
function c (line 39) | function c(){for(var e=arguments.length,t=Array(e),n=0;n<e;n++)t[n]=argu...
function u (line 39) | function u(e,t){return void 0===e||null===e||(!("array"!==t||!Array.isAr...
function h (line 39) | function h(e,t,n){var i=0,r=e.length;!function o(a){if(a&&a.length)n(a);...
function d (line 39) | function d(e,t,n,i){if(t.first)return h(function(e){var t=[];return Obje...
function f (line 39) | function f(e){return function(t){return t&&t.message?(t.field=t.field||e...
function p (line 39) | function p(e,t){if(t)for(var n in t)if(t.hasOwnProperty(n)){var i=t[n];"...
function C (line 39) | function C(){return{default:"Validation error on field %s",required:"%s ...
function E (line 39) | function E(e){this.rules=null,this._messages=S,this.define(e)}
function o (line 39) | function o(e,t){return r()({},t,{fullField:n.fullField+"."+e})}
function u (line 39) | function u(){var a=arguments.length>0&&void 0!==arguments[0]?arguments[0...
function r (line 39) | function r(e,t,n){for(var i,r,a,s=-1,l=0;++s<8;)i=e._cipher.encryptBlock...
function o (line 39) | function o(e,t){var n=e.length,r=-1,o=i.allocUnsafe(e.length);for(e=i.co...
function o (line 39) | function o(e,t){this.name=e,this.body=t,this.decoders={},this.encoders={}}
function n (line 39) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function i (line 39) | function i(e,t,n,i,r,o,a,s){var l,c="function"==typeof e?e.options:e;if(...
function e (line 39) | function e(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.en...
function e (line 39) | function e(t,n,i){!function(e,t){if(!(e instanceof t))throw new TypeErro...
function e (line 39) | function e(t,n){!function(e,t){if(!(e instanceof t))throw new TypeError(...
function l (line 39) | function l(){this.init(),this._w=s,o.call(this,64,56)}
function n (line 39) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function i (line 39) | function i(e,t,n,i,r,o,a,s){var l,c="function"==typeof e?e.options:e;if(...
function i (line 39) | function i(e,t){return function(){e&&e.apply(this,arguments),t&&t.apply(...
function t (line 39) | function t(t){e.call(this,t),this._cbcInit()}
function n (line 39) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function i (line 39) | function i(e,t,n,i,r,o,a,s){var l,c="function"==typeof e?e.options:e;if(...
function a (line 39) | function a(e){return e&&e.__esModule?e:{default:e}}
function s (line 39) | function s(e){o.call(this,"mont",e),this.a=new i(e.a,16).toRed(this.red)...
function l (line 39) | function l(e,t,n){o.BasePoint.call(this,e,"projective"),null===t&&null==...
function c (line 39) | function c(e,n,r,o){if((e=new t(e.toArray())).length<n.byteLength()){var...
function u (line 39) | function u(e,t){var n=new a(e),i=(e.length<<3)-t.bitLength();return i>0&...
function h (line 39) | function h(e,n,r){var o,a;do{for(o=new t(0);8*o.length<e.bitLength();)n....
function d (line 39) | function d(e,t,n,i){return e.toRed(a.mont(n)).redPow(t).fromRed().mod(i)}
function l (line 39) | function l(e){this.enc="der",this.name=e.name,this.entity=e,this.tree=ne...
function c (line 39) | function c(e){a.Node.call(this,"der",e)}
function u (line 39) | function u(e){return e<10?"0"+e:e}
function l (line 39) | function l(e){this.enc="der",this.name=e.name,this.entity=e,this.tree=ne...
function c (line 39) | function c(e){o.Node.call(this,"der",e)}
function u (line 39) | function u(e,t){var n=e.readUInt8(t);if(e.isError(n))return n;var i=s.ta...
function h (line 39) | function h(e,t,n){var i=e.readUInt8(n);if(e.isError(i))return i;if(!t&&1...
function n (line 39) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function i (line 39) | function i(e,t,n,i,r,o,a,s){var l,c="function"==typeof e?e.options:e;if(...
function s (line 39) | function s(e){var t=new o(e),n=r(o.prototype.request,t);return i.extend(...
function r (line 39) | function r(e){return 1===e.length?"0"+e:e}
function o (line 39) | function o(e){for(var t="",n=0;n<e.length;n++)t+=r(e[n].toString(16));re...
function r (line 39) | function r(e){this.rand=e}
function c (line 39) | function c(){o=Number(new Date),n.apply(a,l)}
function a (line 39) | function a(e){return e&&e.__esModule?e:{default:e}}
function a (line 39) | function a(e){o.call(this,e),this.enc="pem"}
function o (line 44) | function o(t){if(void 0!==e&&e&&e.exports)try{return n("VI/i").randomByt...
function a (line 44) | function a(e,t){for(var n=0,i=0,r=0,o=e.length;r<o;++r)e.charCodeAt(r)==...
function r (line 44) | function r(n){s(function(){try{n(null,t.genSaltSync(e))}catch(e){n(e)}})}
function o (line 44) | function o(i){"string"==typeof e&&"number"==typeof n?t.genSalt(n,functio...
function o (line 44) | function o(i){"string"==typeof e&&"string"==typeof n?60===n.length?t.has...
function h (line 44) | function h(e,t){var n,i,r=0,o=[];if(t<=0||t>e.length)throw Error("Illega...
function d (line 44) | function d(e,t){var n,i,r,o,a,s=0,l=e.length,h=0,d=[];if(t<=0)throw Erro...
function w (line 44) | function w(e,t,n,i){var r,o=e[t],a=e[t+1];return r=i[(o^=n[0])>>>24],r+=...
function x (line 44) | function x(e,t){for(var n=0,i=0;n<4;++n)i=i<<8|255&e[t],t=(t+1)%e.length...
function k (line 44) | function k(e,t,n){for(var i,r=0,o=[0,0],a=t.length,s=n.length,l=0;l<a;l+...
function C (line 44) | function C(e,t,n,i,r){var o,a=_.slice(),l=a.length;if(n<4||n>31){if(o=Er...
function S (line 44) | function S(e,t,n,i){var r,o,a;if("string"!=typeof e||"string"!=typeof t)...
function c (line 44) | function c(e,t){var n={};this._baseState=n,n.enc=e,n.parent=t||null,n.ch...
function s (line 44) | function s(){s.init.call(this)}
function c (line 44) | function c(e){if("function"!=typeof e)throw new TypeError('The "listener...
function u (line 44) | function u(e){return void 0===e._maxListeners?s.defaultMaxListeners:e._m...
function h (line 44) | function h(e,t,n,i){var r,o,a,s;if(c(n),void 0===(o=e._events)?(o=e._eve...
function d (line 44) | function d(e,t,n){var i={fired:!1,wrapFn:void 0,target:e,type:t,listener...
function f (line 44) | function f(e,t,n){var i=e._events;if(void 0===i)return[];var r=i[t];retu...
function p (line 44) | function p(e){var t=this._events;if(void 0!==t){var n=t[e];if("function"...
function m (line 44) | function m(e,t){for(var n=new Array(t),i=0;i<t;++i)n[i]=e[i];return n}
function l (line 44) | function l(e){a.call(this,"short",e),this.a=new r(e.a,16).toRed(this.red...
function c (line 44) | function c(e,t,n,i){a.BasePoint.call(this,e,"affine"),null===t&&null===n...
function u (line 44) | function u(e,t,n,i){a.BasePoint.call(this,e,"jacobian"),null===t&&null==...
function r (line 44) | function r(t){return e.point(t[0],t[1],n)}
function r (line 44) | function r(e,t){e.emit("error",t)}
function s (line 44) | function s(e){e.cancelToken&&e.cancelToken.throwIfRequested()}
function o (line 44) | function o(e){r.call(this),this._block=i.allocUnsafe(e),this._blockSize=...
function o (line 44) | function o(e,t){this.ec=e,this.priv=null,this.pub=null,t.priv&&this._imp...
function c (line 44) | function c(e,t){for(var n in t)e[n]=t[n];return e}
function a (line 44) | function a(e){r.call(this),this.hashMode="string"==typeof e,this.hashMod...
function e (line 44) | function e(e,t){var n=-1;return e.some(function(e,i){return e[0]===t&&(n...
function t (line 44) | function t(){this.__entries__=[]}
function e (line 44) | function e(){this.connected_=!1,this.mutationEventsAdded_=!1,this.mutati...
function p (line 44) | function p(e){return parseFloat(e)||0}
function m (line 44) | function m(e){for(var t=[],n=1;n<arguments.length;n++)t[n-1]=arguments[n...
function v (line 44) | function v(e){var t=e.clientWidth,n=e.clientHeight;if(!t&&!n)return f;va...
function b (line 44) | function b(e){return i?g(e)?function(e){var t=e.getBBox();return y(0,0,t...
function y (line 44) | function y(e,t,n,i){return{x:e,y:t,width:n,height:i}}
function e (line 44) | function e(e){this.broadcastWidth=0,this.broadcastHeight=0,this.contentR...
function e (line 44) | function e(e,t,i){if(this.activeObservations_=[],this.observations_=new ...
function n (line 44) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function i (line 44) | function i(e,t,n,i,r,o,a,s){var l,c="function"==typeof e?e.options:e;if(...
function n (line 44) | function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{...
function r (line 44) | function r(e,t,n,i,r,o,a,s){var l,c="function"==typeof e?e.options:e;if(...
function Re (line 44) | function Re(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments...
function xn (line 44) | function xn(e,t){return Object.prototype.hasOwnProperty.call(e,t)}
function kn (line 44) | function kn(e){return void 0!==e&&(e=parseInt(e,10),isNaN(e)&&(e=null)),e}
function Cn (line 44) | function Cn(e){return"number"==typeof e?e:"string"==typeof e?/^\d+(?:px)...
function Sn (line 44) | function Sn(e,t,n){var i=!1,r=e.indexOf(t),o=-1!==r,a=function(){e.push(...
function En (line 44) | function En(e,t){var n=arguments.length>2&&void 0!==arguments[2]?argumen...
function In (line 44) | function In(e){var t={};return Object.keys(e).forEach(function(n){var i=...
function e (line 44) | function e(t){for(var n in function(e,t){if(!(e instanceof t))throw new ...
function li (line 44) | function li(e,t){var n=t.row,i=t.column,r=t.$index,o=i.property,a=o&&Obj...
function xo (line 44) | function xo(){}
function e (line 44) | function e(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.en...
function e (line 44) | function e(t){for(var n in function(e,t){if(!(e instanceof t))throw new ...
function e (line 44) | function e(t){var n=this;for(var i in function(e,t){if(!(e instanceof t)...
function is (line 44) | function is(){}
function e (line 44) | function e(t){for(var n in function(e,t){if(!(e instanceof t))throw new ...
function e (line 44) | function e(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.en...
function e (line 44) | function e(t,n,i){!function(e,t){if(!(e instanceof t))throw new TypeErro...
function e (line 44) | function e(t,n){!function(e,t){if(!(e instanceof t))throw new TypeError(...
function l (line 44) | function l(){this.init(),this._w=s,r.call(this,64,56)}
function c (line 44) | function c(e,t,n){return n^e&(t^n)}
function u (line 44) | function u(e,t,n){return e&t|n&(e|t)}
function h (line 44) | function h(e){return(e>>>2|e<<30)^(e>>>13|e<<19)^(e>>>22|e<<10)}
function d (line 44) | function d(e){return(e>>>6|e<<26)^(e>>>11|e<<21)^(e>>>25|e<<7)}
function f (line 44) | function f(e){return(e>>>7|e<<25)^(e>>>18|e<<14)^e>>>3}
FILE: src/test/java/com/github/fenixsoft/bookstore/DBRollbackBase.java
class DBRollbackBase (line 20) | @ActiveProfiles("test")
method evictAllCaches (line 29) | @BeforeEach
FILE: src/test/java/com/github/fenixsoft/bookstore/resource/AccountResourceTest.java
class AccountResourceTest (line 22) | class AccountResourceTest extends JAXRSResourceBase {
method getUserWithExistAccount (line 24) | @Test
method getUserWithNotExistAccount (line 32) | @Test
method createUser (line 37) | @Test
method updateUser (line 50) | @Test
FILE: src/test/java/com/github/fenixsoft/bookstore/resource/AdvertisementResourceTest.java
class AdvertisementResourceTest (line 13) | class AdvertisementResourceTest extends JAXRSResourceBase {
method getAllAdvertisements (line 15) | @Test
FILE: src/test/java/com/github/fenixsoft/bookstore/resource/AuthResourceTest.java
class AuthResourceTest (line 14) | public class AuthResourceTest extends JAXRSResourceBase {
method refreshToken (line 16) | @Test
FILE: src/test/java/com/github/fenixsoft/bookstore/resource/JAXRSResourceBase.java
class JAXRSResourceBase (line 31) | @SpringBootTest(classes = BookstoreApplication.class, webEnvironment = S...
method build (line 39) | Invocation.Builder build(String path) {
method json (line 49) | JSONObject json(Response response) throws JSONException {
method jsonArray (line 53) | JSONArray jsonArray(Response response) throws JSONException {
method login (line 60) | void login() {
method logout (line 70) | void logout() {
method authenticatedScope (line 74) | void authenticatedScope(Runnable runnable) {
method authenticatedGetter (line 83) | <T> T authenticatedGetter(Supplier<T> supplier) {
method get (line 92) | Response get(String path) {
method delete (line 96) | Response delete(String path) {
method post (line 100) | Response post(String path, Object entity) {
method put (line 104) | Response put(String path, Object entity) {
method patch (line 108) | Response patch(String path) {
method assertOK (line 112) | static void assertOK(Response response) {
method assertNoContent (line 116) | static void assertNoContent(Response response) {
method assertBadRequest (line 120) | static void assertBadRequest(Response response) {
method assertForbidden (line 124) | static void assertForbidden(Response response) {
method assertServerError (line 128) | static void assertServerError(Response response) {
method assertNotFound (line 132) | static void assertNotFound(Response response) {
FILE: src/test/java/com/github/fenixsoft/bookstore/resource/PaymentResourceTest.java
class PaymentResourceTest (line 18) | class PaymentResourceTest extends JAXRSResourceBase {
method createSettlement (line 20) | private Settlement createSettlement() {
method executeSettlement (line 35) | @Test
method updatePaymentState (line 47) | @Test
method updatePaymentStateAlias (line 60) | @Test
FILE: src/test/java/com/github/fenixsoft/bookstore/resource/ProductResourceTest.java
class ProductResourceTest (line 19) | class ProductResourceTest extends JAXRSResourceBase {
method getAllProducts (line 21) | @Test
method getProduct (line 26) | @Test
method updateProduct (line 34) | @Test
method createProduct (line 44) | @Test
method removeProduct (line 60) | @Test
method updateAndQueryStockpile (line 68) | @Test
Condensed preview — 102 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,349K chars).
[
{
"path": ".gitignore",
"chars": 333,
"preview": "HELP.md\ntarget/\n!.mvn/wrapper/maven-wrapper.jar\n!**/src/main/**\n!**/src/test/**\n\n### STS ###\n.apt_generated\n.classpath\n."
},
{
"path": ".mvn/wrapper/MavenWrapperDownloader.java",
"chars": 4951,
"preview": "/*\n * Copyright 2007-present the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": ".mvn/wrapper/maven-wrapper.properties",
"chars": 218,
"preview": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip\nwrap"
},
{
"path": ".travis.yml",
"chars": 387,
"preview": "language: java\njdk:\n - openjdk12\nbefore_install:\n - export TZ='Asia/Shanghai'\ninstall:\n - mvn install -DskipTests=tru"
},
{
"path": "Dockerfile",
"chars": 273,
"preview": "FROM openjdk:12-alpine\n\nMAINTAINER icyfenix\n\nENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \\\n JAVA_OPTS=\"\" \\\n PROFILES=\"de"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 6823,
"preview": "# Fenix's BookStore后端:以单体架构实现\n\n<p align=\"center\">\n <a href=\"https://icyfenix.cn\" target=\"_blank\">\n <img width=\"180\" "
},
{
"path": "mvnw",
"chars": 10070,
"preview": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Softwa"
},
{
"path": "mvnw.cmd",
"chars": 6608,
"preview": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software F"
},
{
"path": "pom.xml",
"chars": 5347,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/BookstoreApplication.java",
"chars": 1280,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/applicaiton/AccountApplicationService.java",
"chars": 1663,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/applicaiton/ProductApplicationService.java",
"chars": 2206,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/applicaiton/payment/PaymentApplicationService.java",
"chars": 2497,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/applicaiton/payment/dto/Settlement.java",
"chars": 3737,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/BaseEntity.java",
"chars": 1304,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/account/Account.java",
"chars": 2824,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/account/AccountRepository.java",
"chars": 2594,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/account/validation/AccountValidation.java",
"chars": 3889,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/account/validation/AuthenticatedAccount.java",
"chars": 1551,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/account/validation/ExistsAccount.java",
"chars": 1454,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/account/validation/NotConflictAccount.java",
"chars": 1552,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/account/validation/UniqueAccount.java",
"chars": 1501,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/auth/AuthenticAccount.java",
"chars": 2557,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/auth/AuthenticAccountRepository.java",
"chars": 1567,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/auth/Role.java",
"chars": 929,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/auth/provider/PreAuthenticatedAuthenticationProvider.java",
"chars": 2968,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/auth/provider/UsernamePasswordAuthenticationProvider.java",
"chars": 2959,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/auth/service/AuthenticAccountDetailsService.java",
"chars": 1673,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/auth/service/JWTAccessToken.java",
"chars": 3812,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/auth/service/JWTAccessTokenService.java",
"chars": 2423,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/auth/service/OAuthClientDetailsService.java",
"chars": 3269,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/payment/Payment.java",
"chars": 3143,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/payment/PaymentRepository.java",
"chars": 1033,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/payment/PaymentService.java",
"chars": 5955,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/payment/Stockpile.java",
"chars": 1956,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/payment/StockpileRepository.java",
"chars": 970,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/payment/StockpileService.java",
"chars": 3141,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/payment/Wallet.java",
"chars": 1591,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/payment/WalletRepository.java",
"chars": 1049,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/payment/WalletService.java",
"chars": 2411,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/payment/validation/SettlementValidator.java",
"chars": 1772,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/payment/validation/SufficientStock.java",
"chars": 1433,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/Advertisement.java",
"chars": 1563,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/AdvertisementRepository.java",
"chars": 1097,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/Product.java",
"chars": 2776,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/ProductRepository.java",
"chars": 1060,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/ProductService.java",
"chars": 2029,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/domain/warehouse/Specification.java",
"chars": 1761,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/infrastructure/cache/CacheConfiguration.java",
"chars": 1256,
"preview": "package com.github.fenixsoft.bookstore.infrastructure.cache;\n\nimport com.github.benmanes.caffeine.cache.Caffeine;\nimport"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/AuthenticationServerConfiguration.java",
"chars": 3256,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/AuthorizationServerConfiguration.java",
"chars": 4162,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/JerseyConfiguration.java",
"chars": 2387,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/ResourceServerConfiguration.java",
"chars": 3057,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/infrastructure/configuration/WebSecurityConfiguration.java",
"chars": 809,
"preview": "package com.github.fenixsoft.bookstore.infrastructure.configuration;\n\nimport org.springframework.security.config.annotat"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/AccessDeniedExceptionMapper.java",
"chars": 998,
"preview": "package com.github.fenixsoft.bookstore.infrastructure.jaxrs;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\ni"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/BaseExceptionMapper.java",
"chars": 1397,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/CodedMessage.java",
"chars": 1877,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/CommonResponse.java",
"chars": 2821,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/infrastructure/jaxrs/ViolationExceptionMapper.java",
"chars": 1716,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/infrastructure/utility/Encryption.java",
"chars": 1535,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/resource/AccountResource.java",
"chars": 2661,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/resource/AdvertisementResource.java",
"chars": 1536,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/resource/PaymentResource.java",
"chars": 2726,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/resource/ProductResource.java",
"chars": 3763,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/java/com/github/fenixsoft/bookstore/resource/SettlementResource.java",
"chars": 1922,
"preview": "/*\n * Copyright 2012-2020. the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "src/main/resources/application-mysql.yml",
"chars": 257,
"preview": "#请在启动参数中加入--spring.profiles.active=mysql以激活本配置文件\ndatabase: mysql\n\nspring:\n datasource:\n url: \"jdbc:mysql://mysql_lan"
},
{
"path": "src/main/resources/application-test.yml",
"chars": 505,
"preview": "database: hsqldb\n\nspring:\n datasource:\n schema: \"classpath:db/${database}/schema.sql\"\n data: \"classpath:db/${data"
},
{
"path": "src/main/resources/application.yml",
"chars": 594,
"preview": "database: hsqldb\n\nspring:\n datasource:\n schema: \"classpath:db/${database}/schema.sql\"\n data: \"classpath:db/${data"
},
{
"path": "src/main/resources/banner.txt",
"chars": 448,
"preview": " ______ _ ____ __ _____ __\n / ____/__ ____ (_) __ / __ )____ ____ / /_/ ___/"
},
{
"path": "src/main/resources/db/hsqldb/data.sql",
"chars": 9896,
"preview": "INSERT INTO product VALUES (8, '凤凰架构:构建可靠的大型分布式系统', 0, 0, '<p>这是一部以“如何构建一套可靠的分布式大型软件系统”为叙事主线的开源文档,是一幅帮助开发人员整理现代软件架构各条分支中"
},
{
"path": "src/main/resources/db/hsqldb/schema.sql",
"chars": 2279,
"preview": "DROP TABLE wallet IF EXISTS;\nDROP TABLE account IF EXISTS;\nDROP TABLE specification IF EXISTS;\nDROP TABLE advertisement "
},
{
"path": "src/main/resources/db/mysql/data.sql",
"chars": 10188,
"preview": "INSERT INTO product\nVALUES (8, '凤凰架构:构建可靠的大型分布式系统', 0, 0,\n '<p>这是一部以“如何构建一套可靠的分布式大型软件系统”为叙事主线的开源文档,是一幅帮助开发人员整理现代软"
},
{
"path": "src/main/resources/db/mysql/schema.sql",
"chars": 2282,
"preview": "DROP TABLE IF EXISTS specification;\nDROP TABLE IF EXISTS advertisement;\nDROP TABLE IF EXISTS stockpile;\nDROP TABLE IF EX"
},
{
"path": "src/main/resources/db/mysql/user.sql",
"chars": 212,
"preview": "CREATE DATABASE IF NOT EXISTS bookstore;\n\nALTER DATABASE bookstore\n DEFAULT CHARACTER SET utf8\n DEFAULT COLLATE utf8_g"
},
{
"path": "src/main/resources/static/index.html",
"chars": 563,
"preview": "<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>F"
},
{
"path": "src/main/resources/static/static/board/gitalk.css",
"chars": 24919,
"preview": "@font-face {\n font-family: octicons-link;\n src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAA"
},
{
"path": "src/main/resources/static/static/board/gitalk.html",
"chars": 497,
"preview": "<link rel=\"stylesheet\" href=\"gitalk.css\">\n<script src=\"gitalk.min.js\"></script>\n<html>\n\t<body>\n\t\t<div id=\"container\"></d"
},
{
"path": "src/main/resources/static/static/board/gitalk.min.js",
"chars": 162223,
"preview": "!function(e,t){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=t():\"function\"==typeof define&&define.am"
},
{
"path": "src/main/resources/static/static/css/app.13440f960e43a3574b009b7352447f18.css",
"chars": 250399,
"preview": ".el-pagination--small .arrow.disabled,.el-table--hidden,.el-table .hidden-columns,.el-table td.is-hidden>*,.el-table th."
},
{
"path": "src/main/resources/static/static/js/0.c178f427b3d08777c70f.js",
"chars": 856,
"preview": "webpackJsonp([0],{\"7WGw\":function(t,e,s){\"use strict\";var a={name:\"PayStepIndicator\",props:{step:Number}},i={render:func"
},
{
"path": "src/main/resources/static/static/js/1.a33faf036923758c7965.js",
"chars": 98090,
"preview": "webpackJsonp([1],{\"7tms\":function(t,e,r){\n/*!\n * vue-qrcode v1.0.2\n * https://fengyuanchen.github.io/vue-qrcode\n *\n * Co"
},
{
"path": "src/main/resources/static/static/js/2.626ed94f3752555e21f0.js",
"chars": 59272,
"preview": "webpackJsonp([2],{\"7tms\":function(t,e,r){\n/*!\n * vue-qrcode v1.0.2\n * https://fengyuanchen.github.io/vue-qrcode\n *\n * Co"
},
{
"path": "src/main/resources/static/static/js/3.bc7f0b2154007257c317.js",
"chars": 8007,
"preview": "webpackJsonp([3],{\"2Lpz\":function(e,t){},Cp5v:function(e,t){},P7ry:function(e,t,a){\"use strict\";Object.defineProperty(t,"
},
{
"path": "src/main/resources/static/static/js/4.b4e48a42cf742af20851.js",
"chars": 4047,
"preview": "webpackJsonp([4],{\"/V72\":function(t,e){},\"2+ps\":function(t,e){},GBxx:function(t,e,n){\"use strict\";Object.defineProperty("
},
{
"path": "src/main/resources/static/static/js/5.d375cbd6c7e1463cdbed.js",
"chars": 10022,
"preview": "webpackJsonp([5],{SmJH:function(t,e){},eRfs:function(t,e){},\"f/ib\":function(t,e){},hplB:function(t,e,r){\"use strict\";Obj"
},
{
"path": "src/main/resources/static/static/js/6.68562501db5734ef1531.js",
"chars": 73736,
"preview": "webpackJsonp([6],{\"95YI\":function(e,t,r){var i;window,i=function(){return function(e){var t={};function r(i){if(t[i])ret"
},
{
"path": "src/main/resources/static/static/js/7.184a5e39cc0c624f6a6d.js",
"chars": 73938,
"preview": "webpackJsonp([7],{\"95YI\":function(e,t,r){var i;window,i=function(){return function(e){var t={};function r(i){if(t[i])ret"
},
{
"path": "src/main/resources/static/static/js/8.176f9455c3442c06ebf6.js",
"chars": 759,
"preview": "webpackJsonp([8],{ZLgQ:function(t,e){},k7C0:function(t,e,a){\"use strict\";Object.defineProperty(e,\"__esModule\",{value:!0}"
},
{
"path": "src/main/resources/static/static/js/9.527be297aba1594ffe0d.js",
"chars": 3484,
"preview": "webpackJsonp([9],{TgyC:function(t,e,a){\"use strict\";Object.defineProperty(e,\"__esModule\",{value:!0});var n=a(\"Dd8w\"),l=a"
},
{
"path": "src/main/resources/static/static/js/app.ea66dc0be78c3ed2ae63.js",
"chars": 24846,
"preview": "webpackJsonp([11],{0:function(e,a){},1:function(e,a){},2:function(e,a){},3:function(e,a){},\"4Vh3\":function(e,a){e.export"
},
{
"path": "src/main/resources/static/static/js/manifest.0437a7f02d3154ee1abb.js",
"chars": 1628,
"preview": "!function(e){var n=window.webpackJsonp;window.webpackJsonp=function(r,c,a){for(var f,i,u,s=0,d=[];s<r.length;s++)i=r[s],"
},
{
"path": "src/main/resources/static/static/js/vendor.c2f13a2146485051ae24.js",
"chars": 1180363,
"preview": "webpackJsonp([10],{\"++K3\":function(e,t){var n,i,r,o,a,s,l,c,u,h,d,f,p,m,v,g=!1;function b(){if(!g){g=!0;var e=navigator."
},
{
"path": "src/test/java/com/github/fenixsoft/bookstore/DBRollbackBase.java",
"chars": 1052,
"preview": "package com.github.fenixsoft.bookstore;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension"
},
{
"path": "src/test/java/com/github/fenixsoft/bookstore/resource/AccountResourceTest.java",
"chars": 1985,
"preview": "package com.github.fenixsoft.bookstore.resource;\n\nimport com.github.fenixsoft.bookstore.domain.account.Account;\nimport o"
},
{
"path": "src/test/java/com/github/fenixsoft/bookstore/resource/AdvertisementResourceTest.java",
"chars": 436,
"preview": "package com.github.fenixsoft.bookstore.resource;\n\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api."
},
{
"path": "src/test/java/com/github/fenixsoft/bookstore/resource/AuthResourceTest.java",
"chars": 1144,
"preview": "package com.github.fenixsoft.bookstore.resource;\n\nimport org.json.JSONException;\nimport org.junit.jupiter.api.Assertions"
},
{
"path": "src/test/java/com/github/fenixsoft/bookstore/resource/JAXRSResourceBase.java",
"chars": 4454,
"preview": "package com.github.fenixsoft.bookstore.resource;\n\nimport com.github.fenixsoft.bookstore.BookstoreApplication;\nimport org"
},
{
"path": "src/test/java/com/github/fenixsoft/bookstore/resource/PaymentResourceTest.java",
"chars": 2502,
"preview": "package com.github.fenixsoft.bookstore.resource;\n\nimport com.github.fenixsoft.bookstore.applicaiton.payment.dto.Settleme"
},
{
"path": "src/test/java/com/github/fenixsoft/bookstore/resource/ProductResourceTest.java",
"chars": 2507,
"preview": "package com.github.fenixsoft.bookstore.resource;\n\nimport com.github.fenixsoft.bookstore.domain.payment.Stockpile;\nimport"
},
{
"path": "travis_docker_push.sh",
"chars": 263,
"preview": "#!/bin/bash\necho \"$DOCKER_PASSWORD\" | docker login -u \"$DOCKER_USERNAME\" --password-stdin\ndocker build -t bookstore:mono"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the fenixsoft/monolithic_arch_springboot GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 102 files (2.1 MB), approximately 553.5k tokens, and a symbol index with 1496 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.