huanglizhi 1 жил өмнө
commit
14fe93f4a5
100 өөрчлөгдсөн 8592 нэмэгдсэн , 0 устгасан
  1. 29 0
      .gitignore
  2. 661 0
      LICENSE
  3. 125 0
      README.md
  4. 403 0
      deploy-docs/api部署.md
  5. 27 0
      deploy-docs/git部署公钥.md
  6. 74 0
      deploy-docs/ui部署.md
  7. 167 0
      deploy-docs/基础设施部署.md
  8. 469 0
      framework/pom.xml
  9. 78 0
      framework/src/main/java/com/hys/app/framework/ErpAppRunner.java
  10. 109 0
      framework/src/main/java/com/hys/app/framework/ErpConfig.java
  11. 22 0
      framework/src/main/java/com/hys/app/framework/FrameworkApplication.java
  12. 29 0
      framework/src/main/java/com/hys/app/framework/apptoken/AccessTokenCache.java
  13. 32 0
      framework/src/main/java/com/hys/app/framework/apptoken/AccessTokenGetter.java
  14. 36 0
      framework/src/main/java/com/hys/app/framework/apptoken/AccessTokenGetterFactory.java
  15. 33 0
      framework/src/main/java/com/hys/app/framework/apptoken/AccessTokenIdentifier.java
  16. 26 0
      framework/src/main/java/com/hys/app/framework/apptoken/AccessTokenManager.java
  17. 97 0
      framework/src/main/java/com/hys/app/framework/apptoken/impl/AccessTokenCacheDefaultImpl.java
  18. 88 0
      framework/src/main/java/com/hys/app/framework/apptoken/impl/AccessTokenManagerImpl.java
  19. 50 0
      framework/src/main/java/com/hys/app/framework/apptoken/model/AccessToken.java
  20. 17 0
      framework/src/main/java/com/hys/app/framework/apptoken/model/AppTypeEnum.java
  21. 17 0
      framework/src/main/java/com/hys/app/framework/auth/AuthUser.java
  22. 88 0
      framework/src/main/java/com/hys/app/framework/auth/BeanUtil.java
  23. 43 0
      framework/src/main/java/com/hys/app/framework/auth/Token.java
  24. 20 0
      framework/src/main/java/com/hys/app/framework/auth/TokenCreater.java
  25. 15 0
      framework/src/main/java/com/hys/app/framework/auth/TokenParseException.java
  26. 20 0
      framework/src/main/java/com/hys/app/framework/auth/TokenParser.java
  27. 33 0
      framework/src/main/java/com/hys/app/framework/auth/auth.uml
  28. 97 0
      framework/src/main/java/com/hys/app/framework/auth/impl/JwtTokenCreater.java
  29. 87 0
      framework/src/main/java/com/hys/app/framework/auth/impl/JwtTokenParser.java
  30. 139 0
      framework/src/main/java/com/hys/app/framework/cache/Cache.java
  31. 163 0
      framework/src/main/java/com/hys/app/framework/cache/MybatisRedisCache.java
  32. 137 0
      framework/src/main/java/com/hys/app/framework/cache/RedisCacheImpl.java
  33. 68 0
      framework/src/main/java/com/hys/app/framework/context/ApplicationContextHolder.java
  34. 117 0
      framework/src/main/java/com/hys/app/framework/context/WebInterceptorConfigurer.java
  35. 25 0
      framework/src/main/java/com/hys/app/framework/context/app/AppTypeContext.java
  36. 33 0
      framework/src/main/java/com/hys/app/framework/context/app/AppTypeEnum.java
  37. 108 0
      framework/src/main/java/com/hys/app/framework/context/instance/AppInstance.java
  38. 13 0
      framework/src/main/java/com/hys/app/framework/context/instance/HeartBeatProxy.java
  39. 33 0
      framework/src/main/java/com/hys/app/framework/context/instance/InstanceContext.java
  40. 186 0
      framework/src/main/java/com/hys/app/framework/context/instance/InstanceContextImpl.java
  41. 28 0
      framework/src/main/java/com/hys/app/framework/context/instance/ScheduledConfig.java
  42. 51 0
      framework/src/main/java/com/hys/app/framework/context/params/JacksonConfig.java
  43. 89 0
      framework/src/main/java/com/hys/app/framework/context/params/SnakeToCamelArgumentResolver.java
  44. 56 0
      framework/src/main/java/com/hys/app/framework/context/params/SnakeToCamelModelAttributeMethodProcessor.java
  45. 73 0
      framework/src/main/java/com/hys/app/framework/context/params/SnakeToCamelRequestDataBinder.java
  46. 152 0
      framework/src/main/java/com/hys/app/framework/context/request/HysRequestInterceptor.java
  47. 48 0
      framework/src/main/java/com/hys/app/framework/context/request/Shop.java
  48. 26 0
      framework/src/main/java/com/hys/app/framework/context/request/ShopContextHolder.java
  49. 49 0
      framework/src/main/java/com/hys/app/framework/context/request/ThreadContextHolder.java
  50. 66 0
      framework/src/main/java/com/hys/app/framework/context/user/AdminUserContext.java
  51. 33 0
      framework/src/main/java/com/hys/app/framework/context/user/ChainUserContext.java
  52. 48 0
      framework/src/main/java/com/hys/app/framework/context/user/DefaultUserHolder.java
  53. 42 0
      framework/src/main/java/com/hys/app/framework/context/user/ParameterServletRequestWrapper.java
  54. 57 0
      framework/src/main/java/com/hys/app/framework/context/user/SnakeCaseInterceptor.java
  55. 74 0
      framework/src/main/java/com/hys/app/framework/context/user/UserContext.java
  56. 28 0
      framework/src/main/java/com/hys/app/framework/context/user/UserHolder.java
  57. 60 0
      framework/src/main/java/com/hys/app/framework/database/BaseQueryParam.java
  58. 53 0
      framework/src/main/java/com/hys/app/framework/database/ColumnMeta.java
  59. 37 0
      framework/src/main/java/com/hys/app/framework/database/DBRuntimeException.java
  60. 271 0
      framework/src/main/java/com/hys/app/framework/database/DaoSupport.java
  61. 56 0
      framework/src/main/java/com/hys/app/framework/database/DataMeta.java
  62. 200 0
      framework/src/main/java/com/hys/app/framework/database/DataSourceConfig.java
  63. 24 0
      framework/src/main/java/com/hys/app/framework/database/DoubleMapper.java
  64. 29 0
      framework/src/main/java/com/hys/app/framework/database/DynamicField.java
  65. 23 0
      framework/src/main/java/com/hys/app/framework/database/LongMapper.java
  66. 25 0
      framework/src/main/java/com/hys/app/framework/database/ObjectNotFoundException.java
  67. 42 0
      framework/src/main/java/com/hys/app/framework/database/SqlMetaBuilder.java
  68. 24 0
      framework/src/main/java/com/hys/app/framework/database/StringMapper.java
  69. 148 0
      framework/src/main/java/com/hys/app/framework/database/WebPage.java
  70. 21 0
      framework/src/main/java/com/hys/app/framework/database/annotation/Column.java
  71. 17 0
      framework/src/main/java/com/hys/app/framework/database/annotation/Id.java
  72. 17 0
      framework/src/main/java/com/hys/app/framework/database/annotation/NotDbField.java
  73. 17 0
      framework/src/main/java/com/hys/app/framework/database/annotation/PrimaryKeyField.java
  74. 21 0
      framework/src/main/java/com/hys/app/framework/database/annotation/Table.java
  75. 559 0
      framework/src/main/java/com/hys/app/framework/database/impl/DaoSupportImpl.java
  76. 39 0
      framework/src/main/java/com/hys/app/framework/database/impl/FilterColumnMapRowMapper.java
  77. 25 0
      framework/src/main/java/com/hys/app/framework/database/impl/IRowMapperColumnFilter.java
  78. 21 0
      framework/src/main/java/com/hys/app/framework/database/impl/LowerCaseJdbcTemplate.java
  79. 39 0
      framework/src/main/java/com/hys/app/framework/database/impl/MySqlColumnMapRowMapper.java
  80. 126 0
      framework/src/main/java/com/hys/app/framework/database/impl/MySqlMetaBuilderImpl.java
  81. 52 0
      framework/src/main/java/com/hys/app/framework/database/impl/OracleColumnMapRowMapper.java
  82. 42 0
      framework/src/main/java/com/hys/app/framework/database/impl/SqlPaser.java
  83. 58 0
      framework/src/main/java/com/hys/app/framework/database/mybatisplus/DefaultDBFieldHandler.java
  84. 18 0
      framework/src/main/java/com/hys/app/framework/database/mybatisplus/MybatisAutoConfiguration.java
  85. 40 0
      framework/src/main/java/com/hys/app/framework/database/mybatisplus/base/BaseDO.java
  86. 157 0
      framework/src/main/java/com/hys/app/framework/database/mybatisplus/base/BaseMapperX.java
  87. 72 0
      framework/src/main/java/com/hys/app/framework/database/mybatisplus/base/BaseService.java
  88. 16 0
      framework/src/main/java/com/hys/app/framework/database/mybatisplus/base/BaseServiceImpl.java
  89. 56 0
      framework/src/main/java/com/hys/app/framework/database/mybatisplus/type/LongListTypeHandler.java
  90. 55 0
      framework/src/main/java/com/hys/app/framework/database/mybatisplus/type/StringListTypeHandler.java
  91. 180 0
      framework/src/main/java/com/hys/app/framework/database/mybatisplus/wrapper/LambdaQueryChainWrapperX.java
  92. 135 0
      framework/src/main/java/com/hys/app/framework/database/mybatisplus/wrapper/LambdaQueryWrapperX.java
  93. 319 0
      framework/src/main/java/com/hys/app/framework/database/mybatisplus/wrapper/MPJLambdaWrapperX.java
  94. 170 0
      framework/src/main/java/com/hys/app/framework/database/mybatisplus/wrapper/QueryChainWrapperX.java
  95. 140 0
      framework/src/main/java/com/hys/app/framework/database/mybatisplus/wrapper/QueryWrapperX.java
  96. 36 0
      framework/src/main/java/com/hys/app/framework/datapermission/annotation/DataPermission.java
  97. 36 0
      framework/src/main/java/com/hys/app/framework/datapermission/aop/DataPermissionAnnotationAdvisor.java
  98. 73 0
      framework/src/main/java/com/hys/app/framework/datapermission/aop/DataPermissionAnnotationInterceptor.java
  99. 65 0
      framework/src/main/java/com/hys/app/framework/datapermission/aop/DataPermissionContextHolder.java
  100. 44 0
      framework/src/main/java/com/hys/app/framework/datapermission/config/DataPermissionConfiguration.java

+ 29 - 0
.gitignore

@@ -0,0 +1,29 @@
+/.idea/
+target
+*.iml
+*.class
+.DS_Store
+*.classpath
+*.project
+*.log
+.settings/
+.factorypath
+
+log/
+node_modules/
+gifs/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+test/unit/coverage
+test/e2e/reports
+selenium-debug.log
+
+*.factorypath
+*.log
+*.out
+
+server/target
+module-erp/target
+module-openapi/target

+ 661 - 0
LICENSE

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

+ 125 - 0
README.md

@@ -0,0 +1,125 @@
+# MagicErp
+
+## ERP简介
+
+MagicErp是使用java语言开发,基于SpringBoot2.X架构体系构建的一套erp系统,主要包含采购管理、仓库管理、销售管理、商品管理、库存报表、基础配置和系统配置等功能,细节上包含采购入库、订单销售、出库发货、库存盘点、库存报损、商品调拨、库存预警等基础进销存功能,同时可以根据不同的用户角色展示不同的数据以及按钮权限。致力于为广大企业提供高效便捷的ERP库存管理系统。
+
+## 使用须知
+
+1. 允许个人学习使用。
+
+2. 允许用于学习、毕业设计等。
+
+3. 禁止将本开源的代码和资源进行任何形式任何名义的出售。
+
+4. 限制商业使用,如需联系QQ:2025555598 或微信扫一扫加我微信。
+
+   ![](https://shoptnt-bbc.oss-cn-beijing.aliyuncs.com/tnt/12261709021247_.pic.jpg?x-oss-process=style/300x300)
+
+## 交流、反馈
+
+官方QQ群:
+<a target="_blank" href="https://qm.qq.com/q/JLXlUb0GMa"><img border="0" src="//pub.idqqimg.com/wpa/images/group.png" alt="magicErp开源交流群" title="magicErp开源交流群"></a> 827733509
+
+
+
+## ui仓库地址
+
+https://gitee.com/javastore/magic-erp-ui.git
+
+
+
+## 演示
+
+### 演示地址
+
+http://erp.javastore.cn 
+
+账号:superadmin
+
+密码:111111
+
+
+## 技术栈
+
+### 服务端
+| 框架             | 备注           | 版本          |
+| ---------------- | -------------- | ------------- |
+| Spring  Boot     | 核心框架       | 2.0.1.RELEASE |
+| mybatis  plus    | 持久框架       | 3.4.2         |
+| sharding  sphere | 分库分表       | 4.1.0         |
+| Maven            | 程序构建       |               |
+| Mysql            | 数据库         | 5.6/5.7       |
+| RabbitMQ         | 消息中间件AMQP | 3.x(3.6.14)   |
+| Redis            | 缓存           | 5.x           |
+| Spring Security  | 安全框架       | 2.0.1.RELEASE |
+| Druid            | 数据库连接池   | 1.1.22        |
+| xxl-job          | 定时任务       | 2.0.0         |
+### 客户端
+| 框架              | 备注         | 版本   |
+| ----------------- | ------------ | ------ |
+| webpack           | 构建工具     | 3.10.0 |
+| ES6               | JS版本       |        |
+| Vue.js            | 基础JS框架   | 2.6.14 |
+| jQuery            | 辅助JS库     | 2.1.4  |
+| Vue Router        | 路由管理     | 3.0.1  |
+| Vuex              | 状态管理     | 3.0.1  |
+| Element UI        | 基础UI库     | 2.15.5 |
+| vue-element-admin | UI界面基于   |        |
+| Axios             | 网络请求     | 0.18.0 |
+| Scss              | CSS预处理    | 4.13.0 |
+| ESLint            | 代码检查     | 4.13.1 |
+| ECharts           | 报表系统     | 3.8.5  |
+| 百度 UEditor      | 富文本编辑器 |        |
+| 百度 Web Uploader | 图片上传插件 |        |
+
+## 功能介绍
+| 功能   | 描述                                                                                           |
+|------|----------------------------------------------------------------------------------------------|
+| 采购管理 | 包括采购计划、合同管理、采购入库、供应商退货、供应商结算等采购相关模块,管理员可根据采购计划以及和三方签订的采购合同进行采购入库,和供应商根据采购入库单进行结算。            |
+| 仓库管理 | 包括商品调拨、商品借出、商品换货、库存盘点、库存报损、库存预警等库存相关模块,管理员可以将商品在不同的仓库之间进行调拨,对单个仓库商品进行盘点,对需要商品进行报损和查看库存预警的商品。 |
+| 库存报表 | 包括库存统计、库存成本统计、总入库统计、供应商退货统计、出库统计、订单退货统计、调拨统计、库存调整统计、商品换货统计等库存变动报表。                           |
+| 销售管理 | 包括订单列表、出库管理、订单退货模块,管理员可以创建销售订单,对订单进行发货、付款处理,也可对订单进行退货处理。                                     |
+| 商品管理 | 包括商品档案、规格管理、品牌管理模块,管理员新增/编辑商品基础信息,维护库存预警数量,管理商品分类、规格参数、品牌等信息。                                |
+| 基础设置 | 包括供应商管理、营销经理管理、仓库管理、编号规则设置、会员管理、支付方式管理、地区管理、物流公司管理、门店管理、运费模板模块。                              |
+| 系统管理 | 包括系统设置、存储方案、数据字典、日志管理、openapi秘钥配置、人员管理、权限管理、菜单维护、部门管理、岗位管理模块。                                |
+
+
+
+## 部署文档
+
+### 原生部署
+
+[基础设施部署](deploy-docs/基础设施部署.md)
+
+[api部署](deploy-docs/api部署.md)
+
+[ui部署](deploy-docs/ui部署.md)
+
+[git公钥部署](deploy-docs/git公钥部署.md)
+
+### 宝塔方式部署
+
+[宝塔部署](https://blog.csdn.net/MagicErp/article/details/136940050?spm=1001.2014.3001.5501)
+
+## 效果图
+
+![首页](https://hys-project.oss-cn-beijing.aliyuncs.com/erp/%E9%A6%96%E9%A1%B5.png)
+![采购入库](https://hys-project.oss-cn-beijing.aliyuncs.com/erp/%E9%87%87%E8%B4%AD%E5%85%A5%E5%BA%93.png)
+![订单退货](https://hys-project.oss-cn-beijing.aliyuncs.com/erp/%E8%AE%A2%E5%8D%95%E9%80%80%E8%B4%A7.png)
+![订单列表](https://hys-project.oss-cn-beijing.aliyuncs.com/erp/%E8%AE%A2%E5%8D%95%E5%88%97%E8%A1%A8.png)
+![新增商品](https://hys-project.oss-cn-beijing.aliyuncs.com/erp/%E6%96%B0%E5%A2%9E%E5%95%86%E5%93%81.png)
+![库存盘点](https://hys-project.oss-cn-beijing.aliyuncs.com/erp/%E5%BA%93%E5%AD%98%E7%9B%98%E7%82%B9.png)
+![商品调拨](https://hys-project.oss-cn-beijing.aliyuncs.com/erp/%E5%95%86%E5%93%81%E8%B0%83%E6%8B%A8.png)
+![商品列表](https://hys-project.oss-cn-beijing.aliyuncs.com/erp/%E5%95%86%E5%93%81%E5%88%97%E8%A1%A8.png)
+![创建订单](https://hys-project.oss-cn-beijing.aliyuncs.com/erp/%E5%88%9B%E5%BB%BA%E8%AE%A2%E5%8D%95.png)
+![仓库管理](https://hys-project.oss-cn-beijing.aliyuncs.com/erp/%E4%BB%93%E5%BA%93%E7%AE%A1%E7%90%86.png)
+![供应商管理](https://hys-project.oss-cn-beijing.aliyuncs.com/erp/%E4%BE%9B%E5%BA%94%E5%95%86%E7%AE%A1%E7%90%86.png)
+![供应商结算](https://hys-project.oss-cn-beijing.aliyuncs.com/erp/%E4%BE%9B%E5%BA%94%E5%95%86%E7%BB%93%E7%AE%97.png)
+![供应商退货](https://hys-project.oss-cn-beijing.aliyuncs.com/erp/%E4%BE%9B%E5%BA%94%E5%95%86%E9%80%80%E8%B4%A7.png)
+![入库统计](https://hys-project.oss-cn-beijing.aliyuncs.com/erp/%E5%85%A5%E5%BA%93%E5%95%86%E5%93%81%E7%BB%9F%E8%AE%A1.png)
+
+
+##博客
+
+[CSDN](https://blog.csdn.net/magicerp/category_12614700.html?spm=1001.2014.3001.5482)

+ 403 - 0
deploy-docs/api部署.md

@@ -0,0 +1,403 @@
+# API部署
+
+安装常用工具
+
+```bash
+sudo yum install  -y  yum-utils wget vim mtr curl telnet lsof git
+```
+
+## 安装jdk
+
+下载jdk
+
+```
+mkdir -p /opt
+cd /opt
+wget https://file.shoptnt.cn/jdk/jdk-8u281-linux-x64.tar.gz
+```
+
+解压缩并重命名:
+
+```
+tar -xzvf jdk-8u281-linux-x64.tar.gz  
+mv jdk1.8.0_281/ jdk8
+```
+
+
+> 如果不正确,可能需要退出重新登录
+
+## 安装maven
+
+~~~
+cd /opt
+wget https://mirrors.huaweicloud.com/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz
+yum install -y vim 
+~~~
+
+~~~
+tar -xzvf apache-maven-3.6.3-bin.tar.gz
+~~~
+
+配置环境变量:
+
+```
+vi /etc/profile
+```
+
+在文件的最后配置如下内容:
+
+```
+JAVA_HOME=/opt/jdk8
+JRE_HOME=/opt/jdk8/jre
+CLASS_PATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib
+MAVEN_HOME=/opt/apache-maven-3.6.3
+PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin:$MAVEN_HOME/bin
+export JAVA_HOME JRE_HOME CLASS_PATH PATH
+``` 
+> 注意可能存在已有配置,请修改为正确的路径
+
+使配置文件生效:
+
+```
+source /etc/profile
+```
+
+验证
+
+```
+java -version
+```
+
+验证
+
+```
+mvn -v
+```
+
+### 配置maven私服
+
+~~~bash
+vim /opt/apache-maven-3.6.3/conf/settings.xml
+~~~
+
+在mirrors节点中新增mirror节点:
+
+~~~xml
+<mirror>
+    <id>alimaven</id>
+    <mirrorOf>central</mirrorOf>
+    <name>aliyun maven</name>
+    <url>https://maven.aliyun.com/repository/central</url>
+</mirror>
+~~~
+
+在profiles节点中新增一个profile节点:
+
+~~~xml
+<profile>  
+  <id>alimaven</id>  
+  <repositories>  
+    <repository>  
+        <id>alimaven</id>  
+      <name>aliyun maven</name>
+      <url>https://maven.aliyun.com/repository/central</url>
+        <releases><enabled>true</enabled></releases>  
+        <snapshots><enabled>true</enabled></snapshots>  
+    </repository>  
+  </repositories>  
+</profile>  
+~~~
+
+在activeProfiles节点中新增一个activeProfile 节点:
+
+~~~xml
+<activeProfiles>
+  <activeProfile>alimaven</activeProfile>
+</activeProfiles>  
+~~~
+
+注意:以上代码不要写到注释里,请仔细检查!
+
+
+## clone源码
+
+在这之前,请您参考"[git部署公钥指南](/deploy/nok8s/create-ssh-rsa)"
+
+>  作为规范,我们约定源码全部放在  /opt/erp/source目录中,jar放在/opt/erp/server中
+
+```
+mkdir -p /opt/erp/server
+mkdir -p /opt/erp/source
+cd /opt/erp/source
+```
+
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+<Tabs>
+ <TabItem value="erp" label="erp" default>
+
+clone我们需要的源码:
+
+```
+git clone git@gitee.com:javastore/magic-erp.git
+```
+
+或者
+
+```
+git clone https://gitee.com/javastore/magic-erp.git
+```
+
+进行初次编译
+
+~~~bash
+cd /opt/erp/source/api
+mvn clean install -DskipTests -f pom.xml
+~~~
+
+
+  </TabItem>
+</Tabs>
+
+
+### 配置config
+
+```
+配置源码中 server/src/main/resources/application-dev.yml文件
+如果是生产环境则配置 application-prod.yml
+配置好后提交代码,服务器这边记得重新拉一下代码
+```
+
+### 建立其它配置文件
+
+~~~
+mkdir -p /opt/erp/server/{config,logs}
+~~~
+
+~~~
+vim /opt/erp/server/config/application.yml
+~~~
+
+粘贴如下配置
+
+~~~
+spring:
+  profiles:
+    active: dev
+~~~
+
+ 如果是生产环境,将dev改为prod     
+
+### 建立执行脚本
+
+~~~
+vim /opt/erp/server/start.sh
+~~~
+
+<Tabs>
+<TabItem value="erp" label="erp">
+
+粘贴如下内容:
+
+```sh
+#!/bin/bash
+
+#版本
+verson=7.2.2
+
+#要部署的分支
+branch=master
+
+#源文件存储目录
+source_dir=/opt/erp/source/api
+
+#程序运行时目录
+server_dir=/opt/erp/server
+mkdir -p $server_dir"/logs/"
+#要运行的api
+apis=("server")
+# 杀死进程
+killone(){
+ps -ef |grep java |grep $1  |grep -v 'grep'|awk '{print $2}'  | xargs kill -9
+echo '停止'$1
+}
+# 启动程序
+startone(){
+nohup java -Xmx512m -Xms256m -Xss1024k  -jar $1"-"$verson.jar > $server_dir"/logs/"$1".out"  2>&1 &
+echo '启动'$1
+}
+# 默认全部启动
+startall="yes"
+for api_name in  ${apis[@]}
+do
+if [[ "$1" != "" && "$1" = $api_name  && "$1" != "dontbuild" ]]
+then
+startall="no"
+break
+fi
+done
+
+echo "startall is "$startall
+
+if [[ "$startall" = "yes" ]]; then
+#杀死进程
+for api_name in  ${apis[@]}
+do
+killone "$api_name"
+echo "$api_name stop success"
+
+    done
+else
+killone "$1"
+fi
+
+#检测端口状态
+checkPort(){
+r=`netstat -ntlp | awk '{print $4}' | grep :$1`
+if [ "$r" == "" ]
+then
+return 0
+else
+return 1
+fi
+
+}
+
+if [ "$1" != "dontbuild" ]
+then
+#更新源码
+cd  $source_dir
+git reset --hard $branch
+git pull
+mvn -T 1C clean install -DskipTests -f pom.xml
+rm -rf $server_dir"/*.jar"
+
+    for api_name in  ${apis[@]}
+    do
+        mv $source_dir"/"$api_name"/target/"$api_name"-"$verson.jar $server_dir
+    done
+else
+echo "不构建,直接重启"    
+fi
+
+cd $server_dir
+
+if [[ "$startall" == "yes" ]]; then
+for api_name in  ${apis[@]}
+do
+startone "$api_name"
+done
+else
+startone "$1"
+fi
+
+#进度条
+progressBar(){
+
+label=('|' '/' '-' '\')
+
+per=$1
+ii=0
+str=''
+myTimes=$2
+text=$3
+let index=myTimes%4
+#拼接#字
+while [ $ii -le  $per ]
+do
+str=#$str
+let ii++
+done
+
+printf "[%-100s]%d%%  %s %s\r" $str $per ${label[$index]} $text
+
+
+if [[ $per == 100 ]]; then
+echo
+fi
+
+}
+
+#声明所有的服务和端口号,以便检测
+serviceMap["9999"]="server"
+
+
+totalPer=0
+
+#是否全部启动成功的标识
+all_is_ok=""
+last_text=''
+times=0
+processNum=0
+processItem=100/1 #七个任务中每个任务所占进度
+while :
+do
+all_is_ok="yes"
+progressBar $totalPer $times $last_text
+
+    #100次检查一下
+    checked=$(($times%100))
+
+    if [[ $checked == 0 ]]; then
+        for key in ${!serviceMap[*]};do
+           checkPort "$key"
+           started_result=`echo $?`
+           if [ "$started_result" == "1" ]
+           then
+             processNum=$(($processNum+1))
+             #每启动一个服务进度
+             totalPer=$(($processNum*$processItem))
+             #显示启动成功的程序
+             printf "%-57s%57s\n" ${serviceMap[$key]} '已启动' | sed 's/[ ]/-/g'
+             #将启动成功的服务移除掉
+             unset serviceMap["$key"]
+           fi
+        done
+    fi
+
+
+    sleep 0.1
+    let times++
+    if [[ ${#serviceMap[*]} == 0 ]]; then
+        progressBar 100 1 "全部启动完成"
+        break
+    fi
+done
+
+
+
+```
+
+</TabItem>
+</Tabs>
+
+
+
+
+进入脚本文件夹中:
+
+~~~
+cd /opt/erp/server
+~~~
+
+更新源码并且部署执行如下脚本:
+
+~~~
+sh start.sh
+~~~
+
+不更新源码直接重启执行如下脚本
+
+~~~
+sh start.sh dontbuild
+~~~
+
+
+### 验证
+
+
+全部启动成功后,通过以下网址检查API是否成功。
+
+ http://ip:9999/swagger-ui.html

+ 27 - 0
deploy-docs/git部署公钥.md

@@ -0,0 +1,27 @@
+## git部署公钥指南
+
+执行此命令,会生成公钥和私钥两个文件
+
+```
+ ssh-keygen -t rsa
+```
+
+执行此命令,进入目录
+
+```
+ cd /root/.ssh/
+```
+
+执行此命令,查看公钥
+
+```
+ cat id_rsa.pub
+```
+
+复制公钥信息
+
+
+登录码云-点击设置-找到安全设置下的ssh公钥-把复制的公钥粘贴到公钥出,点击确定按钮
+
+
+安全连接建立完成

+ 74 - 0
deploy-docs/ui部署.md

@@ -0,0 +1,74 @@
+# UI脚本部署
+
+## 下载源码
+
+```bash
+# 克隆项目
+mkdir /opt/erp/source && cd /opt/erp/source
+git clone https://gitee.com/javastore/magic-erp-ui.git --branch 2.0.1
+git clone https://gitee.com/bbc-se/deploy-script.git
+```
+
+## 安装基础环境
+
+> 基础环境包括nodejs、pm2、nginx
+> 注意:这个命令会禁用centos7的防火墙,如果不需要禁用防火墙,请编辑deploy.sh中第66行相关代码再运行。
+
+```bash
+cd /opt/erp/source/ui
+bash ./deploy.sh base
+```
+
+## 修改域名配置
+
+在这之前您需要规划好您的域名
+
+~~~bash
+vi /opt/erp/source/deploy-script/nginx/create-cfg.sh
+~~~
+
+修改其中的 域名及端口信息。
+
+然后进入到脚本目录下:
+
+```bash
+cd /opt/erp/source/deploy-script/nginx/
+```
+
+非SSL站点,执行脚本生成配置文件:
+
+~~~bash
+sh create-cfg.sh
+~~~
+
+SSL站点,执行脚本生成配置文件:
+
+~~~bash
+sh create-cfg.sh ssl
+~~~
+
+## 启动ui
+
+```bash
+cd /opt/erp/source/ui
+bash ./deploy.sh
+```
+
+## 启动nginx
+
+```bash
+/usr/local/nginx/sbin/nginx
+```
+
+### 验证
+
+访问相应域名
+
+如果重起nginx,则:
+
+~~~bash
+/usr/local/nginx/sbin/nginx -s reload
+~~~
+
+
+

+ 167 - 0
deploy-docs/基础设施部署.md

@@ -0,0 +1,167 @@
+# 基础设施部署
+
+### 前言
+
+> **一**、基础设施请基于Docker安装部署
+
+> **二**、请注意关闭防火墙或开放相关端口
+
+关闭防火墙
+
+```
+systemctl stop firewalld &&\
+systemctl disable firewalld
+```
+
+### 安装Docker
+
+```bash
+sudo yum install -y yum-utils device-mapper-persistent-data lvm2
+```
+
+```bash
+sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
+```
+
+```bash
+sudo yum install -y docker-ce-18.03.1.ce
+```
+
+设置开机自启,并启动docker
+
+```bash
+systemctl enable docker
+systemctl start docker
+```
+
+### 部署RabbitMQ
+
+##### 1.建立数据存储目录
+
+```bash
+mkdir -p /opt/data/mqdata
+chmod -R 777 /opt/data/mqdata
+```
+
+##### 2.运行mq
+
+> 安全起见,请修改上述 RABBITMQ_ERLANG_COOKIE 的值(任意字串)
+>
+> 默认用户名密码是guest/guest
+>
+> 建议修改guest的密码,且创建自己的vhost和用户
+
+```bash
+docker run --rm -d --hostname rabbit \
+ -p 15672:15672  -p 5672:5672 -p 25672:25672 -p 4369:4369 -p 35672:35672 \
+ -v /opt/data/mqdata:/var/lib/rabbitmq \
+ -e RABBITMQ_ERLANG_COOKIE='MY-SECRET-KEY' \
+ --name rabbitmq   registry.cn-beijing.aliyuncs.com/shoptnt/rabbitmq:3.8.9
+```
+
+### 部署Redis
+
+> 注意修改redis的密码,如果不想要密码,可以删除命令末尾的 --requirepass "123456"
+
+##### 1.建立数据存储目录
+
+```bash
+mkdir -p /opt/data/redisdata
+chmod -R 777 /opt/data/redisdata
+```
+
+##### 2.运行redis
+
+```bash
+docker run --rm -d -p 6379:6379 \
+-v /opt/data/redisdata:/data \
+--name redis registry.cn-beijing.aliyuncs.com/shoptnt/redis:6.0.10 \
+redis-server --appendonly yes --requirepass "Iayq@742295"
+```
+
+### 部署MySQL
+
+> 如果您使用阿里云数据库,则可以跳过此步骤
+>
+> 如您需要使用本地安装MySQL,请执行以下步骤
+
+##### 1.建立数据存储目录
+
+```bash
+mkdir -p /home/mysql/{conf,logs,data/mysql}
+```
+
+```bash
+vi /home/mysql/conf/my.cnf
+```
+
+将粘贴以下内容
+
+```
+[mysqld]
+pid-file        = /var/run/mysqld/mysqld.pid
+socket          = /var/run/mysqld/mysqld.sock
+datadir         = /var/lib/mysql
+secure-file-priv= NULL
+# Disabling symbolic-links is recommended to prevent assorted security risks
+symbolic-links=0
+lower_case_table_names=1
+# Custom config should go here
+!includedir /etc/mysql/conf.d/
+```
+
+##### 2.运行MySQL
+
+> 请修改 MYSQL_ROOT_PASSWORD 的值来修改root的密码
+
+```bash
+docker run --rm -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 --name mysql -v /home/mysql/conf/my.cnf:/etc/mysql/my.cnf -v /home/mysql/logs:/logs -v /home/mysql/data/mysql:/var/lib/mysql   registry.cn-beijing.aliyuncs.com/shoptnt/mysql:5.6.35
+```
+
+##### 3.创建数据库
+
+```
+CREATE DATABASE `erp`
+```
+
+##### 4.安装数据
+
+* 执行项目源码中 sql 文件夹下的 database.sql 文件,进行建表以及安装基础数据
+
+### 部署xxl-job
+
+
+
+> 在您部署的MySQL中建立xxl-job的数据库
+
+
+```
+CREATE DATABASE `xxl_job`
+```
+
+
+
+>在xxl-job数据库中执行项目源码中 sql 文件夹下的 xxl_job.sql 文件,进行建表以及安装基础数据
+
+```bash
+docker run --rm -e PARAMS="--spring.datasource.url=jdbc:mysql://ip:3306/xxl_job?Unicode=true&characterEncoding=UTF-8  --spring.datasource.username=root --spring.datasource.password=123456" -p 8080:8080 -v /tmp:/data/applogs --name xxl-job-admin  -d  registry.cn-beijing.aliyuncs.com/shoptnt/xxl-job-admin:2.2.0
+```
+
+
+> 请修改相应密码为在"安装数据"步骤中设定的xxl-job数据库的密码
+>
+> 请注意数据ip使用实际ip,不要使用127.0.0.1等字
+>
+> 注意:这里的密码不支持特殊字符
+
+
+如果要设置xxl-job的账号和密码
+
+可以在docker启动参数的PARAMS中添加
+
+```yaml
+ --xxl.job.login.username=用户名 
+ --xxl.job.login.password=密码
+```
+
+> 2.2.0版本默认的账户是admin,密码是123456

+ 469 - 0
framework/pom.xml

@@ -0,0 +1,469 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>warehouse</artifactId>
+        <groupId>com.hys.app</groupId>
+        <version>7.2.2</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>framework</artifactId>
+
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
+            <artifactId>owasp-java-html-sanitizer</artifactId>
+            <version>r136</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jsoup</groupId>
+            <artifactId>jsoup</artifactId>
+            <version>1.11.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-amqp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-config</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid</artifactId>
+            <version>1.1.22</version>
+        </dependency>
+        <dependency>
+            <groupId>io.seata</groupId>
+            <artifactId>seata-spring-boot-starter</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.8.2</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+            <version>1.8.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.10</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>2.6.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.7.0</version>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>2.8.0</version>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+            <version>2.8.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-bean-validators</artifactId>
+            <version>2.8.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+            <version>2.9.0</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.commons</groupId>
+                    <artifactId>commons-pool2</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+            <version>2.5.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjweaver</artifactId>
+            <version>1.8.9</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson</artifactId>
+            <version>3.5.5</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hibernate.validator</groupId>
+            <artifactId>hibernate-validator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.ant</groupId>
+            <artifactId>ant</artifactId>
+            <version>1.10.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>javax.mail</artifactId>
+            <version>1.6.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>2.18.3</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>3.17</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>3.17</version>
+        </dependency>
+        <dependency>
+            <groupId>org.ehcache</groupId>
+            <artifactId>ehcache</artifactId>
+            <version>3.8.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-freemarker</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.janino</groupId>
+            <artifactId>janino</artifactId>
+            <version>3.0.7</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
+            <version>${shardingsphere.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.5.4</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alipay.sdk</groupId>
+            <artifactId>alipay-easysdk</artifactId>
+            <version>2.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+            <version>8.0.3</version>
+        </dependency>
+        <!--加入log4j2依赖-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-log4j2</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.logging.log4j</groupId>
+                    <artifactId>log4j-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.logging.log4j</groupId>
+                    <artifactId>log4j-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.logging.log4j</groupId>
+                    <artifactId>log4j-slf4j-impl</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!-- 2.17.0修复漏洞版本 -->
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-api</artifactId>
+            <version>${log4j2.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+            <version>${log4j2.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-slf4j-impl</artifactId>
+            <version>${log4j2.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-spring-boot</artifactId>
+            <version>${log4j2.version}</version>
+        </dependency>
+
+        <!--websocket-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.logging.log4j</groupId>
+                    <artifactId>log4j-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.logging.log4j</groupId>
+                    <artifactId>log4j-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.logging.log4j</groupId>
+                    <artifactId>log4j-slf4j-impl</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.logging.log4j</groupId>
+                    <artifactId>log4j-to-slf4j</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct</artifactId>
+            <version>1.5.5.Final</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct-jdk8</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
+            <version>1.5.5.Final</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct-processor</artifactId>
+            <version>1.5.5.Final</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.yulichang</groupId>
+            <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
+            <version>1.4.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.3.5</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>transmittable-thread-local</artifactId>
+            <version>2.14.2</version>
+        </dependency>
+
+
+        <!-- todo 以下是从erp-core拿过来的,暂时先放这里 -->
+<!--        <dependency>-->
+<!--            <groupId>com.xuxueli</groupId>-->
+<!--            <artifactId>xxl-job-core</artifactId>-->
+<!--            <version>2.0.1</version>-->
+<!--        </dependency>-->
+
+        <dependency>
+            <groupId>com.jcraft</groupId>
+            <artifactId>jsch</artifactId>
+            <version>0.1.53</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.integration</groupId>
+            <artifactId>spring-integration-http</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>dom4j</groupId>
+            <artifactId>dom4j</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>3.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>3.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>net.sf.barcode4j</groupId>
+            <artifactId>barcode4j-light</artifactId>
+            <version>2.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.belerweb</groupId>
+            <artifactId>pinyin4j</artifactId>
+            <version>2.5.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-httpclient</groupId>
+            <artifactId>commons-httpclient</artifactId>
+            <version>3.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>23.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.xfire</groupId>
+            <artifactId>xfire-core</artifactId>
+            <version>1.2.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk16</artifactId>
+            <version>1.46</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>2.2.5</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-afs</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <!-- API, java.xml.bind module -->
+        <dependency>
+            <groupId>jakarta.xml.bind</groupId>
+            <artifactId>jakarta.xml.bind-api</artifactId>
+            <version>2.3.2</version>
+        </dependency>
+        <dependency>
+            <groupId>net.bytebuddy</groupId>
+            <artifactId>byte-buddy</artifactId>
+            <version>1.11.13</version>
+        </dependency>
+        <dependency>
+            <groupId>net.bytebuddy</groupId>
+            <artifactId>byte-buddy-agent</artifactId>
+            <version>1.11.13</version>
+            <scope>test</scope>
+        </dependency>
+        <!-- Runtime, com.sun.xml.bind module -->
+        <dependency>
+            <groupId>org.glassfish.jaxb</groupId>
+            <artifactId>jaxb-runtime</artifactId>
+            <version>2.3.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dysmsapi20170525</artifactId>
+            <version>2.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>3.0.5</version>
+        </dependency>
+    </dependencies>
+
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>docker-maven-plugin</artifactId>
+                <version>1.1.1</version>
+                <configuration>
+                    <skipDocker>true</skipDocker>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+
+</project>

+ 78 - 0
framework/src/main/java/com/hys/app/framework/ErpAppRunner.java

@@ -0,0 +1,78 @@
+//package com.hys.app.framework;
+//
+//import com.hys.app.framework.context.instance.InstanceContext;
+//import com.hys.app.framework.context.user.UserContext;
+//import com.hys.app.framework.context.user.UserHolder;
+//import com.hys.app.framework.elasticsearch.EsIndexInit;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.boot.ApplicationArguments;
+//import org.springframework.boot.ApplicationRunner;
+//import org.springframework.core.annotation.Order;
+//import org.springframework.stereotype.Component;
+//
+///**
+// * 项目启动配置
+// *
+// * @author kingapex
+// * @version 1.0
+// * @since 7.1.0
+// * 2019-05-28
+// */
+//
+//@Component
+//@Order(value = 1)
+//public class ErpAppRunner implements ApplicationRunner {
+//
+//
+//    /**
+//     * 用户信息holder,认证信息的获取者
+//     */
+//    @Autowired
+//    private UserHolder userHolder;
+//
+//    @Autowired
+//    private UserHolder chainUserHolder;
+//
+//    @Autowired
+//    private ErpConfig erpConfig;
+//
+//    @Autowired
+//    protected InstanceContext instanceContext;
+//
+//    @Autowired
+//    protected EsIndexInit esIndexInit;
+//
+//    /**
+//     * 在项目加载时指定认证信息获取者
+//     * 默认是由spring 安全上下文中获取
+//     *
+//     * @param args
+//     * @throws Exception
+//     */
+//    @Override
+//    public void run(ApplicationArguments args) throws Exception {
+//        if (erpConfig.getTokenSecret() == null) {
+//            String errorMsg = "配置异常:未配置token秘钥,请检查如下:\n";
+//            errorMsg += "===========================\n";
+//            errorMsg += "   erp.token-secret\n";
+//            errorMsg += "===========================";
+//
+//            throw new Exception(errorMsg);
+//        }
+//
+//            UserContext.setChainHolder(chainUserHolder);
+//
+//            UserContext.setHolder(userHolder);
+//
+//
+//
+//        //注册本实例
+//        instanceContext.register();
+//
+//        //初始ES索引
+////        esIndexInit.initEs();
+//
+//    }
+//
+//
+//}

+ 109 - 0
framework/src/main/java/com/hys/app/framework/ErpConfig.java

@@ -0,0 +1,109 @@
+package com.hys.app.framework;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 配置
+ *
+ * @author zh
+ * @version v7.0
+ * @date 18/4/13 下午8:19
+ * @since v7.0
+ */
+@Configuration
+@ConfigurationProperties(prefix = "erp")
+@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
+@SuppressWarnings("ConfigurationProperties")
+public class ErpConfig {
+
+    /**
+     * token加密秘钥
+     */
+    private String tokenSecret;
+
+    /**
+     * 访问token失效时间
+     */
+    @Value("${erp.timeout.accessTokenTimeout:#{null}}")
+    private Integer accessTokenTimeout;
+
+    @Value("${erp.timeout.refreshTokenTimeout:#{null}}")
+    private Integer refreshTokenTimeout;
+
+    @Value("${erp.timeout.captchaTimout:#{null}}")
+    private Integer captchaTimout;
+
+    @Value("${erp.timeout.smscodeTimout:#{null}}")
+    private Integer smscodeTimout;
+
+    @Value("${erp.debugger:#{false}}")
+    private boolean debugger;
+
+
+    public ErpConfig() {
+    }
+
+
+    @Override
+    public String toString() {
+        return "erpConfig{" +
+                "accessTokenTimeout=" + accessTokenTimeout +
+                ", refreshTokenTimeout=" + refreshTokenTimeout +
+                ", captchaTimout=" + captchaTimout +
+                ", smscodeTimout=" + smscodeTimout +
+                '}';
+    }
+
+    public String getTokenSecret() {
+        return tokenSecret;
+    }
+
+    public void setTokenSecret(String tokenSecret) {
+        this.tokenSecret = tokenSecret;
+    }
+
+    public Integer getAccessTokenTimeout() {
+        return accessTokenTimeout;
+    }
+
+    public void setAccessTokenTimeout(Integer accessTokenTimeout) {
+        this.accessTokenTimeout = accessTokenTimeout;
+    }
+
+    public Integer getRefreshTokenTimeout() {
+        return refreshTokenTimeout;
+    }
+
+    public void setRefreshTokenTimeout(Integer refreshTokenTimeout) {
+        this.refreshTokenTimeout = refreshTokenTimeout;
+    }
+
+    public Integer getCaptchaTimout() {
+        return captchaTimout;
+    }
+
+    public void setCaptchaTimout(Integer captchaTimout) {
+        this.captchaTimout = captchaTimout;
+    }
+
+    public Integer getSmscodeTimout() {
+        return smscodeTimout;
+    }
+
+    public void setSmscodeTimout(Integer smscodeTimout) {
+        this.smscodeTimout = smscodeTimout;
+    }
+
+    public boolean isDebugger() {
+        return debugger;
+    }
+
+    public void setDebugger(boolean debugger) {
+        this.debugger = debugger;
+    }
+
+}

+ 22 - 0
framework/src/main/java/com/hys/app/framework/FrameworkApplication.java

@@ -0,0 +1,22 @@
+//package com.hys.app.framework;
+//
+//import org.springframework.boot.SpringApplication;
+//import org.springframework.boot.autoconfigure.SpringBootApplication;
+//import org.springframework.scheduling.annotation.EnableScheduling;
+//
+///**
+// * Created by kingapex on 2018/3/21.
+// *
+// * @author kingapex
+// * @version 1.0
+// * @since 7.0.0
+// * 2018/3/21
+// */
+//@SpringBootApplication
+//@EnableScheduling
+//public class FrameworkApplication {
+//    public static void main(String[] args) {
+//        SpringApplication.run(FrameworkApplication.class, args);
+//    }
+//
+//}

+ 29 - 0
framework/src/main/java/com/hys/app/framework/apptoken/AccessTokenCache.java

@@ -0,0 +1,29 @@
+package com.hys.app.framework.apptoken;
+
+import com.hys.app.framework.apptoken.model.AccessToken;
+
+/**
+ * access token缓存接口<br/>
+ * 专门面向access token的缓存
+ * @author kingapex
+ * @data 2021/8/29 13:36
+ * @version 1.0
+ **/
+public interface AccessTokenCache {
+
+    /**
+     * 向缓存中压入token
+     * @param accessTokenIdentifier token的唯一标识
+     * @param accessToken 要缓存的token
+     */
+    void put(AccessTokenIdentifier accessTokenIdentifier, AccessToken accessToken);
+
+    /**
+     * 由缓存中获取token
+     * @param accessTokenIdentifier token的唯一标识
+     * @return 缓存的token
+     */
+    AccessToken get(AccessTokenIdentifier accessTokenIdentifier);
+
+
+}

+ 32 - 0
framework/src/main/java/com/hys/app/framework/apptoken/AccessTokenGetter.java

@@ -0,0 +1,32 @@
+package com.hys.app.framework.apptoken;
+
+import com.hys.app.framework.apptoken.model.AccessToken;
+import com.hys.app.framework.apptoken.model.AppTypeEnum;
+
+/**
+ * access token获取器<br/>
+ * 1、为了统一对外提供token的获取接口<br/>
+ * 2、兼容各种第三方应用access token获取的情况<br/>
+ * 3、提供统一的token缓存管理
+ *
+ * @author kingapex
+ * @data 2021/8/29 11:51
+ * @version 1.0
+ **/
+public interface AccessTokenGetter {
+
+    /**
+     * 由第三方服务api获取token
+     * @param accessTokenIdentifier token唯一标识
+     * @return
+     */
+    AccessToken getToken(AccessTokenIdentifier accessTokenIdentifier);
+
+    /**
+     * 定一个这个token getter的唯一凭证
+     * 是和AccesssTokenAppEnum 一一对应的,
+     * 也就是说有一个第三方应用,就有一个token getter
+     * @return
+     */
+    AppTypeEnum id();
+}

+ 36 - 0
framework/src/main/java/com/hys/app/framework/apptoken/AccessTokenGetterFactory.java

@@ -0,0 +1,36 @@
+package com.hys.app.framework.apptoken;
+
+import com.hys.app.framework.apptoken.model.AppTypeEnum;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * AccessTokenGetter Bean工厂
+ * 根据AccesssTokenAppEnum获取唯一的 TokenGetter
+ * @author kingapex
+ * @version 1.0
+ * @description TODO
+ * @data 2021/8/29 13:03
+ **/
+@Service
+public class AccessTokenGetterFactory {
+
+    @Autowired(required = false)
+    private List<AccessTokenGetter> getterList;
+
+    /**
+     * 根据AccesssTokenAppEnum获取唯一的 TokenGetter
+     * @param appTypeEnum token app类型枚举
+     * @return
+     */
+    public AccessTokenGetter getBean(AppTypeEnum appTypeEnum) {
+        List<AccessTokenGetter> list = getterList.stream().filter(getter -> getter.id().equals(appTypeEnum)).collect(Collectors.toList());
+        if (list.isEmpty()) {
+            return null;
+        }
+        return list.get(0);
+    }
+}

+ 33 - 0
framework/src/main/java/com/hys/app/framework/apptoken/AccessTokenIdentifier.java

@@ -0,0 +1,33 @@
+package com.hys.app.framework.apptoken;
+
+import com.hys.app.framework.apptoken.model.AppTypeEnum;
+
+/**
+ * access token唯一标识符<br/>
+ * 这个标识符会用来识别token getter、缓存token,查找token<br/>
+ * 应该保证其中build id的唯一性<b/>
+ * 这个抽象的标识符,包含了一个AccesssTokenAppEnum,即标识了由哪个token getter获取token.
+ * @author kingapex
+ * @data 2021/8/29 12:37
+ * @version 1.0
+ **/
+public  abstract class AccessTokenIdentifier {
+
+
+    /**
+     * 第三方应用
+     */
+    private AppTypeEnum appTypeEnum;
+
+
+    public AccessTokenIdentifier(AppTypeEnum appTypeEnum) {
+        this.appTypeEnum = appTypeEnum;
+    }
+
+    public AppTypeEnum getAppEnum() {
+        return this.appTypeEnum;
+    }
+
+    public abstract String buildId();
+
+}

+ 26 - 0
framework/src/main/java/com/hys/app/framework/apptoken/AccessTokenManager.java

@@ -0,0 +1,26 @@
+package com.hys.app.framework.apptoken;
+
+/**
+ * access token管理接接口
+ * @author kingapex
+ * @data 2021/8/22 16:26
+ * @version 1.0
+ **/
+public interface AccessTokenManager {
+
+    /**
+     *  获取缓存的token,如果过期会重新刷新token、缓存并返回
+     * @param accessTokenIdentifier token的唯一标识
+     *
+     * @return
+     */
+    String  getToken(AccessTokenIdentifier accessTokenIdentifier);
+
+
+    /**
+     * 强制刷新token
+     * 会调用api重新获取token,刷新token、缓存并返回
+     * @param accessTokenIdentifier token的唯一标识
+     */
+    String refreshToken(AccessTokenIdentifier accessTokenIdentifier);
+}

+ 97 - 0
framework/src/main/java/com/hys/app/framework/apptoken/impl/AccessTokenCacheDefaultImpl.java

@@ -0,0 +1,97 @@
+package com.hys.app.framework.apptoken.impl;
+
+import com.hys.app.framework.apptoken.AccessTokenCache;
+import com.hys.app.framework.apptoken.model.AccessToken;
+import com.hys.app.framework.apptoken.AccessTokenIdentifier;
+import com.hys.app.framework.cache.Cache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.hys.app.framework.util.DateUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * access token二级缓存的实现
+ * 一级缓存使用本jvm的static变量
+ * 二级缓存使用redis缓存
+ * @author kingapex
+ * @version 1.0
+ * @description TODO
+ * @data 2021/8/23 17:49
+ **/
+@Service
+public class AccessTokenCacheDefaultImpl implements AccessTokenCache {
+
+    @Autowired
+    private Cache redisCache;
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private static Map<String, AccessToken> localCache;
+    static {
+        localCache = new HashMap<>();
+    }
+
+    /**
+     * 先由本地缓存中读取token
+     * 如果不存在或已经失效则由redis中读取,如果有效则存入本地缓存
+     * 最终判断如果token有效则返回,否则返回null
+     * @param accessTokenIdentifier token的唯一标识
+     * @return
+     */
+    @Override
+    public AccessToken get(AccessTokenIdentifier accessTokenIdentifier){
+        String cacheKey = getCacheKey(accessTokenIdentifier);
+        Long now = DateUtil.getDateline();
+
+        //先从本地缓存获取token
+        AccessToken accessToken = localCache.get(cacheKey);
+
+        logger.debug("由本地缓存获取token "+ accessToken);
+
+
+        //不存在或已经失效,则由redis缓存中获取
+        if (accessToken==null || now > accessToken.getExpires()) {
+            accessToken = (AccessToken) redisCache.get(cacheKey);
+            logger.debug("由redis缓存获取token "+ accessToken);
+            //redis缓存中的token不为null 并且有效,则压入本地缓存
+            if (accessToken!=null && now < accessToken.getExpires()) {
+                localCache.put(cacheKey, accessToken);
+            }
+        }
+
+        //如果token有效,则返回,否则返回null
+        if (accessToken!=null &&  now < accessToken.getExpires()) {
+            return accessToken;
+        }
+        return null;
+    }
+
+    /**
+     * 将token同时放入redis缓存和本地缓存
+     * @param accessTokenIdentifier token的唯一标识
+     * @param accessToken 要缓存的token
+     */
+    @Override
+    public void put(AccessTokenIdentifier accessTokenIdentifier, AccessToken accessToken) {
+        String cacheKey = getCacheKey(accessTokenIdentifier);
+        localCache.put(cacheKey, accessToken);
+        redisCache.put(cacheKey, accessToken,accessToken.getExpires().intValue());
+    }
+
+    /**
+     * 获取缓存的key
+     * 本地缓存是一个map,这个key是map中的key
+     * reids缓存使用这个Key作为缓存key
+     * @param accessTokenIdentifier
+     * @return
+     */
+    private String getCacheKey(AccessTokenIdentifier accessTokenIdentifier) {
+
+        return "{access_token}_" + accessTokenIdentifier.buildId();
+    }
+
+}

+ 88 - 0
framework/src/main/java/com/hys/app/framework/apptoken/impl/AccessTokenManagerImpl.java

@@ -0,0 +1,88 @@
+package com.hys.app.framework.apptoken.impl;
+import com.hys.app.framework.apptoken.*;
+import com.hys.app.framework.apptoken.model.AccessToken;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.hys.app.framework.util.DateUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * access token管理接接口实现
+ * 对token进行统一的缓存处理
+ * @author kingapex
+ * @version 1.0
+ * @data 2021/8/22 16:33
+ **/
+@Service
+public class AccessTokenManagerImpl implements AccessTokenManager {
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+
+    @Autowired
+    private AccessTokenCache accessTokenCache;
+
+    @Autowired
+    AccessTokenGetterFactory accessTokenGetterFactory;
+
+
+
+
+    /**
+     * 从缓存处获取,如果没有由微信获取,并存缓存
+     * @return
+     */
+    @Override
+    public String getToken(AccessTokenIdentifier accessTokenIdentifier) {
+
+
+        //由缓存处获取token
+        AccessToken accessToken = accessTokenCache.get(accessTokenIdentifier);
+
+        Long now = DateUtil.getDateline();
+
+        //如果token已经失效则重新获取token
+        if ( accessToken== null || accessToken.getExpires()<=now ) {
+
+            accessToken = this.getAndCache(accessTokenIdentifier);
+
+            return accessToken.getToken();
+        }
+
+        return accessToken.getToken();
+    }
+
+
+    /**
+     * 强制刷新token并返回
+     * @return
+     */
+    @Override
+    public String refreshToken(AccessTokenIdentifier accessTokenIdentifier) {
+        logger.debug("强制刷新" );
+
+        AccessToken accessToken = this.getAndCache(accessTokenIdentifier);
+
+        return accessToken.getToken();
+    }
+
+    /**
+     * 调用第三方服务api 获取token
+     * @param accessTokenIdentifier token的唯一标识
+     * @return token
+     */
+    private AccessToken getAndCache(AccessTokenIdentifier accessTokenIdentifier) {
+
+        AccessTokenGetter accessTokenGetter = accessTokenGetterFactory.getBean(accessTokenIdentifier.getAppEnum());
+
+        //调用token获取器,由第三个服务api获取token
+        AccessToken  accessToken =accessTokenGetter.getToken(accessTokenIdentifier);
+
+        //存入缓存
+        accessTokenCache.put(accessTokenIdentifier, accessToken);
+
+        return accessToken;
+    }
+
+
+}

+ 50 - 0
framework/src/main/java/com/hys/app/framework/apptoken/model/AccessToken.java

@@ -0,0 +1,50 @@
+package com.hys.app.framework.apptoken.model;
+
+import com.hys.app.framework.util.DateUtil;
+
+import java.io.Serializable;
+
+/**
+ * 第三方应用 accessToken
+ * @author kingapex
+ * @version 1.0
+ * @data 2021/8/22 16:15
+ **/
+public class AccessToken implements Serializable {
+    private static final long serialVersionUID = 9115135330405642L;
+
+    public AccessToken(String token, Long expires) {
+        this.token = token;
+        this.expires = expires;
+    }
+
+    /**
+     * token值
+     */
+    private String token;
+
+    /**
+     * 有效期
+     */
+    private  Long expires;
+
+    public String getToken() {
+        return token;
+    }
+
+    public Long getExpires() {
+        return expires;
+    }
+
+    @Override
+    public String toString() {
+        long now = DateUtil.getDateline();
+        boolean isEnable = now<=expires;
+
+        return "AccessToken{" +
+                "token='" + token + '\'' +
+                ", expires=" + expires +
+                ",isEnable="+isEnable+
+                '}';
+    }
+}

+ 17 - 0
framework/src/main/java/com/hys/app/framework/apptoken/model/AppTypeEnum.java

@@ -0,0 +1,17 @@
+package com.hys.app.framework.apptoken.model;
+
+/**
+ * app 类型 枚举
+ * 获取access token的会根据这个枚举识别token getter
+ * 以便调用相应的第三方应用api。
+ * 如微信,企业微信,支付宝等
+ * @author kingapex
+ * @data 2021/8/29 12:56
+ * @version 1.0
+ **/
+public enum AppTypeEnum {
+
+    WeiXin,
+    QiyeWeiXin
+
+}

+ 17 - 0
framework/src/main/java/com/hys/app/framework/auth/AuthUser.java

@@ -0,0 +1,17 @@
+package com.hys.app.framework.auth;
+
+import java.util.List;
+
+/**
+ * 认证用户接口
+ * @author kingapex
+ * @version 1.0
+ * @since 7.1.0
+ * 2019-06-21
+ */
+public interface AuthUser {
+
+    List<String> getRoles();
+
+    void setRoles(List<String> roles);
+}

+ 88 - 0
framework/src/main/java/com/hys/app/framework/auth/BeanUtil.java

@@ -0,0 +1,88 @@
+package com.hys.app.framework.auth;
+
+import org.springframework.beans.BeanUtils;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author kingapex
+ * @version 1.0
+ * @since 7.1.0
+ * 2019-06-24
+ */
+
+public class BeanUtil {
+
+    public static <T> T mapToBean(Class<T> clz, Map map) {
+
+        T t = BeanUtils.instantiateClass(clz);
+
+        List<Field> fields = getFields(clz);
+        fields.forEach(field -> {
+            String fieldName = field.getName();
+            PropertyDescriptor propertyDescriptor = BeanUtils.getPropertyDescriptor(clz, fieldName);
+            Method setter = propertyDescriptor.getWriteMethod();
+            Object value = map.get(fieldName);
+            try {
+                if (setter != null) {
+                    setter.invoke(t, value);
+                }
+
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            } catch (InvocationTargetException e) {
+                e.printStackTrace();
+            }
+        });
+
+        return t;
+    }
+
+    public static List<Field> getFields(Class clz) {
+        List<Field> fields = new ArrayList<>();
+        //获取所有属性(包含父类)
+        fields = getParentField(clz, fields);
+        return fields;
+    }
+
+
+    /**
+     * 递归获取所有父类的属性
+     *
+     * @param calzz
+     * @param list
+     * @return add by liuyulei 2019-02-14
+     */
+    private static List<Field> getParentField(Class<?> calzz, List<Field> list) {
+
+        if (calzz.getSuperclass() != Object.class) {
+            getParentField(calzz.getSuperclass(), list);
+        }
+
+        Field[] fields = calzz.getDeclaredFields();
+        list.addAll(arrayConvertList(fields));
+
+        return list;
+    }
+
+
+    /**
+     * 将数组转换成List
+     *
+     * @param fields
+     * @return add by liuyulei 2019-02-14
+     */
+    private static List<Field> arrayConvertList(Field[] fields) {
+        List<Field> resultList = new ArrayList<>(fields.length);
+        Collections.addAll(resultList, fields);
+        return resultList;
+
+    }
+}

+ 43 - 0
framework/src/main/java/com/hys/app/framework/auth/Token.java

@@ -0,0 +1,43 @@
+package com.hys.app.framework.auth;
+
+/**
+ * Token
+ * @author kingapex
+ * @version 1.0
+ * @since 7.1.0
+ * 2019-06-21
+ */
+
+public class Token {
+
+
+    /**
+     * token令牌
+     */
+    private String accessToken;
+
+
+    /**
+     * 刷新token
+     */
+    private String refreshToken;
+
+
+    public String getAccessToken() {
+        return accessToken;
+    }
+
+    public void setAccessToken(String accessToken) {
+        this.accessToken = accessToken;
+    }
+
+    public String getRefreshToken() {
+        return refreshToken;
+    }
+
+    public void setRefreshToken(String refreshToken) {
+        this.refreshToken = refreshToken;
+    }
+
+
+}

+ 20 - 0
framework/src/main/java/com/hys/app/framework/auth/TokenCreater.java

@@ -0,0 +1,20 @@
+package com.hys.app.framework.auth;
+
+/**
+ * Token创建接口
+ * @author kingapex
+ * @version 1.0
+ * @since 7.1.0
+ * 2019-06-21
+ */
+public interface TokenCreater {
+
+
+    /**
+     * 创建token
+     * @param user 用户
+     * @return token
+     */
+    Token create(AuthUser user);
+
+}

+ 15 - 0
framework/src/main/java/com/hys/app/framework/auth/TokenParseException.java

@@ -0,0 +1,15 @@
+package com.hys.app.framework.auth;
+
+/**
+ * @author kingapex
+ * @version 1.0
+ * @since 7.1.0
+ * 2019-06-24
+ */
+
+public class TokenParseException extends RuntimeException {
+
+     public TokenParseException(Throwable cause ) {
+        super(cause);
+    }
+}

+ 20 - 0
framework/src/main/java/com/hys/app/framework/auth/TokenParser.java

@@ -0,0 +1,20 @@
+package com.hys.app.framework.auth;
+
+/**
+ * Token 解析器
+ * @author kingapex
+ * @version 1.0
+ * @since 7.1.0
+ * 2019-06-21
+ */
+public interface TokenParser {
+
+    /**
+     * 解析token
+     * @param token
+     * @return 用户对象
+     */
+    <T>  T parse(Class<T> clz, String token) throws TokenParseException;
+
+
+}

+ 33 - 0
framework/src/main/java/com/hys/app/framework/auth/auth.uml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Diagram>
+  <ID>JAVA</ID>
+  <OriginalElement />
+  <nodes>
+    <node x="0.0" y="0.0">com.enation.app.javashop.framework.auth.TokenCreater</node>
+    <node x="0.0" y="324.0">com.enation.app.javashop.framework.auth.Token</node>
+    <node x="262.0" y="113.0">com.enation.app.javashop.framework.auth.impl.JwtTokenParser</node>
+    <node x="267.0" y="0.0">com.enation.app.javashop.framework.auth.TokenParser</node>
+    <node x="216.0" y="324.0">com.enation.app.javashop.framework.auth.AuthUser</node>
+    <node x="0.0" y="113.0">com.enation.app.javashop.framework.auth.impl.JwtTokenCreater</node>
+  </nodes>
+  <notes />
+  <edges>
+    <edge source="com.enation.app.javashop.framework.auth.impl.JwtTokenCreater" target="com.enation.app.javashop.framework.auth.TokenCreater">
+      <point x="0.0" y="-83.0" />
+      <point x="0.0" y="31.5" />
+    </edge>
+    <edge source="com.enation.app.javashop.framework.auth.impl.JwtTokenParser" target="com.enation.app.javashop.framework.auth.TokenParser">
+      <point x="0.0" y="-70.0" />
+      <point x="0.0" y="31.5" />
+    </edge>
+  </edges>
+  <settings layout="Hierarchic Group" zoom="1.0" x="250.0" y="276.0" />
+  <SelectedNodes />
+  <Categories>
+    <Category>Methods</Category>
+    <Category>Properties</Category>
+  </Categories>
+  <SCOPE>All</SCOPE>
+  <VISIBILITY>private</VISIBILITY>
+</Diagram>
+

+ 97 - 0
framework/src/main/java/com/hys/app/framework/auth/impl/JwtTokenCreater.java

@@ -0,0 +1,97 @@
+package com.hys.app.framework.auth.impl;
+
+import com.hys.app.framework.auth.AuthUser;
+import com.hys.app.framework.auth.Token;
+import com.hys.app.framework.auth.TokenCreater;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Jwt token 创建实现
+ *
+ * @author kingapex
+ * @version 1.0
+ * @since 7.1.0
+ * 2019-06-21
+ */
+
+public class JwtTokenCreater implements TokenCreater {
+
+    private String secret;
+
+    private int accessTokenExp;
+
+    private int refreshTokenExp;
+
+    public JwtTokenCreater(String secret) {
+
+        this.secret = secret;
+
+        accessTokenExp=60*60;
+
+        //默认session失效时间为1小时:60秒 x 60 (=1分钟) * 60 (=1小时)
+        refreshTokenExp = 60 * 60 * 60;
+    }
+
+    @Override
+    public Token create(AuthUser user) {
+
+        ObjectMapper oMapper = new ObjectMapper();
+
+        Map buyerMap = oMapper.convertValue(user, HashMap.class);
+
+        //使用Calendar计算时间的加减,以免出现精度问题
+        Calendar cal = Calendar.getInstance();
+
+        //设置为当期日期
+        cal.setTime(new Date());
+        //由当前日期加上访问令牌的有效时长即为访问令牌失效时间
+        cal.add(Calendar.SECOND,accessTokenExp);
+        String accessToken = Jwts.builder()
+                .setClaims(buyerMap)
+                .setSubject("user")
+                .setExpiration( cal.getTime() )
+                .signWith(SignatureAlgorithm.HS512, secret.getBytes())
+                .compact();
+
+        //设置为当期日期
+        cal.setTime(new Date());
+        //由当前日期加上刷新令牌的有效时长即为刷新令牌失效时间
+        cal.add(Calendar.SECOND,refreshTokenExp);
+        String refreshToken = Jwts.builder()
+                .setClaims(buyerMap)
+                .setSubject("user")
+                .setExpiration( cal.getTime() )
+                .signWith(SignatureAlgorithm.HS512, secret.getBytes())
+                .compact();
+
+        Token token = new Token();
+        token.setAccessToken(accessToken);
+        token.setRefreshToken(refreshToken);
+
+
+        return token;
+    }
+
+
+    public JwtTokenCreater setSecret(String secret) {
+        this.secret = secret;
+        return  this;
+    }
+
+    public JwtTokenCreater setAccessTokenExp(int accessTokenExp) {
+        this.accessTokenExp = accessTokenExp;
+        return  this;
+    }
+
+    public JwtTokenCreater setRefreshTokenExp(int refreshTokenExp) {
+        this.refreshTokenExp = refreshTokenExp;
+        return  this;
+    }
+}

+ 87 - 0
framework/src/main/java/com/hys/app/framework/auth/impl/JwtTokenParser.java

@@ -0,0 +1,87 @@
+package com.hys.app.framework.auth.impl;
+
+import com.hys.app.framework.auth.TokenParseException;
+import com.hys.app.framework.auth.TokenParser;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * jwt token解析器
+ *
+ * @author kingapex
+ * @version 1.0
+ * @since 7.1.0
+ * 2019-06-24
+ */
+
+public class JwtTokenParser implements TokenParser {
+
+    private String secret;
+
+    private Claims claims;
+
+    public JwtTokenParser(String secret) {
+        this.secret = secret;
+    }
+
+
+    @Override
+    public <T> T parse(Class<T> clz, String token) throws TokenParseException {
+
+        try {
+            claims
+                    = Jwts.parser()
+                    .setSigningKey(secret.getBytes())
+                    .parseClaimsJws(token).getBody();
+
+            Object obj = claims.get("uid");
+            claims.put("uid", Long.valueOf(obj.toString()));
+
+            Object platformShopId = claims.get("platformShopId");
+            if (Objects.nonNull(platformShopId)) {
+                claims.put("platformShopId", Long.valueOf(platformShopId.toString()));
+            }
+
+            Object clerkId = claims.get("clerkId");
+            if(clerkId != null){
+                claims.put("clerkId", Long.parseLong(clerkId.toString()));
+            }
+            claims.put("context", new HashMap<>());
+
+            T t = cn.hutool.core.bean.BeanUtil.mapToBean(claims, clz, false);
+//            T t = BeanUtil.mapToBean(clz, claims);
+            return t;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new TokenParseException(e);
+        }
+
+    }
+
+    /**
+     * 获取过期时间
+     *
+     * @return
+     */
+    public long getExpiration() {
+        long tokenDate = getFormatDate(claims.getExpiration().toString());
+        return tokenDate;
+    }
+
+
+    private long getFormatDate(String dateFormat) {
+        try {
+            SimpleDateFormat sdf1 = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH);
+
+            return sdf1.parse(dateFormat).getTime() / 1000;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return 0;
+    }
+}

+ 139 - 0
framework/src/main/java/com/hys/app/framework/cache/Cache.java

@@ -0,0 +1,139 @@
+package com.hys.app.framework.cache;
+
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 缓存接口
+ * @author kingapex
+ *
+ */
+public interface Cache<T> {
+
+	/**
+	 * Get an item from the cache, nontransactionally
+	 * @param key
+	 * @return the cached object or <tt>null</tt>
+	 */
+	T get(Object key);
+
+
+	/**
+	 *  multiGet
+	 * @param keys  要查询的key集合
+	 * @return
+	 */
+	List  multiGet(Collection keys);
+
+	/**
+	 *  使用stringRedisTemplate批量获取
+	 * @param keys  要查询的key集合
+	 * @return
+	 */
+	List  strMultiGet(Collection keys);
+
+	/**
+	 * 批量set
+	 * @param map
+	 */
+	void multiSet(Map map);
+
+
+	/**
+	 * 批量删除
+	 * @param keys 要删除的key集合
+	 */
+	void multiDel(Collection keys);
+
+	/**
+	 * Add an item to the cache, nontransactionally, with
+	 * failfast semantics
+	 * @param key
+	 * @param value
+	 */
+	void put(Object key, T value);
+
+	/**
+	 * 往缓存中写入内容
+	 * @param key
+	 * @param value
+	 * @param exp	超时时间,单位为秒
+	 */
+	void put(Object key, T value, int exp);
+
+	/**
+	 * 删除
+	 * @param key
+	 */
+	void remove(Object key);
+
+	/**
+	 * 删除
+	 * @param key
+	 */
+	void vagueDel(Object key);
+
+	/**
+	 * Clear the cache
+	 */
+	void clear();
+
+
+	/**
+	 * 往缓存中写入内容
+	 * @param key		缓存key
+	 * @param hashKey	缓存中hashKey
+	 * @param hashValue hash值
+	 */
+	void putHash(Object key,Object hashKey,Object hashValue);
+
+	/**
+	 * 玩缓存中写入内容
+	 * @param key
+	 * @param map
+	 */
+	void putAllHash(Object key,Map map);
+
+	/**
+	 * 读取缓存值
+	 * @param key
+	 * @param hashKey
+	 * @return
+	 */
+	T getHash(Object key,Object hashKey);
+
+	/**
+	 * 移除hash中的某个key
+	 * @param key
+	 * @param hashKey
+	 */
+	void removeHash(Object key, Object hashKey);
+
+	/**
+	 * 读取缓存值
+	 * @param key
+	 * @return
+	 */
+	Map<Object,Object> getHash(Object key);
+
+
+	/**
+	 * set类型的操作 写入
+	 * @param key
+	 * @param value
+	 */
+	void setSetValue(Object key, T value);
+
+
+	/**
+	 * set类型的操作 读取
+	 * @param key
+	 * @return
+	 */
+	Set getSetValue(Object key);
+
+
+}

+ 163 - 0
framework/src/main/java/com/hys/app/framework/cache/MybatisRedisCache.java

@@ -0,0 +1,163 @@
+package com.hys.app.framework.cache;
+
+import com.hys.app.framework.context.ApplicationContextHolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.connection.RedisServerCommands;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Mybatis二级缓存,使用redis作为缓存。<br/>
+ * 所有的key都是由mybatis生成的,如:<br/>
+ * "1040379671:3603394099:com.hys.app.mapper.UserMapper.selectById:0:2147483647:SELECT id,name,age,email FROM user WHERE id=? :1287644473261592577:MybatisSqlSessionFactoryBean"
+ *
+ * @author kingapex
+ * @version 1.0
+ * @since 7.2.2
+ * 2020/7/27
+ */
+
+public class MybatisRedisCache implements org.apache.ibatis.cache.Cache {
+
+    /**
+     * 读写锁
+     */
+    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
+
+    /**
+     * 操作缓存template
+     */
+    private RedisTemplate<String, Object> redisTemplate;
+
+    /**
+     * 唯一id
+     */
+    private String id;
+
+    private final Logger logger =  LoggerFactory.getLogger(this.getClass());
+
+    /**
+     * 缓存实现构造器
+     *
+     * @param id 唯一id
+     */
+    public MybatisRedisCache(final String id) {
+        if (id == null) {
+            throw new IllegalArgumentException("Cache instances require an ID");
+        }
+        this.id = id;
+    }
+
+    /**
+     * 获取唯一id
+     *
+     * @return
+     */
+    @Override
+    public String getId() {
+        return this.id;
+    }
+
+    /**
+     * 像缓存中存入一个对象
+     *
+     * @param key
+     * @param value
+     */
+    @Override
+    public void putObject(Object key, Object value) {
+        logger.debug("存入key:"+id+"--"+ key);
+        if (value != null) {
+            getRedisTemplate().opsForHash().put(id,key,value);
+//            getRedisTemplate().opsForValue().set(getKey(key), value);
+        }
+    }
+
+
+    /**
+     * 由缓存中读取对象
+     *
+     * @param key
+     * @return
+     */
+    @Override
+    public Object getObject(Object key) {
+        logger.debug("获取key:"+id+"--"+ key);
+        try {
+            if (key != null) {
+                Object obj =getRedisTemplate().opsForHash().get(id, key);
+                return obj;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 移除一个对象,这个方法只有在发生回滚是调用。
+     * 在删除是不会调用,目的是如果发生了删除且缓存过,则查不用读库
+     * As of 3.3.0 this method is only called during a rollback
+     * for any previous value that was missing in the cache.
+     * This lets any blocking cache to release the lock that
+     * may have previously put on the key.
+     * A blocking cache puts a lock when a value is null
+     * and releases it when the value is back again.
+     * This way other threads will wait for the value to be
+     * available instead of hitting the database.
+     *
+     * @param key
+     * @return
+     */
+    @Override
+    public Object removeObject(Object key) {
+
+        logger.debug("移除key:"+id+"--"+ key);
+
+        if (key != null) {
+            getRedisTemplate().opsForHash().delete(id, key);
+        }
+        return null;
+    }
+
+    @Override
+    public void clear() {
+        logger.debug("移除相关的的key-->" + this.id + "*");
+
+        getRedisTemplate().opsForHash().getOperations().delete(id);
+
+    }
+
+    @Override
+    public int getSize() {
+
+        Long size = getRedisTemplate().execute((RedisCallback<Long>) RedisServerCommands::dbSize);
+        logger.debug("获取大小:"+size.intValue());
+        return size.intValue();
+    }
+
+    /**
+     * 为mybatis提供读写锁
+     *
+     * @return
+     */
+    @Override
+    public ReadWriteLock getReadWriteLock() {
+        return this.readWriteLock;
+    }
+
+
+    private RedisTemplate<String, Object> getRedisTemplate() {
+        if (redisTemplate == null) {
+            redisTemplate = (RedisTemplate<String, Object>) ApplicationContextHolder.getBean("redisTemplate");
+        }
+        return redisTemplate;
+    }
+
+
+}
+

+ 137 - 0
framework/src/main/java/com/hys/app/framework/cache/RedisCacheImpl.java

@@ -0,0 +1,137 @@
+package com.hys.app.framework.cache;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * redis的cache实现
+ *
+ * @author fk
+ * @version v6.4
+ * @since v6.4 2017年9月25日 下午4:32:49
+ */
+@Component
+public class RedisCacheImpl implements Cache {
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+    @Autowired
+    private StringRedisTemplate stringRedisTemplate;
+
+
+    public RedisCacheImpl() {
+
+    }
+
+    @Override
+    public Object get(Object key) {
+
+        return redisTemplate.opsForValue().get(key);
+    }
+
+    @Override
+    public List multiGet(Collection  keys) {
+       return redisTemplate.opsForValue().multiGet(keys);
+
+    }
+
+    @Override
+    public List strMultiGet(Collection keys) {
+
+        return stringRedisTemplate.opsForValue().multiGet(keys);
+    }
+
+
+    @Override
+    public void multiSet(Map map) {
+        redisTemplate.opsForValue().multiSet(map);
+    }
+
+    @Override
+    public void multiDel(Collection keys) {
+        redisTemplate.delete(keys);
+    }
+
+    @Override
+    public void put(Object key, Object value) {
+
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    @Override
+    public void put(Object key, Object value, int exp) {
+
+        redisTemplate.opsForValue().set(key, value, exp, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public void remove(Object key) {
+
+        redisTemplate.delete(key);
+    }
+
+    /**
+     * 删除
+     *
+     * @param key
+     */
+    @Override
+    public void vagueDel(Object key) {
+        Set<String> keys = redisTemplate.keys(key + "*");
+        redisTemplate.delete(keys);
+    }
+
+    @Override
+    public void clear() {
+
+        Set keys = redisTemplate.keys("*");
+        redisTemplate.delete(keys);
+    }
+
+    @Override
+    public void putHash(Object key, Object hashKey, Object hashValue) {
+        redisTemplate.opsForHash().put(key, hashKey, hashValue);
+    }
+
+    @Override
+    public void putAllHash(Object key, Map map) {
+        redisTemplate.opsForHash().putAll(key, map);
+    }
+
+    @Override
+    public Object getHash(Object key, Object hashKey) {
+        return redisTemplate.opsForHash().get(key, hashKey);
+    }
+
+    @Override
+    public void removeHash(Object key, Object hashKey) {
+        redisTemplate.opsForHash().delete(key, hashKey);
+    }
+
+    @Override
+    public Map<Object, Object> getHash(Object key) {
+        return this.redisTemplate.opsForHash().entries(key);
+    }
+
+
+    @Override
+    public void setSetValue(Object key, Object value) {
+        redisTemplate.boundSetOps(key).add(value);
+    }
+
+    @Override
+    public Set getSetValue(Object key) {
+        Set set = redisTemplate.boundSetOps(key).members();
+        return set;
+    }
+
+}

+ 68 - 0
framework/src/main/java/com/hys/app/framework/context/ApplicationContextHolder.java

@@ -0,0 +1,68 @@
+package com.hys.app.framework.context;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ * spring 上下文件获取类
+ * Created by kingapex on 2018/3/23.
+ * @author kingapex
+ * @version 1.0
+ * @since 7.0.0
+ * 2018/3/23
+ */
+@Component
+public class ApplicationContextHolder implements ApplicationContextAware {
+
+    /**
+     * 上下文对象实例
+     */
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext appContext) throws BeansException {
+        applicationContext = appContext;
+    }
+
+    /**
+     * 获取applicationContext
+     * @return  spring applicationContext
+     */
+    public static ApplicationContext getApplicationContext() {
+        return applicationContext;
+    }
+
+    /**
+     * 通过name获取 Bean.
+     * @param name bean的名字
+     * @return bean实例
+     */
+    public static Object getBean(String name){
+        return getApplicationContext().getBean(name);
+    }
+
+    /**
+     * 通过class获取Bean.
+     * @param clazz bean的类型
+     * @param <T> bean的类型
+     * @return bean实例
+     */
+    public static <T> T getBean(Class<T> clazz){
+        return getApplicationContext().getBean(clazz);
+    }
+
+    /**
+     * 通过name,以及Clazz返回指定的Bean
+     * @param name bean的名字
+     * @param clazz bean的类型
+     * @param <T>  bean的类型
+     * @return bean实例
+     */
+    public static <T> T getBean(String name,Class<T> clazz){
+        return getApplicationContext().getBean(name, clazz);
+    }
+
+}

+ 117 - 0
framework/src/main/java/com/hys/app/framework/context/WebInterceptorConfigurer.java

@@ -0,0 +1,117 @@
+package com.hys.app.framework.context;
+
+import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
+import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
+import com.hys.app.framework.context.params.SnakeToCamelArgumentResolver;
+import com.hys.app.framework.context.params.SnakeToCamelModelAttributeMethodProcessor;
+import com.hys.app.framework.context.request.HysRequestInterceptor;
+import com.hys.app.framework.context.user.SnakeCaseInterceptor;
+import com.hys.app.framework.parameter.ParameterFilter;
+import com.hys.app.framework.security.XssStringJsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import javax.servlet.DispatcherType;
+import java.util.List;
+
+/**
+ * 加载自定义的 拦截器
+ *
+ * @author Chopper
+ * @version v1.0
+ * @since v6.2 2017年3月7日 下午5:29:56
+ */
+@Configuration
+public class WebInterceptorConfigurer implements WebMvcConfigurer {
+
+    @Autowired
+    private SnakeCaseInterceptor snakeCaseInterceptor;
+
+    @Override
+    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
+
+        //参数蛇形转驼峰拦截
+        argumentResolvers.add(new SnakeToCamelModelAttributeMethodProcessor(true));
+        argumentResolvers.add(new SnakeToCamelArgumentResolver());
+
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(hysRequestInterceptor()).addPathPatterns("/**");
+        registry.addInterceptor(snakeCaseInterceptor).addPathPatterns("/admin/erp/kdn/returnBack");
+
+    }
+
+    /**
+     * 声明hysRequestInterceptor bean
+     *
+     * @return
+     */
+    @Bean
+    public HysRequestInterceptor hysRequestInterceptor() {
+        return new HysRequestInterceptor();
+    }
+
+
+    /**
+     * 自定义 ObjectMapper ,用于xss攻击过滤
+     *
+     * @param builder
+     * @return
+     */
+    @Bean
+    @Primary
+    public ObjectMapper xssObjectMapper(Jackson2ObjectMapperBuilder builder) {
+        //解析器
+        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
+        //注册xss解析器
+        SimpleModule xssModule = new SimpleModule("XssStringJsonSerializer");
+        xssModule.addSerializer(new XssStringJsonSerializer());
+        objectMapper.registerModule(xssModule);
+        //返回
+        return objectMapper;
+    }
+
+
+    /**
+     * 处理参数拦截器声明,目前只处理emoji表情拦截
+     *
+     * @return
+     */
+    @Bean
+    public FilterRegistrationBean<ParameterFilter> parameterFilter() {
+        FilterRegistrationBean<ParameterFilter> filter = new FilterRegistrationBean<>();
+        filter.setDispatcherTypes(DispatcherType.REQUEST);
+        filter.setFilter(new ParameterFilter());
+        //拦截所有请求,如果没有设置则默认“/*”
+        filter.addUrlPatterns("/*");
+        //设置注册的名称,如果没有指定会使用Bean的名称。此name也是过滤器的名称
+        filter.setName("parameterFilter");
+        //该filter在filterChain中的执行顺序
+        filter.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
+        return filter;
+    }
+
+
+    /**
+     * 雪花算法生成唯一数字的bean
+     *
+     * @return
+     */
+    @Bean
+    public IdentifierGenerator keyGenerator() {
+        return new DefaultIdentifierGenerator();
+    }
+
+
+}

+ 25 - 0
framework/src/main/java/com/hys/app/framework/context/app/AppTypeContext.java

@@ -0,0 +1,25 @@
+package com.hys.app.framework.context.app;
+
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * APP类型上下文
+ * @author 张崧
+ * @version 1.0
+ * @since 7.2.2
+ * 2021-08-26
+ */
+public class AppTypeContext {
+
+    public static AppTypeEnum get(){
+        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+//        String appTypeStr =  ThreadContextHolder.getHttpRequest().getHeader("AppType");
+        String appTypeStr =  httpServletRequest.getHeader("AppType");
+        return AppTypeEnum.valueOf(appTypeStr);
+    }
+
+
+}

+ 33 - 0
framework/src/main/java/com/hys/app/framework/context/app/AppTypeEnum.java

@@ -0,0 +1,33 @@
+package com.hys.app.framework.context.app;
+
+/**
+ * AppType枚举
+ * @author 张崧
+ * @version 1.0
+ * @since 7.2.2
+ * 2021-08-26
+ */
+public enum AppTypeEnum {
+
+    Buyer("买家端"),
+
+    Admin("平台管理端"),
+
+    Shop("门店管理端"),
+
+    Cashier("收银台"),
+
+    Guide("智慧导购");
+
+    private String description;
+
+    AppTypeEnum(String description) {
+        this.description = description;
+
+    }
+
+    public String description() {
+        return description;
+    }
+
+}

+ 108 - 0
framework/src/main/java/com/hys/app/framework/context/instance/AppInstance.java

@@ -0,0 +1,108 @@
+package com.hys.app.framework.context.instance;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+/**
+ * App 实例
+ * @author kingapex
+ * @version 1.0
+ * @since 7.2.1
+ * 2020/4/14
+ */
+
+public class AppInstance implements Serializable {
+
+    private static final long serialVersionUID = 4597928439838933735L;
+
+    /**
+     * 全局唯一实例
+     */
+    private static AppInstance instance;
+
+    /**
+     * 单例的工厂方法
+     * @return
+     */
+    public static AppInstance getInstance() {
+        if (instance == null) {
+            instance = new AppInstance();
+            instance.setUuid(UUID.randomUUID().toString());
+        }
+        return instance;
+    }
+
+    /**
+     * 保护起来,只允许工厂实例化
+     */
+    private AppInstance() {
+
+    }
+
+
+    /**
+     * work id 是唯一的一个int值,如1,2,3
+     * 不仅是在app 多实例内唯一,是全局唯一的
+     */
+    private int workId;
+
+    /**
+     * 实例标识自己用的,比如注册到中心时标识自己
+     * 以便在中心中确定是自己注册的
+     */
+    private String uuid;
+
+
+    /**
+     * app的名字,如base-api buyer-api,用来显示的
+     */
+    private String appName;
+
+    /**
+     * instance 的有效期,如果无效,应该被剔除
+     */
+    private Long expirationTime;
+
+
+    public int getWorkId() {
+        return workId;
+    }
+
+    public void setWorkId(int workId) {
+        this.workId = workId;
+    }
+
+    public String getUuid() {
+        return uuid;
+    }
+
+    protected void setUuid(String uuid) {
+        this.uuid = uuid;
+    }
+
+    public String getAppName() {
+        return appName;
+    }
+
+    public void setAppName(String appName) {
+        this.appName = appName;
+    }
+
+    public Long getExpirationTime() {
+        return expirationTime;
+    }
+
+    public void setExpirationTime(Long expirationTime) {
+        this.expirationTime = expirationTime;
+    }
+
+    @Override
+    public String toString() {
+        return "AppInstance{" +
+                "workId=" + workId +
+                ", uuid='" + uuid + '\'' +
+                ", appName='" + appName + '\'' +
+                ", expirationTime=" + expirationTime +
+                '}';
+    }
+}

+ 13 - 0
framework/src/main/java/com/hys/app/framework/context/instance/HeartBeatProxy.java

@@ -0,0 +1,13 @@
+package com.hys.app.framework.context.instance;
+
+/**
+ * 心跳检测代理类
+ */
+public interface HeartBeatProxy {
+
+    /**
+     * 服务心跳检测
+     */
+    void heartbeat();
+
+}

+ 33 - 0
framework/src/main/java/com/hys/app/framework/context/instance/InstanceContext.java

@@ -0,0 +1,33 @@
+package com.hys.app.framework.context.instance;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author kingapex
+ * @version 1.0
+ * @since 7.1.0
+ * 2020/4/15
+ */
+
+public interface InstanceContext {
+
+    /**
+     * 读取app列表
+     * @return
+     */
+    Set<String> getApps();
+
+    /**
+     * 获取实例列表
+     * @return
+     */
+    List<AppInstance> getInstances();
+
+    /**
+     * 注册本实例
+     */
+    void register();
+
+
+}

+ 186 - 0
framework/src/main/java/com/hys/app/framework/context/instance/InstanceContextImpl.java

@@ -0,0 +1,186 @@
+package com.hys.app.framework.context.instance;
+
+import com.hys.app.framework.cache.Cache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.hys.app.framework.util.DateUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+
+/**
+ * 实例上下文基于redis的实现
+ * @author kingapex
+ * @version 1.0
+ * @since 7.1.0
+ * 2020/4/15
+ */
+@Service
+public class InstanceContextImpl implements InstanceContext,HeartBeatProxy {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Autowired
+    @Lazy
+    private Cache redisCache;
+
+    /**
+     * 实例缓存key
+     */
+    public static String REDIS_KEY = "{instances}";
+
+    /**
+     * app name,由spring.application.name配置获取
+     */
+    @Value("${spring.application.name}")
+    private String appName;
+
+    @Override
+    public Set<String> getApps() {
+        List<AppInstance> instances = getInstances();
+        Set<String> list = new HashSet();
+        for (AppInstance instance : instances) {
+            list.add(instance.getAppName());
+        }
+        return list;
+    }
+
+
+    @Override
+    public List<AppInstance> getInstances() {
+        List<AppInstance> instances = (List) redisCache.get(REDIS_KEY);
+
+        if (instances == null) {
+            instances = new ArrayList<>();
+        }
+
+        return instances;
+    }
+
+    @Override
+    public void register() {
+
+        List<AppInstance> instances = getInstances();
+
+        List<Integer> workIdList = new ArrayList<>();
+
+        logger.debug("现有实例:");
+        for (AppInstance instance : instances) {
+            logger.debug(String.valueOf(instance.getWorkId()));
+            workIdList.add(instance.getWorkId());
+        }
+
+        //按大小排好序
+        Collections.sort(workIdList);
+
+        //用于记录上一个id
+        int preId = 0;
+        int newId = 0;
+
+        for (Integer workId : workIdList) {
+
+            //workId不连续,使workid补充上
+            if (workId - 1 != preId) {
+                newId = preId + 1;
+                break;
+            }
+            preId = workId;
+        }
+
+        //如果newId为0,说明workid都是连续的
+        if (newId == 0) {
+            newId = workIdList.size() + 1;
+        }
+
+        //将本实例压入list
+        AppInstance instance = AppInstance.getInstance();
+        logger.debug("此实例workId:"+newId);
+        instance.setWorkId(newId);
+
+        Long now = DateUtil.getDateline();
+        Long expirationTime = now + 30;
+        instance.setExpirationTime(expirationTime);
+
+        logger.debug("实例名称:"+appName);
+        instance.setAppName(appName);
+
+        instances.add(instance);
+
+        //更新缓存
+        redisCache.put(REDIS_KEY, instances);
+
+    }
+
+
+    /**
+     * 服务心跳
+     * 每个10秒执行
+     */
+    @Async
+    @Scheduled(fixedRate = 10000)
+    @Override
+    public void heartbeat() {
+        try {
+            checkHangUp();
+        } catch (Exception e) {
+            logger.error("服务心跳异常:",e);
+        }
+    }
+
+    /**
+     * 检测挂机实例,移除
+     */
+    private void checkHangUp() {
+        List<AppInstance> instances = getInstances();
+        if (instances == null || instances.isEmpty()) {
+            return;
+        }
+        //单例
+        AppInstance instance = AppInstance.getInstance();
+
+        Long now = DateUtil.getDateline();
+        Long expirationTime = now + 30;
+        //挂机的实例列表
+        List<AppInstance> shutdownList = new ArrayList<>();
+        //默认本实例不存在
+        boolean instanceExist = false;
+        for (AppInstance appInstance : instances) {
+
+            //找到当前的instance,并延期30秒
+            if (instance.getUuid().equals(appInstance.getUuid())) {
+                appInstance.setExpirationTime(expirationTime);
+                instanceExist = true;
+            }
+
+
+            //过期判定为超期,已经down掉了
+            if (appInstance.getExpirationTime() < now) {
+                shutdownList.add(appInstance);
+                logger.debug("挂机实例,appName:{},workId:{},uuid:{}",appInstance.getAppName(),appInstance.getWorkId(),appInstance.getUuid());
+            }
+
+        }
+
+        //移除掉down掉的instance
+        for (AppInstance appInstance : shutdownList) {
+            instances.remove(appInstance);
+        }
+
+        //更新缓存
+        redisCache.put(REDIS_KEY, instances);
+
+        //判断本实例是否存在
+        if (!instanceExist) {
+            logger.debug("服务实例{}不存在,将实例本身{}塞入缓存",appName,instance.getWorkId());
+            //如果不存在,则将本实例注册进去
+            register();
+        }
+    }
+
+
+}

+ 28 - 0
framework/src/main/java/com/hys/app/framework/context/instance/ScheduledConfig.java

@@ -0,0 +1,28 @@
+package com.hys.app.framework.context.instance;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.config.ScheduledTaskRegistrar;
+import org.springframework.stereotype.Service;
+
+/**
+ * 定时任务线程池配置
+ * @author liuyulei
+ * @version 1.0
+ * @since 7.2.2
+ * 2020/11/18 0018  14:30
+ */
+@Service
+public class ScheduledConfig implements SchedulingConfigurer {
+
+    @Autowired
+    private AsyncConfigurer asyncConfigurer;
+
+    @Override
+    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
+        scheduledTaskRegistrar.setScheduler(asyncConfigurer.getAsyncExecutor());
+//        scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
+
+    }
+}

+ 51 - 0
framework/src/main/java/com/hys/app/framework/context/params/JacksonConfig.java

@@ -0,0 +1,51 @@
+package com.hys.app.framework.context.params;
+
+import com.hys.app.framework.database.WebPage;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+
+import java.io.IOException;
+
+/**
+ * 自定义json格式转换
+ *
+ * @author kingapex
+ * @version 1.0
+ * @since 7.2.1
+ * 2020/6/14
+ */
+@Configuration
+public class JacksonConfig {
+
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer customJackson() {
+        return new Jackson2ObjectMapperBuilderCustomizer() {
+            @Override
+            public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
+
+                //定义Long型的Json 值转换,转换为String型
+                //为了适配javascript 不支持Long型的精度
+                jacksonObjectMapperBuilder.serializerByType(Long.class, new JsonSerializer() {
+                    @Override
+                    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+                        //如果是Page对象则不转换为String型,而是返回Long类型(为了防止前端分页功能报错)
+                        Object currentValue = gen.getCurrentValue();
+                        if (currentValue != null && WebPage.class.equals(currentValue.getClass())) {
+                            gen.writeNumber(String.valueOf(value));
+                        } else {
+                            gen.writeString(String.valueOf(value));
+                        }
+
+                    }
+                });
+            }
+
+        };
+    }
+
+}

+ 89 - 0
framework/src/main/java/com/hys/app/framework/context/params/SnakeToCamelArgumentResolver.java

@@ -0,0 +1,89 @@
+package com.hys.app.framework.context.params;
+
+import com.hys.app.framework.util.XssUtil;
+import com.google.common.base.CaseFormat;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.SimpleTypeConverter;
+import org.springframework.core.MethodParameter;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+/**
+ * 蛇形转驼峰参数转换器
+ * 以及防xss的参数过滤
+ * 用于基本类型
+ * @author kingapex
+ * @version 1.0
+ * @since 7.0.0
+ * 2017年5月19日上午12:45:33
+ */
+public class SnakeToCamelArgumentResolver implements HandlerMethodArgumentResolver {
+
+	/**
+	 * 默认分页大小
+	 */
+	private static final long DEFAULT_SIZE = 10;
+
+	/**
+	 * 默认开始页码
+	 */
+	private static final long DEFAULT_NO = 1;
+
+	/**
+	 * 定义只有基本类型参数才转换
+	 * @param parameter spring机制传过来的当前要处理的参数
+	 * @return 只有基本类型参数才返回true,否则返回false
+	 */
+	@Override
+	public boolean supportsParameter(MethodParameter parameter) {
+		//只处理基本类型的参数
+		return  BeanUtils.isSimpleProperty(parameter.getParameterType());
+	}
+
+
+	/**
+	 * 将蛇形参数值转换给驼峰参数值
+	 * @param parameter 要处理的参数
+	 * @param mavContainer spring mavContainer
+	 * @param webRequest  spring  webRequest
+	 * @param binderFactory spring binderFactory
+	 * @return 转换后的参数值
+	 * @throws Exception
+	 */
+	@Override
+	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
+                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
+
+		//将蛇形参数名转为驼峰参数名,再读值返回
+		String paramName  = parameter.getParameterName();
+		String camelName = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, paramName);
+		SimpleTypeConverter converter = new SimpleTypeConverter();
+
+		Object param = converter.convertIfNecessary(webRequest.getParameter(camelName),parameter.getParameterType());
+
+		//如果是字串型参数,则进行xss过滤
+		if( param instanceof String){
+			param = XssUtil.clean((String)param);
+		}
+
+		//分页相关,增加默认分页
+		if(param == null ){
+			if ("pageNo".equals(paramName)) {
+
+				return DEFAULT_NO;
+			}
+
+			if ("pageSize".equals(paramName)) {
+
+				return DEFAULT_SIZE;
+			}
+		}
+
+		return 	param;
+
+	}
+
+
+}

+ 56 - 0
framework/src/main/java/com/hys/app/framework/context/params/SnakeToCamelModelAttributeMethodProcessor.java

@@ -0,0 +1,56 @@
+package com.hys.app.framework.context.params;
+
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;
+
+import javax.servlet.ServletRequest;
+
+/**
+ * 蛇形转驼峰参数转换器
+ * 用于非基本类型
+ * Created by kingapex on 2018/3/20.
+ *
+ * @author kingapex
+ * @version 1.0
+ * @since 7.0.0
+ * 2018/3/20
+ */
+public class SnakeToCamelModelAttributeMethodProcessor extends ServletModelAttributeMethodProcessor {
+
+
+    /**
+     * 构造函数
+     *
+     * @param annotationNotRequired 注解是否必须
+     */
+    public SnakeToCamelModelAttributeMethodProcessor(boolean annotationNotRequired) {
+        super(annotationNotRequired);
+    }
+
+    /**
+     * 绑定蛇形转驼峰Binder
+     *
+     * @param binder  spring 机制传琛来的binder
+     * @param request spring机制的web request
+     */
+    @Override
+    @InitBinder
+    protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
+        SnakeToCamelRequestDataBinder camelBinder = new SnakeToCamelRequestDataBinder(binder.getTarget(), binder.getObjectName());
+        ServletRequest req= request.getNativeRequest(ServletRequest.class);
+//        Enumeration<String> paramNames = req.getParameterNames();
+//
+//        while (paramNames.hasMoreElements()){
+//            String name = paramNames.nextElement();
+//            if (name.contains("[")||name.contains("]")) {
+//                return;
+//            }
+//        }
+        camelBinder.bind(req);
+        super.bindRequestParameters(binder, request);
+    }
+
+
+}

+ 73 - 0
framework/src/main/java/com/hys/app/framework/context/params/SnakeToCamelRequestDataBinder.java

@@ -0,0 +1,73 @@
+package com.hys.app.framework.context.params;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.CaseFormat;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.PropertyValue;
+import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder;
+
+import javax.servlet.ServletRequest;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 蛇形转驼峰数据绑定器
+ * Created by kingapex on 2018/3/20.
+ *
+ * @author kingapex
+ * @version 1.0
+ * @since 7.0.0
+ * 2018/3/20
+ */
+public class SnakeToCamelRequestDataBinder extends ExtendedServletRequestDataBinder {
+
+
+    /**
+     * 构造器,根据spring机制机要求存在
+     *
+     * @param target     spring 机制传递 target
+     * @param objectName spring 机制传递 objectName
+     */
+    public SnakeToCamelRequestDataBinder(Object target, String objectName) {
+        super(target, objectName);
+    }
+
+    /**
+     * 对蛇形参数绑定值
+     *
+     * @param mpvs    spring 机制传递   mpvs
+     * @param request spring 机制传递  request
+     */
+    @Override
+    protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
+        super.addBindValues(mpvs, request);
+
+        //处理JsonProperty注释的对象
+        Class<?> targetClass = getTarget().getClass();
+        Field[] fields = targetClass.getDeclaredFields();
+        for (Field field : fields) {
+            JsonProperty jsonPropertyAnnotation = field.getAnnotation(JsonProperty.class);
+            if (jsonPropertyAnnotation != null && mpvs.contains(jsonPropertyAnnotation.value())) {
+                if (!mpvs.contains(field.getName())) {
+                    mpvs.add(field.getName(), mpvs.getPropertyValue(jsonPropertyAnnotation.value()).getValue());
+                }
+            }
+        }
+
+        List<PropertyValue> covertValues = new ArrayList<PropertyValue>();
+        for (PropertyValue propertyValue : mpvs.getPropertyValueList()) {
+            if (propertyValue.getName().contains("_")) {
+                String camelName = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, propertyValue.getName());
+                if (!mpvs.contains(camelName)) {
+                    covertValues.add(new PropertyValue(camelName, propertyValue.getValue()));
+                }
+            }
+        }
+
+
+        mpvs.getPropertyValueList().addAll(covertValues);
+    }
+
+
+}

+ 152 - 0
framework/src/main/java/com/hys/app/framework/context/request/HysRequestInterceptor.java

@@ -0,0 +1,152 @@
+package com.hys.app.framework.context.request;
+
+import com.hys.app.framework.ErpConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.hys.app.framework.util.StringUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.lang.Nullable;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.net.MalformedURLException;
+
+
+/**
+ * 上下文初始化
+ * 以及跨域的支持
+ *
+ * @author kingapex
+ * @version v1.0
+ * @since v7.0.0
+ * 2018年3月23日 上午10:26:41
+ */
+public class HysRequestInterceptor extends HandlerInterceptorAdapter {
+
+    @Autowired(required = false)
+    private ErpConfig erpConfig;
+
+    @Value("${spring.cloud.config.profile:dev}")
+    private String profile;
+
+    private AntPathMatcher matcher = new AntPathMatcher();
+
+    private static Logger logger = LoggerFactory.getLogger(HysRequestInterceptor.class);
+
+    /**
+     * 拦截request和response并放到上下文中
+     *
+     * @param request  要拦截的request
+     * @param response 要拦截的response
+     * @param handler  spring 机制传递过来的
+     * @return 不中断,继续执行,返回为true
+     * @throws Exception
+     */
+    @Override
+    public boolean preHandle(HttpServletRequest request,
+                             HttpServletResponse response, Object handler) throws Exception {
+
+        boolean flag = checkReferer(request, response);
+
+        if (flag) {
+            //request 和response存到 上下文中
+            ThreadContextHolder.setHttpResponse(response);
+            ThreadContextHolder.setHttpRequest(request);
+            String  shopHeader = request.getHeader("Chain-Shop");
+            if(!StringUtil.isEmpty(shopHeader)){
+                //解码
+                String shopInfo = java.net.URLDecoder.decode(shopHeader,"UTF-8");
+                String[] split = shopInfo.split(",");
+                String shopId  =split[0];
+                String shopName = split[1];
+                Shop shop = new Shop(Long.parseLong(shopId), shopName);
+                ShopContextHolder.setShop(shop);
+            }
+
+            return super.preHandle(request, response, handler);
+        }
+
+        return false;
+
+    }
+
+
+
+    @Value("${erp.referer.checked:false}")
+    private boolean checked;
+
+    /**
+     * 验证referer
+     *
+     * @param request
+     * @param response
+     * @return
+     */
+    private boolean checkReferer(HttpServletRequest request,
+                                 HttpServletResponse response) {
+        if (!checked) {
+            return true;
+        }
+        String referer = request.getHeader("referer");
+        String host = request.getServerName();
+        String uri = request.getRequestURI();
+
+        logger.debug("referer=" + referer);
+        logger.debug("uri=" + request.getRequestURI());
+
+        boolean flag = uri.startsWith("/client")
+                || "/load-customwords".equals(uri)
+                || "/swagger-ui.html".equals(uri)
+                || uri.startsWith("/order/pay/weixin/qr")
+                || uri.startsWith("/order/pay/weixin/status")
+                || uri.startsWith("/payment/callback")
+                || uri.startsWith("/payment/return")
+                || uri.startsWith("/debugger")
+                || uri.contains("/callback")
+                || uri.startsWith("/passport/connect");
+        if (referer == null && flag) {
+            // 状态置为404
+            return true;
+        } else if (referer == null) {
+            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+            logger.debug("本次请求的referer为空");
+            return false;
+        }
+
+        java.net.URL url = null;
+        try {
+            url = new java.net.URL(referer);
+        } catch (MalformedURLException e) {
+            // URL解析异常,也置为404
+            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+            return false;
+        }
+        // 首先判断请求域名和referer域名是否相同
+        if (!host.equals(url.getHost())) {
+            // 如果不等,判断是否在白名单中
+            logger.debug("当前referer没有加入到配置中:" + url.getHost());
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 处理完成 从上下文中移除 request 和respseon
+     *
+     * @param request
+     * @param response
+     * @param handler
+     * @param ex
+     * @throws Exception
+     */
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
+        ThreadContextHolder.remove();
+
+        super.afterCompletion(request, response, handler, ex);
+    }
+}

+ 48 - 0
framework/src/main/java/com/hys/app/framework/context/request/Shop.java

@@ -0,0 +1,48 @@
+package com.hys.app.framework.context.request;
+
+
+/**
+ * 门店
+ *
+ * @author 张崧
+ * @version v7.0
+ * @date 2021-08-26
+ * @since v7.0
+ */
+
+public class Shop {
+
+    /**
+     * 门店id
+     */
+    private Long shopId;
+    /**
+     * 门店名称
+     */
+    private String shopName;
+
+    public Long getShopId() {
+        return shopId;
+    }
+
+    public void setShopId(Long shopId) {
+        this.shopId = shopId;
+    }
+
+    public String getShopName() {
+        return shopName;
+    }
+
+    public void setShopName(String shopName) {
+        this.shopName = shopName;
+    }
+
+    public Shop() {
+        
+    }
+
+    public Shop(Long shopId, String shopName) {
+        this.setShopId(shopId);
+        this.setShopName(shopName);
+    }
+}

+ 26 - 0
framework/src/main/java/com/hys/app/framework/context/request/ShopContextHolder.java

@@ -0,0 +1,26 @@
+package com.hys.app.framework.context.request;
+
+
+
+/**
+ *  用ThreadLocal来存储Session,以便实现Session any where
+ * @author kingapex
+ * <p>2009-12-17 下午03:10:09</p>
+ * @version 1.1
+ * 新增request any where
+ */
+public class ShopContextHolder {
+
+
+	private static ThreadLocal<Shop> shopThreadLocalHolder  = new ThreadLocal<Shop>();
+
+
+	public static void setShop(Shop shop){
+
+		shopThreadLocalHolder.set(shop);
+	}
+
+	public static Shop getShop(){
+		return  shopThreadLocalHolder.get();
+	}
+}

+ 49 - 0
framework/src/main/java/com/hys/app/framework/context/request/ThreadContextHolder.java

@@ -0,0 +1,49 @@
+package com.hys.app.framework.context.request;
+
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+
+/**
+ *  用ThreadLocal来存储Session,以便实现Session any where
+ * @author kingapex
+ * <p>2009-12-17 下午03:10:09</p>
+ * @version 1.1
+ * 新增request any where
+ */
+public class ThreadContextHolder  {
+
+	private static ThreadLocal<HttpServletRequest> requestThreadLocalHolder = new ThreadLocal<HttpServletRequest>();
+	private static ThreadLocal<HttpServletResponse> responseThreadLocalHolder = new ThreadLocal<HttpServletResponse>();
+
+
+	public static void setHttpRequest(HttpServletRequest request){
+
+		requestThreadLocalHolder.set(request);
+	}
+
+	public static void setHttpResponse(HttpServletResponse response){
+		responseThreadLocalHolder.set(response);
+	}
+
+
+	public static void remove(){
+		requestThreadLocalHolder.remove();
+		responseThreadLocalHolder.remove();
+	}
+
+
+
+	public static HttpServletResponse getHttpResponse(){
+
+		return responseThreadLocalHolder.get();
+	}
+	public static HttpServletRequest getHttpRequest(){
+		return  requestThreadLocalHolder.get();
+	}
+
+
+
+}

+ 66 - 0
framework/src/main/java/com/hys/app/framework/context/user/AdminUserContext.java

@@ -0,0 +1,66 @@
+package com.hys.app.framework.context.user;
+
+import com.hys.app.framework.security.model.Admin;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+/**
+ * 用户上下文
+ * Created by kingapex on 2018/3/12.
+ *
+ * @author kingapex
+ * @version 1.0
+ * @since 7.0.0
+ * 2018/3/12
+ */
+public class AdminUserContext {
+
+    /**
+     * 获取当前管理员
+     *
+     * @return
+     */
+    public static Admin getAdmin() {
+
+
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication == null) {
+            return null;
+        }
+        Object someOne = authentication.getDetails();
+        if (someOne != null && someOne instanceof Admin) {
+            return (Admin) someOne;
+        }
+        return null;
+
+
+    }
+
+    /**
+     * 获取当前管理员id
+     *
+     * @return
+     */
+    public static Long getAdminUserId() {
+        Admin admin = getAdmin();
+        if(admin == null){
+            return null;
+        }
+        return admin.getUid();
+    }
+
+    /**
+     * 获取当前管理员账号
+     *
+     * @return
+     */
+    public static String getAdminUserName() {
+        Admin admin = getAdmin();
+        if(admin == null){
+            return null;
+        }
+        return admin.getUsername();
+    }
+
+
+}

+ 33 - 0
framework/src/main/java/com/hys/app/framework/context/user/ChainUserContext.java

@@ -0,0 +1,33 @@
+package com.hys.app.framework.context.user;
+
+import com.hys.app.framework.security.model.Clerk;
+import com.hys.app.framework.security.model.Guide;
+
+/**
+ * 用户上下文
+ * Created by kingapex on 2018/3/12.
+ *
+ * @author kingapex
+ * @version 1.0
+ * @since 7.0.0
+ * 2018/3/12
+ */
+public class ChainUserContext {
+
+
+    /**
+     * 获取导购
+     */
+    public static Guide getGuide() {
+        return (Guide) UserContext.getSeller();
+    }
+
+    /**
+     * 获取收银员
+     */
+    public static Clerk getCashier() {
+        return (Clerk) UserContext.getSeller();
+    }
+
+
+}

+ 48 - 0
framework/src/main/java/com/hys/app/framework/context/user/DefaultUserHolder.java

@@ -0,0 +1,48 @@
+package com.hys.app.framework.context.user;
+
+import com.hys.app.framework.security.model.Buyer;
+import com.hys.app.framework.security.model.Seller;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+/**
+ * 默认用户holder
+ * @author kingapex
+ * @version 1.0
+ * @since 7.1.0
+ * 2019-05-28
+ */
+@Service("userHolder")
+public class DefaultUserHolder implements  UserHolder{
+
+    @Override
+    public Seller getSeller() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication == null) {
+            return null;
+        }
+        Object someOne = authentication.getDetails();
+        if (someOne != null && someOne instanceof Seller) {
+            return (Seller) someOne;
+        }
+        return null;
+    }
+
+    @Override
+    public Buyer getBuyer() {
+
+
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication == null) {
+            return null;
+        }
+        //如果含有买家权限则读取卖家信息并返回
+        Object someOne = authentication.getDetails();
+        if (someOne != null && someOne instanceof Buyer) {
+            return (Buyer) someOne;
+        }
+
+        return null;
+    }
+}

+ 42 - 0
framework/src/main/java/com/hys/app/framework/context/user/ParameterServletRequestWrapper.java

@@ -0,0 +1,42 @@
+package com.hys.app.framework.context.user;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ParameterServletRequestWrapper extends HttpServletRequestWrapper {
+    private final Map<String, String[]> parameterMap;
+
+    public ParameterServletRequestWrapper(HttpServletRequest request, Map<String, String[]> newParams) {
+        super(request);
+        parameterMap = new HashMap<>();
+        parameterMap.putAll(newParams);
+    }
+
+    @Override
+    public String getParameter(String name) {
+        String[] values = parameterMap.get(name);
+        if (values == null || values.length == 0) {
+            return null;
+        }
+        return values[0];
+    }
+
+    @Override
+    public Map<String, String[]> getParameterMap() {
+        return Collections.unmodifiableMap(parameterMap);
+    }
+
+    @Override
+    public Enumeration<String> getParameterNames() {
+        return Collections.enumeration(parameterMap.keySet());
+    }
+
+    @Override
+    public String[] getParameterValues(String name) {
+        return parameterMap.get(name);
+    }
+}

+ 57 - 0
framework/src/main/java/com/hys/app/framework/context/user/SnakeCaseInterceptor.java

@@ -0,0 +1,57 @@
+package com.hys.app.framework.context.user;
+
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Enumeration;
+
+@Component
+public class SnakeCaseInterceptor implements HandlerInterceptor {
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        Map<String, String[]> updatedParams = new HashMap<>(request.getParameterMap());
+        Enumeration<String> paramNames = request.getParameterNames();
+
+        while (paramNames.hasMoreElements()) {
+            String paramName = paramNames.nextElement();
+            String snakeCaseName = toSnakeCase(paramName);
+            if (!paramName.equals(snakeCaseName)) {
+                updatedParams.put(snakeCaseName, updatedParams.remove(paramName));
+            }
+        }
+
+        // 替换HttpServletRequest中的参数为下划线命名
+        ParameterServletRequestWrapper wrappedRequest = new ParameterServletRequestWrapper(request, updatedParams);
+        request = wrappedRequest;
+
+        return true;
+    }
+
+    @Override
+    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
+        // 可以在这里处理响应逻辑
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+        // 请求完成后的清理工作
+    }
+
+    private String toSnakeCase(String s) {
+        StringBuilder builder = new StringBuilder();
+        for (char c : s.toCharArray()) {
+            if (Character.isUpperCase(c)) {
+                builder.append('_').append(Character.toLowerCase(c));
+            } else {
+                builder.append(c);
+            }
+        }
+        return builder.toString();
+    }
+}

+ 74 - 0
framework/src/main/java/com/hys/app/framework/context/user/UserContext.java

@@ -0,0 +1,74 @@
+package com.hys.app.framework.context.user;
+
+import com.hys.app.framework.context.app.AppTypeContext;
+import com.hys.app.framework.context.app.AppTypeEnum;
+import com.hys.app.framework.security.model.Buyer;
+import com.hys.app.framework.security.model.Seller;
+
+/**
+ * 用户上下文
+ * Created by kingapex on 2018/3/12.
+ *
+ * @author kingapex
+ * @version 1.0
+ * @since 7.0.0
+ * 2018/3/12
+ */
+public class UserContext {
+
+    private static UserHolder userHolder;
+
+
+    private static UserHolder chainUserHolder;
+
+    public static void setChainHolder(UserHolder user) {
+        chainUserHolder = user;
+    }
+
+    public static void setHolder(UserHolder user) {
+        userHolder = user;
+    }
+
+    /**
+     * 获取当前卖家
+     *
+     * @return
+     */
+    public static Seller getSeller() {
+        return userHolder.getSeller();
+    }
+
+    /**
+     * 为了方便在单元测试中测试已登录的情况,请使用此属性
+     * 如果此属性有值,买家上下文中将会直接返回此模拟对象
+     */
+    public static Buyer mockBuyer = null;
+
+    /**
+     * 获取当前买家
+     *
+     * @return
+     */
+    public static Buyer getBuyer() {
+
+        //如果有模拟对象,会直接返回此模拟对象
+        if (mockBuyer != null) {
+            return mockBuyer;
+        }
+
+        //**********************************************
+        //因为收银端登录后 需要通过会员对象获取结算信息
+        //普通会员端登录 则不需要获取结算信息
+        //所以这里将两个对象进行区分
+        AppTypeEnum appTypeEnum = AppTypeContext.get();
+
+        if (AppTypeEnum.Cashier.equals(appTypeEnum) || AppTypeEnum.Guide.equals(appTypeEnum)) {
+            return chainUserHolder.getBuyer();
+        } else {
+            return userHolder.getBuyer();
+        }
+
+    }
+
+
+}

+ 28 - 0
framework/src/main/java/com/hys/app/framework/context/user/UserHolder.java

@@ -0,0 +1,28 @@
+package com.hys.app.framework.context.user;
+
+import com.hys.app.framework.security.model.Buyer;
+import com.hys.app.framework.security.model.Seller;
+
+/**
+ * 用户信息hold接口
+ * @author kingapex
+ * @version 1.0
+ * @since 7.1.0
+ * 2019-05-28
+ */
+public interface UserHolder {
+
+    /**
+     * 获取seller
+     * @return
+     */
+    Seller getSeller();
+
+    /**
+     * 获取buyer
+     * @return
+     */
+    Buyer getBuyer();
+
+
+}

+ 60 - 0
framework/src/main/java/com/hys/app/framework/database/BaseQueryParam.java

@@ -0,0 +1,60 @@
+package com.hys.app.framework.database;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 基础查询参数
+ *
+ * @author cs
+ * @since v7.3.0
+ * 2022/10/20
+ */
+public class BaseQueryParam {
+
+    /**
+     * 不分页
+     */
+    public static final long PAGE_NONE = -1L;
+
+    /**
+     * 页码
+     */
+    @ApiModelProperty(name = "page_no", value = "页码")
+    @NotNull(message = "页码不能为空")
+    private Long pageNo = 1L;
+
+    /**
+     * 页大小
+     */
+    @ApiModelProperty(name = "page_size", value = "页大小")
+    @NotNull(message = "页大小不能为空")
+    private Long pageSize = 10L;
+
+    public String getKeyWord() {
+        return keyWord;
+    }
+
+    public void setKeyWord(String keyWord) {
+        this.keyWord = keyWord;
+    }
+
+    private String keyWord;
+
+    public Long getPageNo() {
+        return pageNo;
+    }
+
+    public void setPageNo(Long pageNo) {
+        this.pageNo = pageNo;
+    }
+
+    public Long getPageSize() {
+        return pageSize;
+    }
+
+    public void setPageSize(Long pageSize) {
+        this.pageSize = pageSize;
+    }
+}

+ 53 - 0
framework/src/main/java/com/hys/app/framework/database/ColumnMeta.java

@@ -0,0 +1,53 @@
+package com.hys.app.framework.database;
+
+/**
+ * 列数据
+ * @author Snow create in 2018/3/28
+ * @version v2.0
+ * @since v7.0.0
+ */
+public class ColumnMeta {
+
+    /**
+     * 主键名字
+     */
+    private String primaryKeyName;
+
+
+    /**
+     * 字段名
+     */
+    private Object[] names;
+
+
+    /**
+     * 字段值
+     */
+    private Object[] values;
+
+
+
+    public Object[] getNames() {
+        return names;
+    }
+
+    public void setNames(Object[] names) {
+        this.names = names;
+    }
+
+    public Object[] getValues() {
+        return values;
+    }
+
+    public void setValues(Object[] values) {
+        this.values = values;
+    }
+
+    public String getPrimaryKeyName() {
+        return primaryKeyName;
+    }
+
+    public void setPrimaryKeyName(String primaryKeyName) {
+        this.primaryKeyName = primaryKeyName;
+    }
+}

+ 37 - 0
framework/src/main/java/com/hys/app/framework/database/DBRuntimeException.java

@@ -0,0 +1,37 @@
+package com.hys.app.framework.database;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 数据库操作运行期异常
+ * @author kingapex
+ * 2010-1-6下午06:16:47
+ */
+public class DBRuntimeException extends RuntimeException {
+
+ 
+	private static final long serialVersionUID = -368646349014580765L;
+
+	private final Logger logger = LoggerFactory.getLogger(getClass());
+
+	
+	public DBRuntimeException(String message){
+		super(message);
+	}
+	public DBRuntimeException(Exception e,String sql) {
+		
+		super("数据库运行期异常");
+		e.printStackTrace();
+		logger.error("数据库运行期异常,相关sql语句为"+ sql);
+	}
+	
+	
+	public DBRuntimeException(String message,String sql) {
+		
+		super(message);
+		logger.error(message+",相关sql语句为"+ sql);
+	}
+	
+	
+}

+ 271 - 0
framework/src/main/java/com/hys/app/framework/database/DaoSupport.java

@@ -0,0 +1,271 @@
+package com.hys.app.framework.database;
+
+import org.springframework.jdbc.core.RowMapper;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 数据库操作支撑接口
+ * @author Snow create in 2018/3/21
+ * @version v2.0
+ * @since v7.0.0
+ */
+public interface DaoSupport {
+
+	/**
+	 * 执行sql语句
+	 * @param sql	sql语句
+	 * @param args	参数组
+	 */
+	int execute(String sql, Object... args) ;
+
+	/**
+	 * 查询单一结果集<br/>
+	 * 并将结果转为<code>int</code>型返回
+	 * @param sql 查询的sql语句,确定结果为一行一列,且为数字型
+	 * @param args 对应sql语句中的参数值
+	 * @return
+	 */
+	Integer queryForInt(String sql, Object... args);
+
+	/**
+	 * 查询单一结果集<br/>
+	 * 并将结果转为<code>long</code>型返回
+	 * @param sql 查询的sql语句,确保结果为一行一列,且为数字型
+	 * @param args 对应sql语句中的参数值
+	 * @return
+	 */
+	Long queryForLong(String sql, Object... args);
+
+
+	/**
+	 * 查询单一结果集<br/>
+	 * 并将结果转为<code>float</code>型返回
+	 * @param sql 查询的sql语句,确保结果为一行一列,且为数字型
+	 * @param args 对应sql语句中的参数值
+	 * @return
+	 */
+	Float queryForFloat(String sql, Object... args);
+
+
+	/**
+	 * 查询单一结果集<br/>
+	 * 并将结果转为<code>dobule</code>型返回
+	 * @param sql 查询的sql语句,确保结果为一行一列,且为数字型
+	 * @param args 对应sql语句中的参数值
+	 * @return
+	 */
+	Double queryForDouble(String sql, Object... args);
+
+
+
+
+	/**
+	 * 查询String 结果
+	 * @param sql 查询语句
+	 * @param args 参数
+	 * @return String型的结果
+	 */
+	String queryForString(String sql, Object... args);
+
+	/**
+	 * 查询单一结果集<br/>
+	 * 并将结果转为<code>Map</code>对象返回
+	 * @param sql 查询的sql语句
+	 * @param args 对应sql语句中的参数值
+	 * @return 以结果集中的列为key,值为value的<code>Map</code>
+	 */
+	@SuppressWarnings("unchecked")
+	Map queryForMap(String sql, Object... args) ;
+
+	/**
+	 * 查询多行结果集<br/>
+	 * 并将结果转为<code>List<Map></code>
+	 * @param sql 查询的sql语句
+	 * @param args 对应sql语句中的参数值
+	 * @return  列表中元素为<code>Map</code>的<code>List</code>,<br/>Map结构:以结果集中的列为key,值为value,
+	 */
+	@SuppressWarnings("unchecked")
+	List  queryForList(String sql, Object... args);
+
+
+
+	/**
+	 * 查询多行结果集<br/>
+	 * 并将结果转为<code>List<T></code>
+	 * @param sql 查询的sql语句
+	 * @param clazz <code><T></code>的Class对象
+	 * @param args 对应sql语句中的参数值
+	 * @return  列表中元素为<code>T</code>的<code>List</code>
+	 */
+	<T> List<T> queryForList(String sql, Class<T> clazz, Object... args);
+
+
+	/**
+	 * 使用rowmapper查询多行结果集<br/>
+	 * 并将结果转为<code>List<T></code>
+	 * @param sql 查询的sql语句
+	 * @param rowMapper mapper
+	 * @param args  查询参数
+	 * @param <T>   列表中元素类型
+	 * @return
+	 */
+	<T> List<T> queryForList(String sql, RowMapper<T> rowMapper, Object... args);
+
+
+	/**
+	 * 分页查询多行结果集<br/>
+	 * @param sql 查询的sql语句
+	 * @param pageNo 查询的起始页
+	 * @param pageSize  每页数量
+	 * @param args  对应sql语句中的参数值
+	 * @return 列表中元素为<code>Map</code>的<code>List</code>,<br/>Map结构:以结果集中的列为key,值为value,
+	 */
+	@SuppressWarnings("unchecked")
+	<T> List<Map> queryForListPage(String sql, long pageNo, long pageSize, Object... args);
+
+
+	/**
+	 * 分页查询
+	 * @param sql  查询的sql语句
+	 * @param pageNo 查询的起始页
+	 * @param pageSize  每页数量
+	 * @param args  对应sql语句中的参数值
+	 * @return 分页结果集对象
+	 */
+	WebPage queryForPage(String sql, long pageNo, long pageSize, Object... args) ;
+
+	/**
+	 * 分页查询
+	 * @param sql  查询的sql语句
+	 * @param countSql 用于查询总记录数的sql语句
+	 * @param pageNo 查询的起始页
+	 * @param pageSize  每页数量
+	 * @param args  对应sql语句中的参数值
+	 * @return 分页结果集对象
+	 */
+	WebPage queryForPage(String sql, String countSql, long pageNo, long pageSize, Object... args);
+
+
+	/**
+	 * 分页查询
+	 * @param sql 查询的sql语句
+	 * @param pageNo 查询的起始页
+	 * @param pageSize 每页数量
+	 * @param clazz  <code><T></code>的Class对象
+	 * @param args 对应sql语句中的参数值
+	 * @return
+	 */
+	<T> WebPage queryForPage(String sql, long pageNo, long pageSize, Class<T> clazz, Object... args);
+
+	/**
+	 * 更新数据
+	 * @param table 表名
+	 * @param fields 字段-值Map
+	 * @param where 更新条件(字段-值Map)
+	 */
+	@SuppressWarnings("unchecked")
+	int update(String table, Map fields, Map<String,?> where);
+
+	/**
+	 * 批量更新数据
+	 * @param sql 要执行的sql
+	 * @param batchArgs 相应的参数
+	 * @return
+	 */
+	int[] batchUpdate(String sql,List<Object[]> batchArgs);
+
+	/**
+	 * 批量更新数据
+	 * @param sql 要执行的sql数组
+	 * @return
+	 */
+	int[] batchUpdate(String... sql);
+
+
+	/**
+	 * 更新数据
+	 * @param table 表名
+	 * @param po 要更新的对象,保证对象的属性名和字段名对应
+	 * @param where 更新条件(字段-值Map)
+	 */
+	@SuppressWarnings("unchecked")
+	int update(String table, Object po, Map<String,?> where);
+
+
+
+	/**
+	 * 新增数据
+	 * @param table  表名
+	 * @param fields 字段-值Map
+	 */
+	@SuppressWarnings("unchecked")
+	void insert(String table, Map fields);
+
+	/**
+	 * 新增数据
+	 * @param table 表名
+	 * @param po 要新增的对象,保证对象的属性名和字段名对应
+	 */
+	void insert(String table, Object po);
+
+	/**
+	 * 读取最后插入的主键id
+	 * @param table	表名
+	 * @return
+	 */
+	long getLastId(String table);
+
+
+	/**
+	 * 执行带分页的sql语句
+	 * @param sql	sql语句
+	 * @param page	页数
+	 * @param pageSize	条数
+	 * @return
+	 */
+	String buildPageSql(String sql, long page, long pageSize);
+
+	/**
+	 * 新增数据
+	 * @param t  DO对象
+	 * @param <T>
+	 */
+	<T> void insert(T t);
+
+	/**
+	 * 修改数据
+	 * @param model	DO对象
+	 * @param id	主键ID
+	 * @param <T>
+	 */
+	<T> void update(T model,Long id);
+
+	/**
+	 * 删除数据
+     * @param <T>
+     * @param clazz    DO对象的class
+     * @param id    主键ID
+     */
+	<T> void delete(Class<T> clazz, Long id);
+
+	/**
+	 * 查询一行数据
+	 * @param <T>
+	 * @param clazz    DO对象的class
+	 * @param id    主键ID
+	 * @return
+	 */
+	<T> T queryForObject(Class<T> clazz, Long id);
+
+
+	/**
+	 * 查询一个对象
+	 * @param sql 查询 sql
+	 * @param args  sql中的查询 参数
+	 * @param clazz 对象的类型
+	 * @return 数据转后的对象
+	 */
+	<T> T queryForObject(String sql, Class<T> clazz, Object... args);
+}

+ 56 - 0
framework/src/main/java/com/hys/app/framework/database/DataMeta.java

@@ -0,0 +1,56 @@
+package com.hys.app.framework.database;
+
+import java.util.Arrays;
+
+/**
+ * 元数据
+ * @author Snow create in 2018/3/28
+ * @version v2.0
+ * @since v7.0.0
+ */
+public class DataMeta {
+
+    /**
+     * sql 语句
+     */
+    private String sql;
+
+    /**
+     * sql 语句中需要的参数
+     */
+    private Object[] paramter;
+
+    private Long primaryValue;
+
+    public String getSql() {
+        return sql;
+    }
+
+    public void setSql(String sql) {
+        this.sql = sql;
+    }
+
+    public Object[] getParamter() {
+        return paramter;
+    }
+
+    public void setParamter(Object[] paramter) {
+        this.paramter = paramter;
+    }
+
+    public Long getPrimaryValue() {
+        return primaryValue;
+    }
+
+    public void setPrimaryValue(Long primaryValue) {
+        this.primaryValue = primaryValue;
+    }
+
+    @Override
+    public String toString() {
+        return "DataMeta{" +
+                "sql='" + sql + '\'' +
+                ", paramter=" + Arrays.toString(paramter) +
+                '}';
+    }
+}

+ 200 - 0
framework/src/main/java/com/hys/app/framework/database/DataSourceConfig.java

@@ -0,0 +1,200 @@
+package com.hys.app.framework.database;
+
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import com.hys.app.framework.database.impl.DaoSupportImpl;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.sql.DataSource;
+
+/**
+ * Created by kingapex on 2018/3/6.
+ * 数据源配置
+ * @author kingapex
+ * @version 1.0
+ * @since 7.0.0
+ * 2018/3/6
+ */
+@Configuration
+@EnableTransactionManagement
+public class DataSourceConfig {
+
+    /*----------------------------------------------------------------------------*/
+    /*                           DaoSupport配置                                    */
+    /*----------------------------------------------------------------------------*/
+
+    /**
+     * 商品daoSupport
+     * @param jdbcTemplate 商品jdbcTemplate
+     * @return
+     */
+    @Bean(name = "goodsDaoSupport")
+    @Primary
+    public DaoSupport goodsDaoSupport(JdbcTemplate jdbcTemplate) {
+        DaoSupport daosupport = new DaoSupportImpl(jdbcTemplate);
+        return daosupport;
+    }
+
+    /**
+     * 交易daoSupport
+     * @param jdbcTemplate 交易jdbcTemplate
+     * @return
+     */
+    @Bean(name = "tradeDaoSupport")
+    public DaoSupport tradeDaoSupport( JdbcTemplate jdbcTemplate) {
+        DaoSupport daosupport = new DaoSupportImpl(jdbcTemplate);
+        return daosupport;
+    }
+
+
+
+    /**
+     * 会员daoSupport
+     * @param jdbcTemplate 会员jdbcTemplate
+     * @return
+     */
+    @Bean(name = "memberDaoSupport")
+    public DaoSupport memberDaoSupport(JdbcTemplate jdbcTemplate) {
+        DaoSupport daosupport = new DaoSupportImpl(jdbcTemplate);
+        return daosupport;
+    }
+
+    /**
+     * 系统daoSupport
+     * @param jdbcTemplate 系统 jdbcTemplate
+     * @return
+     */
+    @Bean(name = "systemDaoSupport")
+    public DaoSupport systemDaoSupport(JdbcTemplate jdbcTemplate) {
+        DaoSupport daosupport = new DaoSupportImpl(jdbcTemplate);
+        return daosupport;
+    }
+
+
+    /**
+     * 统计 daoSupport
+     * @param jdbcTemplate 统计jdbcTemplate
+     * @return
+     */
+    @Bean(name = "sssDaoSupport")
+    public DaoSupport sssDaoSupport(JdbcTemplate jdbcTemplate) {
+        DaoSupport daosupport = new DaoSupportImpl(jdbcTemplate);
+        return daosupport;
+    }
+
+
+    /**
+     * 分销 daoSupport
+     * @param jdbcTemplate 分销jdbcTemplate
+     * @return
+     */
+    @Bean(name = "distributionDaoSupport")
+    public DaoSupport distributionDaoSupport( JdbcTemplate jdbcTemplate) {
+        DaoSupport daosupport = new DaoSupportImpl(jdbcTemplate);
+        return daosupport;
+    }
+
+    @Bean
+    public JdbcTemplate jdbcTemplate(final DataSource dataSource) {
+        return new JdbcTemplate(dataSource);
+    }
+
+
+    /*----------------------------------------------------------------------------*/
+    /*                           事务配置                                         */
+    /*----------------------------------------------------------------------------*/
+
+    @Bean
+    public PlatformTransactionManager transactionManager(DataSource dataSource) {
+        return new DataSourceTransactionManager(dataSource);
+    }
+
+    /**
+     * 会员事务
+     * @return
+     */
+    @Bean
+    public PlatformTransactionManager memberTransactionManager(PlatformTransactionManager transactionManager) {
+        return transactionManager;
+    }
+
+    /**
+     * 商品事务
+     * @return
+     */
+    @Bean
+    @Primary
+    public PlatformTransactionManager goodsTransactionManager(PlatformTransactionManager transactionManager) {
+        return transactionManager;
+    }
+
+    /**
+     * 交易事务
+     * @return
+     */
+    @Bean
+    public PlatformTransactionManager tradeTransactionManager(PlatformTransactionManager transactionManager) {
+        return transactionManager;
+    }
+
+    /**
+     * 系统事务
+     * @return
+     */
+    @Bean
+    public PlatformTransactionManager systemTransactionManager(PlatformTransactionManager transactionManager) {
+        return transactionManager;
+    }
+
+    /**
+     * 统计事务
+     * @return
+     */
+    @Bean
+    public PlatformTransactionManager sssTransactionManager(PlatformTransactionManager transactionManager) {
+        return transactionManager;
+    }
+
+
+    /**
+     * 分销事务
+     * @return
+     */
+    @Bean
+    public PlatformTransactionManager distributionTransactionManager(PlatformTransactionManager transactionManager) {
+        return transactionManager;
+    }
+
+    /**
+     * mybatis plus 分页设置
+     * @return
+     */
+//    @Bean
+//    public PaginationInnerInterceptor paginationInterceptor() {
+//        PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();
+//        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
+//        // paginationInterceptor.setOverflow(false);
+//        // 设置最大单页限制数量,默认 500 条,-1 不受限制
+//        // paginationInterceptor.setLimit(500);
+//        // 开启 count 的 join 优化,只针对部分 left join
+//        return paginationInterceptor;
+//    }
+
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
+        // 分页插件
+        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
+        //乐观锁插件
+        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
+        return mybatisPlusInterceptor;
+    }
+
+}

+ 24 - 0
framework/src/main/java/com/hys/app/framework/database/DoubleMapper.java

@@ -0,0 +1,24 @@
+package com.hys.app.framework.database;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+/**
+ * 浮点型mapper
+ * @author kingapex
+ * @version v1.0
+ * @since v7.0.0
+ * 2018年3月23日 上午10:26:41
+ */
+public class DoubleMapper implements RowMapper<Double> {
+
+	
+	@Override
+    public Double mapRow(ResultSet rs, int rowNum) throws SQLException {
+		Double dobule = new Double(rs.getDouble(1));
+		return dobule;
+	}
+
+}

+ 29 - 0
framework/src/main/java/com/hys/app/framework/database/DynamicField.java

@@ -0,0 +1,29 @@
+package com.hys.app.framework.database;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.hys.app.framework.database.annotation.NotDbField;
+
+
+/**
+ * 动态字段
+ * @author kingapex
+ *2012-5-5下午12:46:37
+ */
+public class DynamicField {
+	
+	private Map<String,Object> fields;
+	public DynamicField(){
+		fields = new HashMap<String, Object>();
+	}
+	
+	public void addField(String name,Object value){
+		fields.put(name, value);
+	}
+	
+	@NotDbField
+	public Map<String,Object> getFields(){
+		return fields;
+	}
+}

+ 23 - 0
framework/src/main/java/com/hys/app/framework/database/LongMapper.java

@@ -0,0 +1,23 @@
+package com.hys.app.framework.database;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+/**
+ * Long mapper
+ * @author kingapex
+ * @version v1.0
+ * @since v7.0.0
+ * 2018年3月23日 上午10:26:41
+ */
+public class LongMapper implements RowMapper<Long> {
+
+	@Override
+    public Long mapRow(ResultSet rs, int rowNum) throws SQLException {
+		Long  v = rs.getLong(1);
+		return v;
+	}
+
+}

+ 25 - 0
framework/src/main/java/com/hys/app/framework/database/ObjectNotFoundException.java

@@ -0,0 +1,25 @@
+package com.hys.app.framework.database;
+
+/**
+ * 对像未找到异常<br>
+ * 多用于根据某id查询一条记录,但此记录不存在
+ * @author kingapex
+ *2010-4-9上午09:09:20
+ */
+public class ObjectNotFoundException extends DBRuntimeException {
+
+	public ObjectNotFoundException(String message) {
+		super(message);
+	}
+		
+	public ObjectNotFoundException(Exception e, String sql) {
+		super(e, sql);
+	 
+	}
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -3403302876974180460L;
+
+}

+ 42 - 0
framework/src/main/java/com/hys/app/framework/database/SqlMetaBuilder.java

@@ -0,0 +1,42 @@
+package com.hys.app.framework.database;
+
+/**
+ * 元数据构建器
+ * 构建模型构建执行sql的构建器
+ * @author Snow create in 2018/3/21
+ * @version v2.0
+ * @since v7.0.0
+ */
+public interface SqlMetaBuilder {
+
+    /**
+     * 通过一个模型,获取新增数据的元数据信息
+     * @param <T> model的类型
+     * @param model 模型
+     * @return insert的元数据
+     */
+    <T> DataMeta insert(T model);
+
+    /**
+     * 通过一个模型,获取修改数据的元数据信息
+     * @param <T>  model的类型
+     * @param model 模型
+     * @param id 主键值
+     * @return update的元数据
+     */
+    <T> DataMeta update(T model, Long id);
+
+    /**
+     * 通过Class,获取查询一行数据的sql
+     * @param clazz 模型的类型
+     * @return 查询一个模型的sql,其中已经拼接好根据主键查询的的where条件
+     */
+    String queryForModel(Class clazz);
+
+    /**
+     * 通过Class,获取删除一行数据的sql
+     * @param clazz 模型的类型
+     * @return 删除的sql语句,其中已经拼接好根据主键删除的where条件
+     */
+    String delete(Class clazz);
+}

+ 24 - 0
framework/src/main/java/com/hys/app/framework/database/StringMapper.java

@@ -0,0 +1,24 @@
+package com.hys.app.framework.database;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+/**
+ * 字符串型mapper
+ * @author kingapex
+ * @version v1.0
+ * @since v7.0.0
+ * 2018年3月23日 上午10:26:41
+ */
+public class StringMapper implements  RowMapper<String> {
+
+	
+	@Override
+    public String mapRow(ResultSet rs, int rowNum) throws SQLException {
+		String str = rs.getString(1);
+		return str;
+	}
+
+}

+ 148 - 0
framework/src/main/java/com/hys/app/framework/database/WebPage.java

@@ -0,0 +1,148 @@
+package com.hys.app.framework.database;
+
+import com.hys.app.framework.util.group.BaseGroup;
+import com.fasterxml.jackson.annotation.JsonView;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 分页对象. 包含当前页数据及分页信息如总记录数.
+ *
+ * @param <T> 数据类型
+ * @author kingapex
+ * @version v1.0
+ * @since v7.0.0
+ * 2017年8月15日 上午10:55:08
+ */
+@ApiModel(value = "数据分页对象")
+@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class WebPage<T> implements Serializable {
+
+	/**
+	 * 数据列表
+	 */
+	@ApiModelProperty(value = "列表数据")
+	@JsonView(BaseGroup.class)
+	private List<T> data;
+
+	/**
+	 * 当前页码
+	 */
+	@ApiModelProperty(value = "当前页码")
+	@JsonView(BaseGroup.class)
+	private Long pageNo;
+
+
+	/**
+	 * 分页大小
+	 */
+	@ApiModelProperty(value = "分页大小")
+	@JsonView(BaseGroup.class)
+	private Long pageSize;
+
+	/**
+	 * 总计录数
+	 */
+	@ApiModelProperty(value = "总计录数")
+	@JsonView(BaseGroup.class)
+	private Long dataTotal;
+
+
+	/**
+	 * 构造方法
+	 *
+	 * @param data      数据列表
+	 * @param pageNo    当前页码
+	 * @param pageSize  页大小
+	 * @param dataTotal 总计录数
+	 */
+	public WebPage(Long pageNo, Long dataTotal, Long pageSize, List<T> data) {
+		this.data = data;
+		this.pageNo = pageNo;
+		this.pageSize = pageSize;
+		this.dataTotal = dataTotal;
+	}
+
+
+	public WebPage() {
+	}
+
+	public List<T> getData() {
+		return data;
+	}
+
+	public void setData(List<T> data) {
+		this.data = data;
+	}
+
+	public Long getPageNo() {
+		return pageNo;
+	}
+
+	public void setPageNo(Long pageNo) {
+		this.pageNo = pageNo;
+	}
+
+	public Long getPageSize() {
+		return pageSize;
+	}
+
+	public void setPageSize(Long pageSize) {
+		this.pageSize = pageSize;
+	}
+
+	public Long getDataTotal() {
+		return dataTotal;
+	}
+
+	public void setDataTotal(Long dataTotal) {
+		this.dataTotal = dataTotal;
+	}
+
+	@Override
+	public String toString() {
+		return "WebPage{" +
+				"data=" + data +
+				", pageNo=" + pageNo +
+				", pageSize=" + pageSize +
+				", dataTotal=" + dataTotal +
+				'}';
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) {
+			return true;
+		}
+		if (o == null || getClass() != o.getClass()) {
+			return false;
+		}
+
+		WebPage<?> page = (WebPage<?>) o;
+
+		if (data != null ? !data.equals(page.data) : page.data != null) {
+			return false;
+		}
+		if (pageNo != null ? !pageNo.equals(page.pageNo) : page.pageNo != null) {
+			return false;
+		}
+		if (pageSize != null ? !pageSize.equals(page.pageSize) : page.pageSize != null) {
+			return false;
+		}
+		return dataTotal != null ? dataTotal.equals(page.dataTotal) : page.dataTotal == null;
+	}
+
+	@Override
+	public int hashCode() {
+		int result = data != null ? data.hashCode() : 0;
+		result = 31 * result + (pageNo != null ? pageNo.hashCode() : 0);
+		result = 31 * result + (pageSize != null ? pageSize.hashCode() : 0);
+		result = 31 * result + (dataTotal != null ? dataTotal.hashCode() : 0);
+		return result;
+	}
+}

+ 21 - 0
framework/src/main/java/com/hys/app/framework/database/annotation/Column.java

@@ -0,0 +1,21 @@
+package com.hys.app.framework.database.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 标识为数据库的字段
+ * @author Snow
+ */
+@Target(ElementType.FIELD) 
+@Retention(RetentionPolicy.RUNTIME) 
+public @interface Column {
+
+	String name() default "";
+
+	boolean allowNullUpdate() default  false;
+
+
+}

+ 17 - 0
framework/src/main/java/com/hys/app/framework/database/annotation/Id.java

@@ -0,0 +1,17 @@
+package com.hys.app.framework.database.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 标识为此表的主键ID
+ * @author Snow
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Id {
+
+    String name() default "";
+}

+ 17 - 0
framework/src/main/java/com/hys/app/framework/database/annotation/NotDbField.java

@@ -0,0 +1,17 @@
+package com.hys.app.framework.database.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 标识不是数据库读写的字段
+ * @author kingapex
+ * 2010-1-22下午04:08:58
+ */
+@Retention(RetentionPolicy.RUNTIME) 
+@Target(ElementType.METHOD) 
+public @interface NotDbField {
+
+}

+ 17 - 0
framework/src/main/java/com/hys/app/framework/database/annotation/PrimaryKeyField.java

@@ -0,0 +1,17 @@
+package com.hys.app.framework.database.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 标识不是数据库读写的字段
+ * @author kingapex
+ * 2010-1-22下午04:08:58
+ */
+@Retention(RetentionPolicy.RUNTIME) 
+@Target(ElementType.METHOD) 
+public @interface PrimaryKeyField {
+
+}

+ 21 - 0
framework/src/main/java/com/hys/app/framework/database/annotation/Table.java

@@ -0,0 +1,21 @@
+package com.hys.app.framework.database.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 标识为数据库的表名
+ * @author Snow
+ */
+@Target(ElementType.TYPE) 
+@Retention(RetentionPolicy.RUNTIME) 
+public @interface Table {
+
+	/**
+	 * 数据库的表名
+	 * @return
+	 */
+	String name();
+}

+ 559 - 0
framework/src/main/java/com/hys/app/framework/database/impl/DaoSupportImpl.java

@@ -0,0 +1,559 @@
+package com.hys.app.framework.database.impl;
+
+import com.hys.app.framework.database.*;
+import com.hys.app.framework.sncreator.SnCreator;
+import com.hys.app.framework.util.ReflectionUtil;
+import com.hys.app.framework.util.StringUtil;
+import org.apache.commons.collections.map.HashedMap;
+import org.apache.commons.lang.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.util.Assert;
+
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * jdbc数据库操作支撑实现类
+ * @author Snow create in 2018/3/21
+ * @version v2.0
+ * @since v7.0.0
+ */
+public class DaoSupportImpl implements DaoSupport {
+
+
+    private JdbcTemplate jdbcTemplate;
+
+	@Autowired
+	private SqlMetaBuilder sqlMetaBuilder;
+
+	private ThreadLocal<Long> lastIdThreadLocal = new ThreadLocal<>();
+
+    /**
+     * 日志记录
+     */
+	private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /**
+     * order by 语句正则
+     */
+    private  static final Pattern ORDER_PATTERN = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*", Pattern.CASE_INSENSITIVE);
+
+    /**
+     * 删除select正则
+     */
+    private  static final Pattern REMOVE_SELECT_PATTERN  = Pattern.compile("\\(.*\\)", Pattern.CASE_INSENSITIVE);
+
+	public DaoSupportImpl(){
+	}
+
+    /**
+     * 实例化jdbcTemplate
+     */
+	public DaoSupportImpl(JdbcTemplate jdbcTemplate){
+		this.jdbcTemplate= jdbcTemplate;
+	}
+
+
+	@Override
+	public int execute(String sql, Object... args) {
+		try {
+			int rowNum = this.jdbcTemplate.update(sql, args);
+
+			return rowNum;
+		} catch (Exception e) {
+			throw new DBRuntimeException(e, sql);
+		}
+	}
+
+
+	@Override
+	public long getLastId(String table) {
+		return lastIdThreadLocal.get();
+	}
+
+	@Override
+	public void insert(String table, Map fields) {
+		String sql = "";
+
+		try {
+
+			Assert.hasText(table, "表名不能为空");
+			Assert.notEmpty(fields, "字段不能为空");
+			table = quoteCol(table);
+
+			Object[] cols = fields.keySet().toArray();
+			Object[] values = new Object[cols.length];
+
+			for (int i = 0; i < cols.length; i++) {
+				if (fields.get(cols[i]) == null) {
+					values[i] = null;
+				} else {
+					values[i] = fields.get(cols[i]);
+				}
+				cols[i] = quoteCol(cols[i].toString());
+			}
+
+			sql = "INSERT INTO " + table + " (" + StringUtil.implode(", ", cols);
+
+			sql = sql + ") VALUES (" + StringUtil.implodeValue(", ", values);
+
+			sql = sql + ")";
+			jdbcTemplate.update(sql, values);
+		} catch (Exception e) {
+			this.logger.error(e.getMessage(), e);
+			throw new DBRuntimeException(e, sql);
+		}
+	}
+
+	@Autowired
+	SnCreator snCreator;
+
+	int getSubCode(String table) {
+
+		return 1;
+	}
+	@Override
+	public void insert(String table, Object po) {
+		Long id = snCreator.create(getSubCode(table));
+
+		//设置刚刚主键值到 thread local
+		lastIdThreadLocal.set(id);
+		Map poMap = new HashedMap();
+		ColumnMeta columnMeta = ReflectionUtil.getColumnMeta(po);
+
+		poMap.put(columnMeta.getPrimaryKeyName(), id);
+		Object[] columnName = columnMeta.getNames();
+		Object[] columnValue = columnMeta.getValues();
+
+		for (int i = 0; i < columnValue.length; i++) {
+			poMap.put(columnName[i],columnValue[i]);
+		}
+
+		insert(table, poMap);
+	}
+
+	@Override
+	public Integer queryForInt(String sql, Object... args) {
+		try {
+			Integer value = jdbcTemplate.queryForObject(sql, Integer.class, args);
+			return  value==null?0:value;
+		}catch(EmptyResultDataAccessException e){
+			return 0;
+		} catch (RuntimeException e) {
+			this.logger.error(e.getMessage(), e);
+			throw e;
+		}
+	}
+
+	@Override
+	public Float queryForFloat(String sql, Object... args) {
+		try {
+
+			Float value = jdbcTemplate.queryForObject(sql, Float.class, args);
+			return  value==null?0F:value;
+
+		} catch(EmptyResultDataAccessException e){
+			return 0F;
+		} catch (RuntimeException e) {
+			this.logger.error(e.getMessage(), e);
+			throw e;
+		}
+	}
+
+	@Override
+	public Long queryForLong(String sql, Object... args) {
+		try {
+			Long value = jdbcTemplate.queryForObject(sql, Long.class, args);
+			return  value==null?0L:value;
+		} catch(EmptyResultDataAccessException e){
+			return 0L;
+		} catch (RuntimeException e) {
+			this.logger.error(e.getMessage(), e);
+			throw e;
+		}
+
+	}
+
+	@Override
+	public Double queryForDouble(String sql, Object... args) {
+		try {
+
+			Double value = jdbcTemplate.queryForObject(sql, Double.class, args);
+			return  value==null?0D:value;
+
+		} catch(EmptyResultDataAccessException e){
+			return 0D;
+		} catch (RuntimeException e) {
+			this.logger.error(e.getMessage(), e);
+			throw e;
+		}
+	}
+
+	@Override
+	public String queryForString(String sql, Object... args) {
+
+		String s = "";
+		try {
+			s = this.jdbcTemplate.queryForObject(sql, String.class,args);
+		}catch (EmptyResultDataAccessException e){
+			return "";
+		}catch (RuntimeException e) {
+			logger.debug("查询sql:["+sql+"]出错",e);
+
+		}
+		return s;
+	}
+
+	@Override
+	@SuppressWarnings("unchecked")
+	public List queryForList(String sql, Object... args) {
+		return this.jdbcTemplate.queryForList(sql, args);
+	}
+
+
+	@Override
+	public <T> List<T> queryForList(String sql, Class<T> clazz, Object... args) {
+
+		return this.jdbcTemplate.query(sql, new BeanPropertyRowMapper<T>(clazz), args);
+
+	}
+
+	@Override
+	public <T> List<T> queryForList(String sql, RowMapper<T> rowMapper, Object... args) {
+		return jdbcTemplate.query(sql,rowMapper,args);
+	}
+
+	@Override
+	public List queryForListPage(String sql, long pageNo, long pageSize, Object... args) {
+
+		try {
+			Assert.hasText(sql, "SQL语句不能为空");
+			Assert.isTrue(pageNo >= 1, "pageNo 必须大于等于1");
+			String listSql = this.buildPageSql(sql, pageNo, pageSize);
+			return queryForList(listSql, args);
+		} catch (Exception ex) {
+			throw new DBRuntimeException(ex, sql);
+		}
+
+	}
+
+	@Override
+	public Map queryForMap(String sql, Object... args) {
+		try {
+			Map map = this.jdbcTemplate.queryForMap(sql, args);
+
+				return map;
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			throw new ObjectNotFoundException(ex, sql);
+		}
+	}
+
+	@Override
+	public WebPage queryForPage(String sql, long pageNo, long pageSize, Object... args) {
+		String countSql = "SELECT COUNT(*) " + removeSelect(removeOrders(sql));
+		return this.queryForPage(sql, countSql, pageNo, pageSize, args);
+	}
+
+	@Override
+	public WebPage queryForPage(String sql, String countSql, long pageNo, long pageSize, Object... args) {
+		Assert.hasText(sql, "SQL语句不能为空");
+		Assert.isTrue(pageNo >= 1, "pageNo 必须大于等于1");
+		String listSql = buildPageSql(sql, pageNo, pageSize);
+
+		List list = queryForList(listSql, args);
+		Long totalCount = queryForLong(countSql, args);
+		return new WebPage(pageNo, totalCount, pageSize, list);
+
+	}
+	@Override
+	public <T> WebPage queryForPage(String sql, long pageNo, long pageSize, Class<T> clazz, Object... args) {
+
+		Assert.hasText(sql, "SQL语句不能为空");
+		Assert.isTrue(pageNo >= 1, "pageNo 必须大于等于1");
+		String listSql = buildPageSql(sql, pageNo, pageSize);
+		String countSql = "SELECT COUNT(*) " + removeSelect(removeOrders(sql));
+		List<T> list = this.queryForList(listSql, clazz, args);
+		Long totalCount = queryForLong(countSql, args);
+		return new WebPage(pageNo, totalCount, pageSize, list);
+
+	}
+
+	@Override
+	public int update(String table, Map fields, Map<String,?> where) {
+
+		Assert.hasText(table, "表名不能为空");
+		Assert.notEmpty(fields, "字段不能为空");
+		Assert.notEmpty(where, "where条件不能为空");
+
+		String whereSql = this.createWhereSql(where);
+
+		// 字段名
+		Object[] cols = fields.keySet().toArray();
+		String fieldSql = "";
+
+		for(int i=0;i<cols.length;i++){
+
+			fieldSql+= cols[i]+"=?";
+			 if(i!=cols.length-1){
+				 fieldSql+=",";
+			 }
+		}
+
+		// 字段值
+		Object[] values  = ArrayUtils.addAll(fields.values().toArray(),where.values().toArray());
+
+		String sql = "UPDATE " + table + " SET " +fieldSql  + " WHERE " + whereSql;
+
+		return this.jdbcTemplate.update(sql, values);
+
+	}
+
+	@Override
+	public int[] batchUpdate(String sql, List<Object[]> batchArgs) {
+		return jdbcTemplate.batchUpdate(sql, batchArgs);
+	}
+
+	@Override
+	public int[] batchUpdate(String... sql) {
+		return jdbcTemplate.batchUpdate(sql);
+	}
+
+	@Override
+	public int update(String table, Object po, Map<String,?> where) {
+
+		return update(table, ReflectionUtil.po2Map(po), where);
+	}
+
+
+	@Override
+	public String buildPageSql(String sql, long page, long pageSize) {
+
+		String sqlStr = null;
+
+		String dbType =  "mysql";
+
+		//防止魔法值
+		String mysqlStr = "mysql";
+		String sqlserverStr = "sqlserver";
+        String oracleStr = "oracle";
+
+		if (mysqlStr.equals(dbType)) {
+			sqlStr = sql + " LIMIT " + (page - 1) * pageSize + "," + pageSize;
+		} else if (oracleStr.equals(dbType)) {
+			StringBuffer localSql = new StringBuffer("SELECT * FROM (SELECT t1.*,rownum sn1 FROM (");
+			localSql.append(sql);
+			localSql.append(") t1) t2 WHERE t2.sn1 BETWEEN ");
+			localSql.append((page - 1) * pageSize + 1);
+			localSql.append(" AND ");
+			localSql.append(page * pageSize);
+			sqlStr = localSql.toString();
+		} else if (sqlserverStr.equals(dbType)) {
+			StringBuffer localSql = new StringBuffer();
+			// 找到order by 子句
+			String order = SqlPaser.findOrderStr(sql);
+
+			// 剔除order by 子句
+			if (order != null) {
+				sql = removeOrders(sql);
+			}
+			else {
+				// SQLServer分页必需有order by
+				// 子句,如果默认语句不包含order by,
+				// 自动以id降序,如果没有id字段会报错
+				order = "order by id desc";
+
+			}
+
+			// 拼装分页sql
+			localSql.append("select * from (");
+			localSql.append(SqlPaser.insertSelectField("ROW_NUMBER() Over(" + order + ") as rowNum", sql));
+			localSql.append(") tb where rowNum between ");
+			localSql.append((page - 1) * pageSize + 1);
+			localSql.append(" AND ");
+			localSql.append(page * pageSize);
+
+			return localSql.toString();
+
+		}
+
+		return sqlStr.toString();
+
+	}
+
+	/**
+	 * 格式化列名 只适用于Mysql
+	 *
+	 * @param col
+	 * @return
+	 */
+	private String quoteCol(String col) {
+		if (col == null || "".equals(col)) {
+			return "";
+		} else {
+			// if("2".equals(EopSetting.DBTYPE))//Oracle
+			// return "\"" + col + "\"";
+			// else if("1".equals(EopSetting.DBTYPE))//mysql
+			// return "`" + col + "`";
+			// else //mssql
+			// return "[" + col + "]";
+			return col;
+		}
+	}
+
+	/**
+	 * 格式化值 只适用于Mysql
+	 *
+	 * @param value
+	 * @return
+	 */
+	private String quoteValue(String value) {
+		if (value == null || "".equals(value)) {
+			return "''";
+		} else {
+			return "'" + value.replaceAll("'", "''") + "'";
+		}
+	}
+
+	private String getStr(int num, String str) {
+		StringBuffer sb = new StringBuffer("");
+		for (int i = 0; i < num; i++) {
+			sb.append(str);
+		}
+		return sb.toString();
+	}
+
+
+
+
+
+    @Override
+	public <T> void insert(T t){
+		DataMeta dataMeta  = this.sqlMetaBuilder.insert(t);
+		lastIdThreadLocal.set(dataMeta.getPrimaryValue());
+
+		String sql = dataMeta.getSql();
+		Object[] param = dataMeta.getParamter();
+		this.jdbcTemplate.update(sql,param);
+
+	}
+
+
+	@Override
+	public <T> void update(T model,Long id) {
+		DataMeta dataMeta  = this.sqlMetaBuilder.update(model,id);
+		String sql = dataMeta.getSql();
+		Object[] param = dataMeta.getParamter();
+		this.jdbcTemplate.update(sql,param);
+	}
+
+
+	@Override
+	public <T> void delete(Class<T> clazz, Long id) {
+		String sql = this.sqlMetaBuilder.delete(clazz);
+		Long[] ids = {id};
+		this.jdbcTemplate.update(sql,ids);
+	}
+
+
+	@Override
+	public <T> T  queryForObject(Class<T> clazz, Long id) {
+
+		String sql = this.sqlMetaBuilder.queryForModel(clazz);
+		Long[] ids = {id};
+		List<T> objList = this.queryForList(sql, clazz, ids);
+		if (objList.isEmpty()) {
+			return null;
+		}
+		return objList.get(0);
+	}
+
+
+	@Override
+	public <T> T queryForObject(String sql, Class<T> clazz, Object... args) {
+		List<T> objList = this.queryForList(sql, clazz, args);
+		if (objList.isEmpty()) {
+			return null;
+		}
+		return objList.get(0);
+	}
+
+
+    /**
+     * 去除hql的order by 子句,用于pagedQuery.
+     *
+     */
+    private String removeOrders(String hql) {
+        Assert.hasText(hql,"hql must hast text");
+
+        Matcher m = ORDER_PATTERN.matcher(hql);
+        StringBuffer sb = new StringBuffer();
+        while (m.find()) {
+            m.appendReplacement(sb, "");
+        }
+        m.appendTail(sb);
+        return sb.toString();
+    }
+
+    /**
+     * 去除sql的select 子句,未考虑union的情况,用于pagedQuery.
+     */
+    private String removeSelect(String sql) {
+
+        String groupBySql="group by";
+        sql = sql.toLowerCase();
+        if (sql.indexOf(groupBySql) != -1) {
+            return " from (" + sql + ") temp_table";
+        }
+
+        // FIXME 当查询中含有函数,比如SUM(),替换SQL出错
+        Matcher m = REMOVE_SELECT_PATTERN.matcher(sql);
+        StringBuffer sb = new StringBuffer();
+        while (m.find()) {
+            int c = m.end() - m.start();
+            m.appendReplacement(sb, getStr(c, "~"));
+        }
+        m.appendTail(sb);
+
+        String replacedSql = sb.toString();
+
+        int index = replacedSql.indexOf("from");
+
+        // 如果不存在
+        if (index == -1) {
+            index = replacedSql.indexOf("FROM");
+        }
+        return sql.substring(index);
+    }
+
+    /**
+     * 根据一个Map的条件,生成where 语句
+     * @param where  key为条件,value为条件值
+     * @return where 语句
+     */
+    private String createWhereSql(Map<String,?> where){
+
+        String whereSql = "";
+        if (where != null) {
+            Object[] whereCols = where.keySet().toArray();
+            for (int i = 0; i < whereCols.length; i++) {
+                StringBuffer str = new StringBuffer();
+                str.append(whereCols[i].toString());
+                str.append("=?");
+                whereCols[i] = str.toString();
+            }
+            whereSql += StringUtil.implode(" AND ", whereCols);
+        }
+
+        return whereSql;
+    }
+}

+ 39 - 0
framework/src/main/java/com/hys/app/framework/database/impl/FilterColumnMapRowMapper.java

@@ -0,0 +1,39 @@
+package com.hys.app.framework.database.impl;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.springframework.jdbc.core.ColumnMapRowMapper;
+import org.springframework.jdbc.support.JdbcUtils;
+
+/**
+ * map mapper
+ * @author kingapex
+ * @version v1.0
+ * @since v7.0.0
+ * 2018年3月23日 上午10:26:41
+ */
+public class FilterColumnMapRowMapper extends ColumnMapRowMapper {
+	private IRowMapperColumnFilter filter;
+	public FilterColumnMapRowMapper(IRowMapperColumnFilter filter){
+		this.filter = filter;
+	}
+	
+	@Override
+	public  Map<String, Object>  mapRow(ResultSet rs, int rowNum) throws SQLException {
+		ResultSetMetaData rsmd = rs.getMetaData();
+		int columnCount = rsmd.getColumnCount();
+		Map mapOfColValues = createColumnMap(columnCount);
+		for (int i = 1; i <= columnCount; i++) {
+			String key = getColumnKey(JdbcUtils.lookupColumnName(rsmd, i));
+			key  = key.toLowerCase();
+			Object obj = getColumnValue(rs, i);
+			mapOfColValues.put(key, obj);
+			//对此行结果集进行过滤
+			this.filter.filter(mapOfColValues, rs);
+		}
+		return mapOfColValues;
+	}
+}

+ 25 - 0
framework/src/main/java/com/hys/app/framework/database/impl/IRowMapperColumnFilter.java

@@ -0,0 +1,25 @@
+package com.hys.app.framework.database.impl;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Map;
+
+/**
+ * 数据库结果集过滤器
+ * 可对RowMapper的结果某或多列进行过滤 
+ * @author kingapex
+ *
+ */
+public interface IRowMapperColumnFilter {
+	
+	
+	/**
+	 * 对结果集的行进行过滤
+	 * @param colValues 结果集一行的map
+	 * @param rs 结果集
+	 * @throws  SQLException 可能的sql异常
+	 */
+    void filter(Map colValues, ResultSet rs) throws SQLException;
+	
+	
+}

+ 21 - 0
framework/src/main/java/com/hys/app/framework/database/impl/LowerCaseJdbcTemplate.java

@@ -0,0 +1,21 @@
+package com.hys.app.framework.database.impl;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+
+
+
+/**
+ * 覆写jdbctemlate ,使用LowerCaseColumnMapRowMapper
+ * @author kingapex
+ * 2010-6-13上午11:05:32
+ */
+public class LowerCaseJdbcTemplate extends JdbcTemplate {
+	@Override
+	protected RowMapper getColumnMapRowMapper() {
+
+		return new MySqlColumnMapRowMapper();
+
+	}
+
+}

+ 39 - 0
framework/src/main/java/com/hys/app/framework/database/impl/MySqlColumnMapRowMapper.java

@@ -0,0 +1,39 @@
+package com.hys.app.framework.database.impl;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.springframework.jdbc.core.ColumnMapRowMapper;
+import org.springframework.jdbc.support.JdbcUtils;
+
+/**
+ * map mapper
+ * @author kingapex
+ * @version v1.0
+ * @since v7.0.0
+ * 2018年3月23日 上午10:26:41
+ */
+public class MySqlColumnMapRowMapper extends ColumnMapRowMapper {
+	@Override
+	public Map<String, Object>  mapRow(ResultSet rs, int rowNum) throws SQLException {
+		ResultSetMetaData rsmd = rs.getMetaData();
+		int columnCount = rsmd.getColumnCount();
+		Map mapOfColValues = createColumnMap(columnCount);
+		for (int i = 1; i <= columnCount; i++) {
+			String key = getColumnKey(JdbcUtils.lookupColumnName(rsmd, i));
+			key = key.toLowerCase();
+			Object obj = null;
+			String typename= rsmd.getColumnTypeName(i).toUpperCase();
+			if("DECIMAL".equals(typename)){
+				obj = rs.getDouble(i);
+			}else{
+				obj = getColumnValue(rs, i);
+			}
+			 
+			mapOfColValues.put(key, obj);
+		}
+		return mapOfColValues;
+	}
+}

+ 126 - 0
framework/src/main/java/com/hys/app/framework/database/impl/MySqlMetaBuilderImpl.java

@@ -0,0 +1,126 @@
+package com.hys.app.framework.database.impl;
+
+import com.hys.app.framework.database.ColumnMeta;
+import com.hys.app.framework.database.DataMeta;
+import com.hys.app.framework.database.SqlMetaBuilder;
+import com.hys.app.framework.sncreator.SnCreator;
+import com.hys.app.framework.util.ReflectionUtil;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 基于Mysql的基本增删改查操作实现类
+ *
+ * @author Snow create in 2018/3/21
+ * @version v2.0
+ * @since v7.0.0
+ */
+@Service
+public class MySqlMetaBuilderImpl implements SqlMetaBuilder {
+
+    @Autowired
+    SnCreator snCreator;
+
+
+    @Override
+    public <T> DataMeta insert(T model) {
+
+        ColumnMeta columnMeta = ReflectionUtil.getColumnMeta(model);
+        String primaryKeyName = columnMeta.getPrimaryKeyName();
+        Long idValue = snCreator.create(1);
+
+        Object[] columnName = columnMeta.getNames();
+        Object[] columnValue = columnMeta.getValues();
+
+        //拼接主键
+        columnName = arrayInsert(columnName, primaryKeyName);
+        columnValue = arrayInsert(columnValue, idValue);
+
+        StringBuffer questionMarkStr = new StringBuffer();
+        for (int i = 0; i < columnValue.length; i++) {
+            questionMarkStr.append("?");
+            if ((i + 1) != columnValue.length) {
+                questionMarkStr.append(",");
+            }
+        }
+
+
+        //修改使其能获取父类Table注解的表名 add by liuyulei 2019-12-12
+        String tableName = ReflectionUtil.getTableName(model.getClass());
+        String columnNameStr = StringUtils.join(columnName, ",");
+
+        String addSql = "INSERT INTO " + tableName + " (" + columnNameStr + ") VALUES (" + questionMarkStr.toString() + ")";
+
+        DataMeta dataMeta = new DataMeta();
+        dataMeta.setPrimaryValue(idValue);
+
+        dataMeta.setSql(addSql);
+        dataMeta.setParamter(columnValue);
+        return dataMeta;
+    }
+
+    @Override
+    public <T> DataMeta update(T model, Long id) {
+        ColumnMeta columnMeta = ReflectionUtil.getColumnMeta(model);
+        Object[] columnName = columnMeta.getNames();
+        Object[] columnValue = columnMeta.getValues();
+
+        String columnId = ReflectionUtil.getPrimaryKey(model.getClass());
+        //修改使其能获取父类Table注解的表名 add by liuyulei 2019-12-12
+        String tableName = ReflectionUtil.getTableName(model.getClass());
+
+        List valueList = new ArrayList();
+        StringBuffer setStr = new StringBuffer();
+        for (int i = 0; i < columnName.length; i++) {
+            setStr.append(columnName[i] + "=?");
+            valueList.add(columnValue[i]);
+            if ((i + 1) != columnName.length) {
+                setStr.append(",");
+            }
+        }
+        String editSql = "UPDATE " + tableName + " SET " + setStr.toString() + " WHERE " + columnId + "=?";
+        valueList.add(id);
+
+        DataMeta dataMeta = new DataMeta();
+        dataMeta.setSql(editSql);
+        dataMeta.setParamter(valueList.toArray());
+        return dataMeta;
+    }
+
+
+    @Override
+    public String queryForModel(Class clazz) {
+        //修改使其能获取父类Table注解的表名 add by liuyulei 2019-12-12
+        String tableName = ReflectionUtil.getTableName(clazz);
+        String columnId = ReflectionUtil.getPrimaryKey(clazz);
+        String queryOneSql = "SELECT * FROM " + tableName + " WHERE " + columnId + "=?";
+        return queryOneSql;
+    }
+
+    @Override
+    public String delete(Class clazz) {
+        //修改使其能获取父类Table注解的表名 add by liuyulei 2019-12-12
+        String tableName = ReflectionUtil.getTableName(clazz);
+        String columnId = ReflectionUtil.getPrimaryKey(clazz);
+        String deleteSql = "DELETE FROM " + tableName + " WHERE " + columnId + "=?";
+        return deleteSql;
+    }
+
+    /**
+     * 给数组的最后添加元素
+     *
+     * @param ar
+     * @param value
+     * @return
+     */
+    Object[] arrayInsert(Object[] ar, Object value) {
+        ar = Arrays.copyOf(ar, ar.length + 1);
+        ar[ar.length - 1] = value;
+        return ar;
+    }
+}

+ 52 - 0
framework/src/main/java/com/hys/app/framework/database/impl/OracleColumnMapRowMapper.java

@@ -0,0 +1,52 @@
+package com.hys.app.framework.database.impl;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.springframework.jdbc.core.ColumnMapRowMapper;
+import org.springframework.jdbc.support.JdbcUtils;
+
+/**
+ * 小写key的columnrowmapper
+ *  本类覆写了spring 的ColumnMapRowMapper,将字段名全部小写
+ * @author kingapex
+ * 2010-6-13上午11:01:34
+ */
+public class OracleColumnMapRowMapper extends ColumnMapRowMapper {
+
+	@Override
+	public Map<String, Object>  mapRow(ResultSet rs, int rowNum) throws SQLException {
+		ResultSetMetaData rsmd = rs.getMetaData();
+		int columnCount = rsmd.getColumnCount();
+		Map mapOfColValues = createColumnMap(columnCount);
+		for (int i = 1; i <= columnCount; i++) {
+			String key = getColumnKey(JdbcUtils.lookupColumnName(rsmd, i));
+			key = key.toLowerCase();
+			Object obj = null;
+			String typename= rsmd.getColumnTypeName(i);
+			if("NUMBER".equals(typename)){
+				int scale = rsmd.getScale(i);
+				int precision = rsmd.getPrecision(i);
+				if(scale == 0){
+					if(precision<=10) {
+						obj = rs.getInt(i);
+					}
+					else {
+						obj = rs.getLong(i);
+					}
+				}else if(scale>0){
+					obj = rs.getDouble(i);
+				}else {
+					obj = rs.getLong(i);
+				}
+			}else{
+				obj = getColumnValue(rs, i);
+			}
+ 
+			mapOfColValues.put(key, obj);
+		}
+		return mapOfColValues;
+	}
+}

+ 42 - 0
framework/src/main/java/com/hys/app/framework/database/impl/SqlPaser.java

@@ -0,0 +1,42 @@
+package com.hys.app.framework.database.impl;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * sql解析器
+ * @author kingapex
+ *
+ */
+public class SqlPaser {
+	
+	
+	public static String insertSelectField(String field,String sql){
+		sql = "select " + field+","+sql.substring(6, sql.length());
+		return sql;
+	}
+	
+	
+	/**
+	 * 从一个sql语句中找到order by 子句
+	 * @param sql
+	 * @return
+	 */
+	public static String findOrderStr(String sql ){
+
+		String pattern = "(order\\s*by[\\w|\\W|\\s|\\S]*)";
+		Pattern p = Pattern.compile(pattern, 2 | Pattern.DOTALL);
+		Matcher m = p.matcher(sql);
+
+		if (m.find()) {
+			return m.group();
+		 
+		} 
+		return null;
+	}
+	
+	
+	public static void main(String[] args){
+		String sql ="select * from abc where 12=12 order by id asc ";
+	}
+}

+ 58 - 0
framework/src/main/java/com/hys/app/framework/database/mybatisplus/DefaultDBFieldHandler.java

@@ -0,0 +1,58 @@
+package com.hys.app.framework.database.mybatisplus;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.hys.app.framework.context.user.AdminUserContext;
+import com.hys.app.framework.database.mybatisplus.base.BaseDO;
+import com.hys.app.framework.util.DateUtil;
+import org.apache.ibatis.reflection.MetaObject;
+
+import java.util.Objects;
+
+/**
+ * 通用参数填充实现类
+ * 如果没有显式的对通用参数进行赋值,这里会对通用参数进行填充、赋值
+ */
+public class DefaultDBFieldHandler implements MetaObjectHandler {
+
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) {
+            BaseDO baseDO = (BaseDO) metaObject.getOriginalObject();
+
+            // 创建时间为空,则以当前时间为插入时间
+            if (Objects.isNull(baseDO.getCreateTime())) {
+                baseDO.setCreateTime(DateUtil.getDateline());
+            }
+            // 更新时间为空,则以当前时间为更新时间
+            if (Objects.isNull(baseDO.getUpdateTime())) {
+                baseDO.setUpdateTime(DateUtil.getDateline());
+            }
+
+            String adminUserName = AdminUserContext.getAdminUserName();
+            // 当前登录用户不为空,创建人为空,则当前登录用户为创建人
+            if (Objects.nonNull(adminUserName) && Objects.isNull(baseDO.getCreator())) {
+                baseDO.setCreator(adminUserName);
+            }
+            // 当前登录用户不为空,更新人为空,则当前登录用户为更新人
+            if (Objects.nonNull(adminUserName) && Objects.isNull(baseDO.getUpdater())) {
+                baseDO.setUpdater(adminUserName);
+            }
+        }
+    }
+
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        // 更新时间为空,则以当前时间为更新时间
+        Object modifyTime = getFieldValByName("updateTime", metaObject);
+        if (Objects.isNull(modifyTime)) {
+            setFieldValByName("updateTime", DateUtil.getDateline(), metaObject);
+        }
+
+        // 当前登录用户不为空,更新人为空,则当前登录用户为更新人
+        Object modifier = getFieldValByName("updater", metaObject);
+        String adminUserName = AdminUserContext.getAdminUserName();
+        if (Objects.nonNull(adminUserName) && Objects.isNull(modifier)) {
+            setFieldValByName("updater", adminUserName, metaObject);
+        }
+    }
+}

+ 18 - 0
framework/src/main/java/com/hys/app/framework/database/mybatisplus/MybatisAutoConfiguration.java

@@ -0,0 +1,18 @@
+package com.hys.app.framework.database.mybatisplus;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * MyBaits Plus配置类
+ */
+@Configuration
+public class MybatisAutoConfiguration {
+
+    @Bean
+    public MetaObjectHandler defaultMetaObjectHandler() {
+        return new DefaultDBFieldHandler(); // 自动填充参数类
+    }
+
+}

+ 40 - 0
framework/src/main/java/com/hys/app/framework/database/mybatisplus/base/BaseDO.java

@@ -0,0 +1,40 @@
+package com.hys.app.framework.database.mybatisplus.base;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import lombok.Data;
+import org.apache.ibatis.type.JdbcType;
+
+import java.io.Serializable;
+
+/**
+ * 基础实体对象
+ *
+ * @author 张崧
+ * @since 2023-11-28
+ */
+@Data
+public abstract class BaseDO implements Serializable {
+
+    /**
+     * 创建时间
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Long createTime;
+    /**
+     * 最后更新时间
+     */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Long updateTime;
+    /**
+     * 创建者
+     */
+    @TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
+    private String creator;
+    /**
+     * 更新者
+     */
+    @TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR)
+    private String updater;
+
+}

+ 157 - 0
framework/src/main/java/com/hys/app/framework/database/mybatisplus/base/BaseMapperX.java

@@ -0,0 +1,157 @@
+package com.hys.app.framework.database.mybatisplus.base;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
+import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
+import com.github.yulichang.interfaces.MPJBaseJoin;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import com.hys.app.framework.database.WebPage;
+import com.hys.app.framework.database.mybatisplus.wrapper.LambdaQueryChainWrapperX;
+import com.hys.app.framework.database.mybatisplus.wrapper.QueryChainWrapperX;
+import com.hys.app.framework.util.PageConvert;
+import com.hys.app.framework.database.BaseQueryParam;
+import com.github.yulichang.base.MPJBaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+
+import static com.hys.app.framework.database.BaseQueryParam.PAGE_NONE;
+
+/**
+ * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力
+ *
+ * 1. {@link BaseMapper} 为 MyBatis Plus 的基础接口,提供基础的 CRUD 能力
+ * 2. {@link MPJBaseMapper} 为 MyBatis Plus Join 的基础接口,提供连表 Join 能力
+ */
+public interface BaseMapperX<T> extends MPJBaseMapper<T> {
+
+
+    /**
+     * 分页查询
+     * @param pageParam 分页查询参数
+     * @return 分页数据
+     */
+    default WebPage<T> selectPage(BaseQueryParam pageParam, @Param("ew") Wrapper<T> queryWrapper) {
+        // 如果不分页,直接查列表
+        if(pageParam.getPageSize() == PAGE_NONE){
+            List<T> list = selectList(queryWrapper);
+            return new WebPage<>(pageParam.getPageNo(), (long) list.size(), pageParam.getPageSize(), list);
+        }
+        IPage<T> mpPage = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
+
+        selectPage(mpPage, queryWrapper);
+
+        return PageConvert.convert(mpPage);
+    }
+
+    default QueryChainWrapperX<T> query() {
+        return new QueryChainWrapperX<>(this);
+    }
+
+    default LambdaQueryChainWrapperX<T> lambdaQuery() {
+        return new LambdaQueryChainWrapperX<>(this);
+    }
+
+    default UpdateChainWrapper<T> update() {
+        return ChainWrappers.updateChain(this);
+    }
+
+    default LambdaUpdateChainWrapper<T> lambdaUpdate() {
+        return ChainWrappers.lambdaUpdateChain(this);
+    }
+
+    default T selectOne(String field, Object value) {
+        return selectOne(new QueryWrapper<T>().eq(field, value));
+    }
+
+    default T selectOne(SFunction<T, ?> field, Object value) {
+        return selectOne(new LambdaQueryWrapper<T>().eq(field, value));
+    }
+
+    default T selectOne(String field1, Object value1, String field2, Object value2) {
+        return selectOne(new QueryWrapper<T>().eq(field1, value1).eq(field2, value2));
+    }
+
+    default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2) {
+        return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
+    }
+
+    default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2,
+                        SFunction<T, ?> field3, Object value3) {
+        return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)
+                .eq(field3, value3));
+    }
+
+    default Long selectCount() {
+        return selectCount(new QueryWrapper<>());
+    }
+
+    default Long selectCount(String field, Object value) {
+        return selectCount(new QueryWrapper<T>().eq(field, value));
+    }
+
+    default Long selectCount(SFunction<T, ?> field, Object value) {
+        return selectCount(new LambdaQueryWrapper<T>().eq(field, value));
+    }
+
+    default List<T> selectList() {
+        return selectList(new QueryWrapper<>());
+    }
+
+    default List<T> selectList(String field, Object value) {
+        return selectList(new QueryWrapper<T>().eq(field, value));
+    }
+
+    default List<T> selectList(SFunction<T, ?> field, Object value) {
+        return selectList(new LambdaQueryWrapper<T>().eq(field, value));
+    }
+
+    default List<T> selectList(String field, Collection<?> values) {
+        if (CollUtil.isEmpty(values)) {
+            return CollUtil.newArrayList();
+        }
+        return selectList(new QueryWrapper<T>().in(field, values));
+    }
+
+    default List<T> selectList(SFunction<T, ?> field, Collection<?> values) {
+        if (CollUtil.isEmpty(values)) {
+            return CollUtil.newArrayList();
+        }
+        return selectList(new LambdaQueryWrapper<T>().in(field, values));
+    }
+
+    default List<T> selectList(SFunction<T, ?> leField, SFunction<T, ?> geField, Object value) {
+        return selectList(new LambdaQueryWrapper<T>().le(leField, value).ge(geField, value));
+    }
+
+    default <D> WebPage<D> selectJoinPage(BaseQueryParam pageParam, Class<D> clazz, MPJLambdaWrapper<T> lambdaWrapper) {
+        // 特殊:不分页,直接查询全部
+        if(pageParam.getPageSize() == PAGE_NONE){
+            List<D> list = selectJoinList(clazz, lambdaWrapper);
+            return new WebPage<>(pageParam.getPageNo(), (long) list.size(), pageParam.getPageSize(), list);
+        }
+
+        // MyBatis Plus Join 查询
+        IPage<D> mpPage = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
+        mpPage = selectJoinPage(mpPage, clazz, lambdaWrapper);
+        // 转换返回
+        return PageConvert.convert(mpPage);
+    }
+
+    default <DTO> WebPage<DTO> selectJoinPage(BaseQueryParam pageParam, Class<DTO> resultTypeClass, MPJBaseJoin<T> joinQueryWrapper) {
+        IPage<DTO> mpPage = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
+        selectJoinPage(mpPage, resultTypeClass, joinQueryWrapper);
+        // 转换返回
+        return PageConvert.convert(mpPage);
+    }
+
+}

+ 72 - 0
framework/src/main/java/com/hys/app/framework/database/mybatisplus/base/BaseService.java

@@ -0,0 +1,72 @@
+package com.hys.app.framework.database.mybatisplus.base;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.github.yulichang.base.MPJBaseService;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Mybatis Plus IService的拓展
+ * 1. {@link IService} 为 MyBatis Plus 的基础接口,提供基础的 CRUD 能力
+ * 2. {@link MPJBaseService} 为 MyBatis Plus Join 的基础接口,提供连表 Join 能力
+ *
+ * @author 张崧
+ */
+public interface BaseService<T> extends MPJBaseService<T> {
+
+    /**
+     * 根据id集合查询
+     *
+     * @param ids id集合
+     * @return 集合
+     */
+    @Override
+    default List<T> listByIds(Collection<? extends Serializable> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        return this.getBaseMapper().selectBatchIds(ids);
+    }
+
+    /**
+     * 根据某个字段in查询,并转为以该字段为key,数据库实体为value的map
+     * eg:根据用户编号集合查询出多个用户,返回以用户编号为key,用户实体为value的map
+     *
+     * @param inValue  查询的字段值
+     * @param keyField 查询的字段 & map的key
+     * @return Map集合
+     */
+    default <K> Map<K, T> listAndConvertMap(Collection<?> inValue, SFunction<T, K> keyField) {
+        return listAndConvertMap(inValue, keyField, Function.identity());
+    }
+
+    /**
+     * 根据某个字段in查询,并转为以该字段为key,指定字段为value的map
+     * eg:根据用户编号集合查询出多个用户,返回以用户编号为key,用户名为value的map
+     *
+     * @param inValue    查询的字段值
+     * @param keyField   查询的字段 & map的key
+     * @param valueField map的value
+     * @return Map集合
+     */
+    default <K, V> Map<K, V> listAndConvertMap(Collection<?> inValue, SFunction<T, K> keyField, Function<T, V> valueField) {
+        if (CollUtil.isEmpty(inValue)) {
+            return Collections.emptyMap();
+        }
+
+        return lambdaQuery()
+                .in(keyField, inValue)
+                .list()
+                .stream()
+                .collect(Collectors.toMap(keyField, valueField));
+    }
+
+}

+ 16 - 0
framework/src/main/java/com/hys/app/framework/database/mybatisplus/base/BaseServiceImpl.java

@@ -0,0 +1,16 @@
+package com.hys.app.framework.database.mybatisplus.base;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.github.yulichang.base.MPJBaseMapper;
+import com.github.yulichang.base.MPJBaseServiceImpl;
+
+/**
+ * Mybatis Plus ServiceImpl的拓展
+ * 1. {@link ServiceImpl} 为 MyBatis Plus 的基础接口,提供基础的 CRUD 能力
+ * 2. {@link MPJBaseServiceImpl} 为 MyBatis Plus Join 的基础接口,提供连表 Join 能力
+ *
+ * @author 张崧
+ */
+public class BaseServiceImpl<M extends MPJBaseMapper<T>, T> extends MPJBaseServiceImpl<M, T> implements BaseService<T> {
+
+}

+ 56 - 0
framework/src/main/java/com/hys/app/framework/database/mybatisplus/type/LongListTypeHandler.java

@@ -0,0 +1,56 @@
+package com.hys.app.framework.database.mybatisplus.type;
+
+import cn.hutool.core.collection.CollUtil;
+import com.hys.app.framework.util.StringUtil;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+import org.apache.ibatis.type.TypeHandler;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ * List<Long> 的类型转换器实现类,对应数据库的 varchar 类型
+ *
+ */
+@MappedJdbcTypes(JdbcType.VARCHAR)
+@MappedTypes(List.class)
+public class LongListTypeHandler implements TypeHandler<List<Long>> {
+
+    private static final String COMMA = ",";
+
+    @Override
+    public void setParameter(PreparedStatement ps, int i, List<Long> strings, JdbcType jdbcType) throws SQLException {
+        // 设置占位符
+        ps.setString(i, CollUtil.join(strings, COMMA));
+    }
+
+    @Override
+    public List<Long> getResult(ResultSet rs, String columnName) throws SQLException {
+        String value = rs.getString(columnName);
+        return getResult(value);
+    }
+
+    @Override
+    public List<Long> getResult(ResultSet rs, int columnIndex) throws SQLException {
+        String value = rs.getString(columnIndex);
+        return getResult(value);
+    }
+
+    @Override
+    public List<Long> getResult(CallableStatement cs, int columnIndex) throws SQLException {
+        String value = cs.getString(columnIndex);
+        return getResult(value);
+    }
+
+    private List<Long> getResult(String value) {
+        if (value == null) {
+            return null;
+        }
+        return StringUtil.splitToLong(value, COMMA);
+    }
+}

+ 55 - 0
framework/src/main/java/com/hys/app/framework/database/mybatisplus/type/StringListTypeHandler.java

@@ -0,0 +1,55 @@
+package com.hys.app.framework.database.mybatisplus.type;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+import org.apache.ibatis.type.TypeHandler;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ * List<String> 的类型转换器实现类,对应数据库的 varchar 类型
+ */
+@MappedJdbcTypes(JdbcType.VARCHAR)
+@MappedTypes(List.class)
+public class StringListTypeHandler implements TypeHandler<List<String>> {
+
+    private static final String COMMA = ",";
+
+    @Override
+    public void setParameter(PreparedStatement ps, int i, List<String> strings, JdbcType jdbcType) throws SQLException {
+        // 设置占位符
+        ps.setString(i, CollUtil.join(strings, COMMA));
+    }
+
+    @Override
+    public List<String> getResult(ResultSet rs, String columnName) throws SQLException {
+        String value = rs.getString(columnName);
+        return getResult(value);
+    }
+
+    @Override
+    public List<String> getResult(ResultSet rs, int columnIndex) throws SQLException {
+        String value = rs.getString(columnIndex);
+        return getResult(value);
+    }
+
+    @Override
+    public List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException {
+        String value = cs.getString(columnIndex);
+        return getResult(value);
+    }
+
+    private List<String> getResult(String value) {
+        if (value == null) {
+            return null;
+        }
+        return StrUtil.splitTrim(value, COMMA);
+    }
+}

+ 180 - 0
framework/src/main/java/com/hys/app/framework/database/mybatisplus/wrapper/LambdaQueryChainWrapperX.java

@@ -0,0 +1,180 @@
+package com.hys.app.framework.database.mybatisplus.wrapper;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.hys.app.framework.database.WebPage;
+import com.hys.app.framework.util.PageConvert;
+import com.hys.app.framework.database.BaseQueryParam;
+import com.hys.app.framework.util.ArrayUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+
+import static com.hys.app.framework.database.BaseQueryParam.PAGE_NONE;
+
+/**
+ * 拓展 MyBatis Plus LambdaQueryChainWrapper 类,主要增加如下功能:
+ * <p>
+ * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
+ *
+ * @param <T> 数据类型
+ *
+ * @author 张崧
+ * @since 2023-11-28
+ */
+public class LambdaQueryChainWrapperX<T> extends LambdaQueryChainWrapper<T> {
+
+    public LambdaQueryChainWrapperX(BaseMapper<T> baseMapper) {
+        super(baseMapper);
+    }
+
+    public LambdaQueryChainWrapperX<T> likeIfPresent(SFunction<T, ?> column, String val) {
+        if (StringUtils.hasText(val)) {
+            return (LambdaQueryChainWrapperX<T>) super.like(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryChainWrapperX<T> inIfPresent(SFunction<T, ?> column, Collection<?> values) {
+        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {
+            return (LambdaQueryChainWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public LambdaQueryChainWrapperX<T> inIfPresent(SFunction<T, ?> column, Object... values) {
+        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {
+            return (LambdaQueryChainWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public LambdaQueryChainWrapperX<T> eqIfPresent(SFunction<T, ?> column, Object val) {
+        if (ObjectUtil.isNotEmpty(val)) {
+            return (LambdaQueryChainWrapperX<T>) super.eq(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryChainWrapperX<T> neIfPresent(SFunction<T, ?> column, Object val) {
+        if (ObjectUtil.isNotEmpty(val)) {
+            return (LambdaQueryChainWrapperX<T>) super.ne(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryChainWrapperX<T> gtIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryChainWrapperX<T>) super.gt(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryChainWrapperX<T> geIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryChainWrapperX<T>) super.ge(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryChainWrapperX<T> ltIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryChainWrapperX<T>) super.lt(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryChainWrapperX<T> leIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryChainWrapperX<T>) super.le(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryChainWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object val1, Object val2) {
+        if (val1 != null && val2 != null) {
+            return (LambdaQueryChainWrapperX<T>) super.between(column, val1, val2);
+        }
+        if (val1 != null) {
+            return (LambdaQueryChainWrapperX<T>) ge(column, val1);
+        }
+        if (val2 != null) {
+            return (LambdaQueryChainWrapperX<T>) le(column, val2);
+        }
+        return this;
+    }
+
+    public LambdaQueryChainWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object[] values) {
+        Object val1 = ArrayUtils.get(values, 0);
+        Object val2 = ArrayUtils.get(values, 1);
+        return betweenIfPresent(column, val1, val2);
+    }
+
+    public LambdaQueryChainWrapperX<T> andX(boolean condition, Consumer<LambdaQueryWrapper<T>> consumer) {
+        this.getWrapper().and(condition, consumer);
+        return this;
+    }
+
+    // ========== 重写父类方法,方便链式调用 ==========
+
+    @Override
+    public LambdaQueryChainWrapperX<T> eq(boolean condition, SFunction<T, ?> column, Object val) {
+        super.eq(condition, column, val);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryChainWrapperX<T> eq(SFunction<T, ?> column, Object val) {
+        super.eq(column, val);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryChainWrapperX<T> orderByDesc(SFunction<T, ?> column) {
+        super.orderByDesc(true, column);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryChainWrapperX<T> orderByAsc(SFunction<T, ?> column) {
+        super.orderByAsc(true, column);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryChainWrapperX<T> last(String lastSql) {
+        super.last(lastSql);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryChainWrapperX<T> in(SFunction<T, ?> column, Collection<?> coll) {
+        super.in(column, coll);
+        return this;
+    }
+
+    /**
+     * 分页查询
+     * @param pageParam 分页查询参数
+     * @return 分页数据
+     */
+    public WebPage<T> page(BaseQueryParam pageParam) {
+        // 如果不分页,直接查列表
+        if(pageParam.getPageSize() == PAGE_NONE){
+            List<T> list = list();
+            return new WebPage<>(pageParam.getPageNo(), (long) list.size(), pageParam.getPageSize(), list);
+        }
+
+        IPage<T> mpPage = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
+        super.page(mpPage);
+        return PageConvert.convert(mpPage);
+    }
+}

+ 135 - 0
framework/src/main/java/com/hys/app/framework/database/mybatisplus/wrapper/LambdaQueryWrapperX.java

@@ -0,0 +1,135 @@
+package com.hys.app.framework.database.mybatisplus.wrapper;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.hys.app.framework.util.ArrayUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+
+/**
+ * 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能:
+ * <p>
+ * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
+ *
+ * @param <T> 数据类型
+ */
+public class LambdaQueryWrapperX<T> extends LambdaQueryWrapper<T> {
+
+    public LambdaQueryWrapperX<T> likeIfPresent(SFunction<T, ?> column, String val) {
+        if (StringUtils.hasText(val)) {
+            return (LambdaQueryWrapperX<T>) super.like(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> inIfPresent(SFunction<T, ?> column, Collection<?> values) {
+        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {
+            return (LambdaQueryWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> inIfPresent(SFunction<T, ?> column, Object... values) {
+        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {
+            return (LambdaQueryWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> eqIfPresent(SFunction<T, ?> column, Object val) {
+        if (ObjectUtil.isNotEmpty(val)) {
+            return (LambdaQueryWrapperX<T>) super.eq(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> neIfPresent(SFunction<T, ?> column, Object val) {
+        if (ObjectUtil.isNotEmpty(val)) {
+            return (LambdaQueryWrapperX<T>) super.ne(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> gtIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryWrapperX<T>) super.gt(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> geIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryWrapperX<T>) super.ge(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> ltIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryWrapperX<T>) super.lt(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> leIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryWrapperX<T>) super.le(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object val1, Object val2) {
+        if (val1 != null && val2 != null) {
+            return (LambdaQueryWrapperX<T>) super.between(column, val1, val2);
+        }
+        if (val1 != null) {
+            return (LambdaQueryWrapperX<T>) ge(column, val1);
+        }
+        if (val2 != null) {
+            return (LambdaQueryWrapperX<T>) le(column, val2);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object[] values) {
+        Object val1 = ArrayUtils.get(values, 0);
+        Object val2 = ArrayUtils.get(values, 1);
+        return betweenIfPresent(column, val1, val2);
+    }
+
+    // ========== 重写父类方法,方便链式调用 ==========
+
+    @Override
+    public LambdaQueryWrapperX<T> eq(boolean condition, SFunction<T, ?> column, Object val) {
+        super.eq(condition, column, val);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryWrapperX<T> eq(SFunction<T, ?> column, Object val) {
+        super.eq(column, val);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryWrapperX<T> orderByDesc(SFunction<T, ?> column) {
+        super.orderByDesc(true, column);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryWrapperX<T> last(String lastSql) {
+        super.last(lastSql);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryWrapperX<T> in(SFunction<T, ?> column, Collection<?> coll) {
+        super.in(column, coll);
+        return this;
+    }
+
+}

+ 319 - 0
framework/src/main/java/com/hys/app/framework/database/mybatisplus/wrapper/MPJLambdaWrapperX.java

@@ -0,0 +1,319 @@
+package com.hys.app.framework.database.mybatisplus.wrapper;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.hys.app.framework.util.ArrayUtils;
+import com.github.yulichang.toolkit.MPJWrappers;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+import java.util.function.Consumer;
+
+/**
+ * 拓展 MyBatis Plus Join QueryWrapper 类,主要增加如下功能:
+ * <p>
+ * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
+ *
+ * @param <T> 数据类型
+ */
+public class MPJLambdaWrapperX<T> extends MPJLambdaWrapper<T> {
+
+    public MPJLambdaWrapperX<T> likeIfPresent(SFunction<T, ?> column, String val) {
+        MPJWrappers.lambdaJoin().like(column, val);
+        if (StringUtils.hasText(val)) {
+            return (MPJLambdaWrapperX<T>) super.like(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> inIfPresent(SFunction<T, ?> column, Collection<?> values) {
+        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {
+            return (MPJLambdaWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> inIfPresent(SFunction<T, ?> column, Object... values) {
+        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {
+            return (MPJLambdaWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> eqIfPresent(SFunction<T, ?> column, Object val) {
+        if (ObjectUtil.isNotEmpty(val)) {
+            return (MPJLambdaWrapperX<T>) super.eq(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> neIfPresent(SFunction<T, ?> column, Object val) {
+        if (ObjectUtil.isNotEmpty(val)) {
+            return (MPJLambdaWrapperX<T>) super.ne(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> gtIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (MPJLambdaWrapperX<T>) super.gt(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> geIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (MPJLambdaWrapperX<T>) super.ge(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> ltIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (MPJLambdaWrapperX<T>) super.lt(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> leIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (MPJLambdaWrapperX<T>) super.le(column, val);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object val1, Object val2) {
+        if (val1 != null && val2 != null) {
+            return (MPJLambdaWrapperX<T>) super.between(column, val1, val2);
+        }
+        if (val1 != null) {
+            return (MPJLambdaWrapperX<T>) ge(column, val1);
+        }
+        if (val2 != null) {
+            return (MPJLambdaWrapperX<T>) le(column, val2);
+        }
+        return this;
+    }
+
+    public MPJLambdaWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object[] values) {
+        Object val1 = ArrayUtils.get(values, 0);
+        Object val2 = ArrayUtils.get(values, 1);
+        return betweenIfPresent(column, val1, val2);
+    }
+
+    // ========== 重写父类方法,方便链式调用 ==========
+
+    @Override
+    public <X> MPJLambdaWrapperX<T> eq(boolean condition, SFunction<X, ?> column, Object val) {
+        super.eq(condition, column, val);
+        return this;
+    }
+
+    @Override
+    public <X> MPJLambdaWrapperX<T> eq(SFunction<X, ?> column, Object val) {
+        super.eq(column, val);
+        return this;
+    }
+
+    @Override
+    public <X> MPJLambdaWrapperX<T> orderByDesc(SFunction<X, ?> column) {
+        //noinspection unchecked
+        super.orderByDesc(true, column);
+        return this;
+    }
+
+    @Override
+    public MPJLambdaWrapperX<T> last(String lastSql) {
+        super.last(lastSql);
+        return this;
+    }
+
+    @Override
+    public <X> MPJLambdaWrapperX<T> in(SFunction<X, ?> column, Collection<?> coll) {
+        super.in(column, coll);
+        return this;
+    }
+
+    @Override
+    public MPJLambdaWrapperX<T> selectAll(Class<?> clazz) {
+        super.selectAll(clazz);
+        return this;
+    }
+
+    @Override
+    public MPJLambdaWrapperX<T> selectAll(Class<?> clazz, String prefix) {
+        super.selectAll(clazz, prefix);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectAs(SFunction<S, ?> column, String alias) {
+        super.selectAs(column, alias);
+        return this;
+    }
+
+    @Override
+    public <E> MPJLambdaWrapperX<T> selectAs(String column, SFunction<E, ?> alias) {
+        super.selectAs(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectAs(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectAs(column, alias);
+        return this;
+    }
+
+    @Override
+    public <E, X> MPJLambdaWrapperX<T> selectAs(String index, SFunction<E, ?> column, SFunction<X, ?> alias) {
+        super.selectAs(index, column, alias);
+        return this;
+    }
+
+    @Override
+    public <E> MPJLambdaWrapperX<T> selectAsClass(Class<E> source, Class<?> tag) {
+        super.selectAsClass(source, tag);
+        return this;
+    }
+
+    @Override
+    public <E, F> MPJLambdaWrapperX<T> selectSub(Class<E> clazz, Consumer<MPJLambdaWrapper<E>> consumer, SFunction<F, ?> alias) {
+        super.selectSub(clazz, consumer, alias);
+        return this;
+    }
+
+    @Override
+    public <E, F> MPJLambdaWrapperX<T> selectSub(Class<E> clazz, String st, Consumer<MPJLambdaWrapper<E>> consumer, SFunction<F, ?> alias) {
+        super.selectSub(clazz, st, consumer, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column) {
+        super.selectCount(column);
+        return this;
+    }
+
+    @Override
+    public MPJLambdaWrapperX<T> selectCount(Object column, String alias) {
+        super.selectCount(column, alias);
+        return this;
+    }
+
+    @Override
+    public <X> MPJLambdaWrapperX<T> selectCount(Object column, SFunction<X, ?> alias) {
+        super.selectCount(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column, String alias) {
+        super.selectCount(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectCount(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column) {
+        super.selectSum(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column, String alias) {
+        super.selectSum(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectSum(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column) {
+        super.selectMax(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column, String alias) {
+        super.selectMax(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectMax(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column) {
+        super.selectMin(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column, String alias) {
+        super.selectMin(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectMin(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column) {
+        super.selectAvg(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column, String alias) {
+        super.selectAvg(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectAvg(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column) {
+        super.selectLen(column);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column, String alias) {
+        super.selectLen(column, alias);
+        return this;
+    }
+
+    @Override
+    public <S, X> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column, SFunction<X, ?> alias) {
+        super.selectLen(column, alias);
+        return this;
+    }
+
+    @Override
+    public <D, X> MPJLambdaWrapperX<T> leftJoin(Class<D> clazz, SFunction<D, ?> left, SFunction<X, ?> right) {
+        super.join("LEFT JOIN", clazz, left, right);
+        return this;
+    }
+
+}

+ 170 - 0
framework/src/main/java/com/hys/app/framework/database/mybatisplus/wrapper/QueryChainWrapperX.java

@@ -0,0 +1,170 @@
+package com.hys.app.framework.database.mybatisplus.wrapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.hys.app.framework.database.WebPage;
+import com.hys.app.framework.util.PageConvert;
+import com.hys.app.framework.database.BaseQueryParam;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+import java.util.List;
+
+import static com.hys.app.framework.database.BaseQueryParam.PAGE_NONE;
+
+/**
+ * 拓展 MyBatis Plus QueryChainWrapper 类,主要增加如下功能:
+ *
+ * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
+ *
+ * @param <T> 数据类型
+ */
+public class QueryChainWrapperX<T> extends QueryChainWrapper<T> {
+
+    public QueryChainWrapperX(BaseMapper<T> baseMapper) {
+        super(baseMapper);
+    }
+
+    public QueryChainWrapperX<T> likeIfPresent(String column, String val) {
+        if (StringUtils.hasText(val)) {
+            return (QueryChainWrapperX<T>) super.like(column, val);
+        }
+        return this;
+    }
+
+    public QueryChainWrapperX<T> inIfPresent(String column, Collection<?> values) {
+        if (!CollectionUtils.isEmpty(values)) {
+            return (QueryChainWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public QueryChainWrapperX<T> inIfPresent(String column, Object... values) {
+        if (!ArrayUtils.isEmpty(values)) {
+            return (QueryChainWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public QueryChainWrapperX<T> eqIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryChainWrapperX<T>) super.eq(column, val);
+        }
+        return this;
+    }
+
+    public QueryChainWrapperX<T> neIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryChainWrapperX<T>) super.ne(column, val);
+        }
+        return this;
+    }
+
+    public QueryChainWrapperX<T> gtIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryChainWrapperX<T>) super.gt(column, val);
+        }
+        return this;
+    }
+
+    public QueryChainWrapperX<T> geIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryChainWrapperX<T>) super.ge(column, val);
+        }
+        return this;
+    }
+
+    public QueryChainWrapperX<T> ltIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryChainWrapperX<T>) super.lt(column, val);
+        }
+        return this;
+    }
+
+    public QueryChainWrapperX<T> leIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryChainWrapperX<T>) super.le(column, val);
+        }
+        return this;
+    }
+
+    public QueryChainWrapperX<T> betweenIfPresent(String column, Object val1, Object val2) {
+        if (val1 != null && val2 != null) {
+            return (QueryChainWrapperX<T>) super.between(column, val1, val2);
+        }
+        if (val1 != null) {
+            return (QueryChainWrapperX<T>) ge(column, val1);
+        }
+        if (val2 != null) {
+            return (QueryChainWrapperX<T>) le(column, val2);
+        }
+        return this;
+    }
+
+    public QueryChainWrapperX<T> betweenIfPresent(String column, Object[] values) {
+        if (values!= null && values.length != 0 && values[0] != null && values[1] != null) {
+            return (QueryChainWrapperX<T>) super.between(column, values[0], values[1]);
+        }
+        if (values!= null && values.length != 0 && values[0] != null) {
+            return (QueryChainWrapperX<T>) ge(column, values[0]);
+        }
+        if (values!= null && values.length != 0 && values[1] != null) {
+            return (QueryChainWrapperX<T>) le(column, values[1]);
+        }
+        return this;
+    }
+
+    // ========== 重写父类方法,方便链式调用 ==========
+
+    @Override
+    public QueryChainWrapperX<T> eq(boolean condition, String column, Object val) {
+        super.eq(condition, column, val);
+        return this;
+    }
+
+    @Override
+    public QueryChainWrapperX<T> eq(String column, Object val) {
+        super.eq(column, val);
+        return this;
+    }
+
+    @Override
+    public QueryChainWrapperX<T> orderByDesc(String column) {
+        super.orderByDesc(true, column);
+        return this;
+    }
+
+    @Override
+    public QueryChainWrapperX<T> last(String lastSql) {
+        super.last(lastSql);
+        return this;
+    }
+
+    @Override
+    public QueryChainWrapperX<T> in(String column, Collection<?> coll) {
+        super.in(column, coll);
+        return this;
+    }
+
+    /**
+     * 分页查询
+     * @param pageParam 分页查询参数
+     * @return 分页数据
+     */
+    public WebPage<T> page(BaseQueryParam pageParam) {
+        // 如果不分页,直接查列表
+        if(pageParam.getPageSize() == PAGE_NONE){
+            List<T> list = list();
+            return new WebPage<>(pageParam.getPageNo(), (long) list.size(), pageParam.getPageSize(), list);
+        }
+
+        IPage<T> mpPage = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
+        super.page(mpPage);
+        return PageConvert.convert(mpPage);
+    }
+
+}

+ 140 - 0
framework/src/main/java/com/hys/app/framework/database/mybatisplus/wrapper/QueryWrapperX.java

@@ -0,0 +1,140 @@
+package com.hys.app.framework.database.mybatisplus.wrapper;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+
+/**
+ * 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能:
+ *
+ * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
+ *
+ * @param <T> 数据类型
+ */
+public class QueryWrapperX<T> extends QueryWrapper<T> {
+
+    public QueryWrapperX<T> likeIfPresent(String column, String val) {
+        if (StringUtils.hasText(val)) {
+            return (QueryWrapperX<T>) super.like(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> inIfPresent(String column, Collection<?> values) {
+        if (!CollectionUtils.isEmpty(values)) {
+            return (QueryWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> inIfPresent(String column, Object... values) {
+        if (!ArrayUtils.isEmpty(values)) {
+            return (QueryWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> eqIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.eq(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> neIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.ne(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> gtIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.gt(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> geIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.ge(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> ltIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.lt(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> leIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.le(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> betweenIfPresent(String column, Object val1, Object val2) {
+        if (val1 != null && val2 != null) {
+            return (QueryWrapperX<T>) super.between(column, val1, val2);
+        }
+        if (val1 != null) {
+            return (QueryWrapperX<T>) ge(column, val1);
+        }
+        if (val2 != null) {
+            return (QueryWrapperX<T>) le(column, val2);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> betweenIfPresent(String column, Object[] values) {
+        if (values!= null && values.length != 0 && values[0] != null && values[1] != null) {
+            return (QueryWrapperX<T>) super.between(column, values[0], values[1]);
+        }
+        if (values!= null && values.length != 0 && values[0] != null) {
+            return (QueryWrapperX<T>) ge(column, values[0]);
+        }
+        if (values!= null && values.length != 0 && values[1] != null) {
+            return (QueryWrapperX<T>) le(column, values[1]);
+        }
+        return this;
+    }
+
+    // ========== 重写父类方法,方便链式调用 ==========
+
+    @Override
+    public QueryWrapperX<T> eq(boolean condition, String column, Object val) {
+        super.eq(condition, column, val);
+        return this;
+    }
+
+    @Override
+    public QueryWrapperX<T> eq(String column, Object val) {
+        super.eq(column, val);
+        return this;
+    }
+
+    @Override
+    public QueryWrapperX<T> orderByDesc(String column) {
+        super.orderByDesc(true, column);
+        return this;
+    }
+
+    @Override
+    public QueryWrapperX<T> last(String lastSql) {
+        super.last(lastSql);
+        return this;
+    }
+
+    @Override
+    public QueryWrapperX<T> in(String column, Collection<?> coll) {
+        super.in(column, coll);
+        return this;
+    }
+
+}

+ 36 - 0
framework/src/main/java/com/hys/app/framework/datapermission/annotation/DataPermission.java

@@ -0,0 +1,36 @@
+package com.hys.app.framework.datapermission.annotation;
+
+import com.hys.app.framework.datapermission.rule.DataPermissionRule;
+
+import java.lang.annotation.*;
+
+/**
+ * 数据权限注解
+ * 可声明在类或者方法上,标识使用的数据权限规则
+ *
+ * @author 张崧
+ * @since 2023-12-04
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataPermission {
+
+    /**
+     * 当前类或方法是否开启数据权限
+     * 即使不添加 @DataPermission 注解,默认是开启状态
+     * 可通过设置 enable 为 false 禁用
+     */
+    boolean enable() default true;
+
+    /**
+     * 生效的数据权限规则数组,优先级高于 {@link #excludeRules()}
+     */
+    Class<? extends DataPermissionRule>[] includeRules() default {};
+
+    /**
+     * 排除的数据权限规则数组,优先级最低
+     */
+    Class<? extends DataPermissionRule>[] excludeRules() default {};
+
+}

+ 36 - 0
framework/src/main/java/com/hys/app/framework/datapermission/aop/DataPermissionAnnotationAdvisor.java

@@ -0,0 +1,36 @@
+package com.hys.app.framework.datapermission.aop;
+
+import com.hys.app.framework.datapermission.annotation.DataPermission;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import org.aopalliance.aop.Advice;
+import org.springframework.aop.Pointcut;
+import org.springframework.aop.support.AbstractPointcutAdvisor;
+import org.springframework.aop.support.ComposablePointcut;
+import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
+
+/**
+ * {@link DataPermission} 注解的 Advisor 实现类
+ *
+ * @author 张崧
+ */
+@Getter
+@EqualsAndHashCode(callSuper = true)
+public class DataPermissionAnnotationAdvisor extends AbstractPointcutAdvisor {
+
+    private final Advice advice;
+
+    private final Pointcut pointcut;
+
+    public DataPermissionAnnotationAdvisor() {
+        this.advice = new DataPermissionAnnotationInterceptor();
+        this.pointcut = this.buildPointcut();
+    }
+
+    protected Pointcut buildPointcut() {
+        Pointcut classPointcut = new AnnotationMatchingPointcut(DataPermission.class, true);
+        Pointcut methodPointcut = new AnnotationMatchingPointcut(null, DataPermission.class, true);
+        return new ComposablePointcut(classPointcut).union(methodPointcut);
+    }
+
+}

+ 73 - 0
framework/src/main/java/com/hys/app/framework/datapermission/aop/DataPermissionAnnotationInterceptor.java

@@ -0,0 +1,73 @@
+package com.hys.app.framework.datapermission.aop;
+
+import com.hys.app.framework.datapermission.annotation.DataPermission;
+import lombok.Getter;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.core.MethodClassKey;
+import org.springframework.core.annotation.AnnotationUtils;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * {@link DataPermission} 注解的拦截器
+ * 1. 在执行方法前,将 @DataPermission 注解入栈
+ * 2. 在执行方法后,将 @DataPermission 注解出栈
+ *
+ * @author 张崧
+ * @since 2023-12-04
+ */
+@DataPermission // 该注解,用于 {@link DATA_PERMISSION_NULL} 的空对象
+public class DataPermissionAnnotationInterceptor implements MethodInterceptor {
+
+    /**
+     * DataPermission 空对象,用于方法无 {@link DataPermission} 注解时,使用 DATA_PERMISSION_NULL 进行占位
+     */
+    static final DataPermission DATA_PERMISSION_NULL = DataPermissionAnnotationInterceptor.class.getAnnotation(DataPermission.class);
+
+    @Getter
+    private final Map<MethodClassKey, DataPermission> dataPermissionCache = new ConcurrentHashMap<>();
+
+    @Override
+    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
+        // 入栈
+        DataPermission dataPermission = this.findAnnotation(methodInvocation);
+        if (dataPermission != null) {
+            DataPermissionContextHolder.add(dataPermission);
+        }
+        try {
+            // 执行逻辑
+            return methodInvocation.proceed();
+        } finally {
+            // 出栈
+            if (dataPermission != null) {
+                DataPermissionContextHolder.remove();
+            }
+        }
+    }
+
+    private DataPermission findAnnotation(MethodInvocation methodInvocation) {
+        // 1. 从缓存中获取
+        Method method = methodInvocation.getMethod();
+        Object targetObject = methodInvocation.getThis();
+        Class<?> clazz = targetObject != null ? targetObject.getClass() : method.getDeclaringClass();
+        MethodClassKey methodClassKey = new MethodClassKey(method, clazz);
+        DataPermission dataPermission = dataPermissionCache.get(methodClassKey);
+        if (dataPermission != null) {
+            return dataPermission != DATA_PERMISSION_NULL ? dataPermission : null;
+        }
+
+        // 2.1 从方法中获取
+        dataPermission = AnnotationUtils.findAnnotation(method, DataPermission.class);
+        // 2.2 从类上获取
+        if (dataPermission == null) {
+            dataPermission = AnnotationUtils.findAnnotation(clazz, DataPermission.class);
+        }
+        // 2.3 添加到缓存中
+        dataPermissionCache.put(methodClassKey, dataPermission != null ? dataPermission : DATA_PERMISSION_NULL);
+        return dataPermission;
+    }
+
+}

+ 65 - 0
framework/src/main/java/com/hys/app/framework/datapermission/aop/DataPermissionContextHolder.java

@@ -0,0 +1,65 @@
+package com.hys.app.framework.datapermission.aop;
+
+
+import com.alibaba.ttl.TransmittableThreadLocal;
+import com.hys.app.framework.datapermission.annotation.DataPermission;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * {@link DataPermission} 注解的 Context 上下文
+ *
+ * @author 张崧
+ * @since 2023-12-04
+ */
+public class DataPermissionContextHolder {
+
+    /**
+     * 使用 List 的原因,可能存在方法的嵌套调用
+     */
+    private static final ThreadLocal<LinkedList<DataPermission>> DATA_PERMISSIONS =
+            TransmittableThreadLocal.withInitial(LinkedList::new);
+
+    /**
+     * 获得当前的 DataPermission 注解
+     *
+     * @return DataPermission 注解
+     */
+    public static DataPermission get() {
+        return DATA_PERMISSIONS.get().peekLast();
+    }
+
+    /**
+     * 入栈 DataPermission 注解
+     *
+     * @param dataPermission DataPermission 注解
+     */
+    public static void add(DataPermission dataPermission) {
+        DATA_PERMISSIONS.get().addLast(dataPermission);
+    }
+
+    /**
+     * 出栈 DataPermission 注解
+     *
+     * @return DataPermission 注解
+     */
+    public static DataPermission remove() {
+        DataPermission dataPermission = DATA_PERMISSIONS.get().removeLast();
+        // 无元素时,清空 ThreadLocal
+        if (DATA_PERMISSIONS.get().isEmpty()) {
+            DATA_PERMISSIONS.remove();
+        }
+        return dataPermission;
+    }
+
+    /**
+     * 获得所有 DataPermission
+     *
+     * @return DataPermission 队列
+     */
+    public static List<DataPermission> getAll() {
+        return DATA_PERMISSIONS.get();
+    }
+
+}

+ 44 - 0
framework/src/main/java/com/hys/app/framework/datapermission/config/DataPermissionConfiguration.java

@@ -0,0 +1,44 @@
+package com.hys.app.framework.datapermission.config;
+
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.hys.app.framework.datapermission.db.DataPermissionDatabaseInterceptor;
+import com.hys.app.framework.datapermission.aop.DataPermissionAnnotationAdvisor;
+import com.hys.app.framework.datapermission.rule.DataPermissionRule;
+import com.hys.app.framework.datapermission.rule.DataPermissionRuleFactory;
+import com.hys.app.framework.datapermission.rule.DataPermissionRuleFactoryImpl;
+import com.hys.app.framework.util.MyBatisPlusUtils;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+/**
+ * 数据权限的自动配置类
+ *
+ * @author 张崧
+ */
+@Configuration
+public class DataPermissionConfiguration {
+
+    @Bean
+    public DataPermissionRuleFactory dataPermissionRuleFactory(List<DataPermissionRule> rules) {
+        return new DataPermissionRuleFactoryImpl(rules);
+    }
+
+    @Bean
+    public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(MybatisPlusInterceptor interceptor,
+                                                                               DataPermissionRuleFactory ruleFactory) {
+        // 创建 DataPermissionDatabaseInterceptor 拦截器
+        DataPermissionDatabaseInterceptor inner = new DataPermissionDatabaseInterceptor(ruleFactory);
+        // 添加到 interceptor 中
+        // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定
+        MyBatisPlusUtils.addInterceptor(interceptor, inner, 0);
+        return inner;
+    }
+
+    @Bean
+    public DataPermissionAnnotationAdvisor dataPermissionAnnotationAdvisor() {
+        return new DataPermissionAnnotationAdvisor();
+    }
+
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно