Compare commits

...

74 Commits

Author SHA1 Message Date
Yadeno 4c9bcca77f
Merge pull request #42 from copyboy/master
Update format_username.py
2024-10-11 18:22:44 +08:00
Qingdong Zhang 06721a35af
Update format_username.py
钉钉更新了企业邮箱字段,
原先biz_mail已不生效, 前端抓取发现已改用orgEmail
2024-10-11 17:32:52 +08:00
Leven d0b86b543c FIX some bug 2023-02-13 09:25:03 +08:00
Leven 2b2c76c162 Merge remote-tracking branch 'github/master' into gitee_master 2023-02-10 23:33:40 +08:00
Leven 8278895041 Merge remote-tracking branch 'github/master' into gitee_master 2023-02-10 23:33:22 +08:00
Leven c42e8bb4b7 Merge remote-tracking branch 'github/master' into gitee_master 2023-02-10 17:56:50 +08:00
Leven 7bd470f32c fix session cache bug 2023-02-10 17:55:01 +08:00
Leven 1379f26670 Merge remote-tracking branch 'github/master' into gitee_master 2023-02-10 17:50:20 +08:00
Leven cd33b5e9a9 fix session cache bug 2023-02-10 17:50:07 +08:00
Leven aedfcb22f5 Merge remote-tracking branch 'github/master' into gitee_master 2023-02-10 17:28:21 +08:00
Leven 85587fa3ae fix session cache bug 2023-02-10 17:28:12 +08:00
Leven 63026a2e3c Merge remote-tracking branch 'github/master' into gitee_master 2023-02-10 17:17:27 +08:00
Leven 8e2ffb552f fix session cache bug 2023-02-10 17:17:14 +08:00
Leven 87d5432cfd Merge remote-tracking branch 'github/master' into gitee_master 2023-02-10 17:02:40 +08:00
Leven 33f32999ba fix session cache bug 2023-02-10 17:02:24 +08:00
Leven 429fadc010 Merge remote-tracking branch 'github/master' into gitee_master 2023-02-10 16:50:01 +08:00
Leven 0839baacab fix session cache bug 2023-02-10 16:49:44 +08:00
Leven 2625ef2cb3 Merge remote-tracking branch 'github/master' into gitee_master 2023-02-10 16:45:24 +08:00
Leven ea5d2e131d fix session cache bug 2023-02-10 16:45:13 +08:00
Leven 42a362c7c4 Merge remote-tracking branch 'github/master' into gitee_master 2023-02-10 16:06:37 +08:00
Leven 362d78c9b5 fix session cache bug 2023-02-10 16:06:24 +08:00
Leven a880da7fb2 Merge remote-tracking branch 'github/master' into gitee_master 2023-02-10 14:28:58 +08:00
Leven dd48467161 Merge remote-tracking branch 'github/master' into gitee_master 2023-02-10 14:28:45 +08:00
Leven 97b60fe384 Merge remote-tracking branch 'github/master' into gitee_master 2023-02-10 12:32:07 +08:00
Leven f7390e1e03 fix session cache bug 2023-02-10 12:31:52 +08:00
Leven a68fc3f307 fix session cache bug 2023-02-10 12:29:39 +08:00
Leven 1752b1d16f fix some bugs 2023-02-06 22:01:00 +08:00
Leven a8afae7c2f Merge remote-tracking branch 'github/master' into gitee_master 2023-01-18 09:34:33 +08:00
Leven 263cb7ce6f Merge branch 'feature/update_to_layui' of https://gitee.com/yadeno/ad-password-self-service into gitee_master
# Conflicts:
#	.gitignore
#	LICENSE
2023-01-18 09:34:30 +08:00
Yadeno cda291f075
Merge pull request #30 from capricornxl/feature/update_to_layui
更新一些小功能
2023-01-17 20:01:23 +08:00
Leven e7130fa983 更新一些小功能 2023-01-17 19:58:29 +08:00
Yadeno 30212b2988
Merge pull request #29 from capricornxl/feature/update_to_layui
add screenshot
2023-01-17 10:34:01 +08:00
Leven ba81a90df7 add screenshot 2023-01-17 10:33:08 +08:00
Yadeno 2ae4acbffc
Merge pull request #28 from capricornxl/feature/update_to_layui
应用内授权页面独立
2023-01-16 16:07:24 +08:00
Leven 2e95fa3b8e 移除auto-install.sh中的redis部署步骤
抽出应用中的授权验证跳转的代码,单独做成一个auth页面,可实现选择首页是进入修改密码,还是自动跳转重置页面
调整部分文件说明
2023-01-16 16:05:29 +08:00
Yadeno 4f377ba09b
Merge pull request #27 from capricornxl/feature/update_to_layui
Solve some H5 compatibility
2023-01-14 21:16:41 +08:00
Leven b82f2ececb remove dependence redis-sever, now use mem storage
update to layui, support PC and Mobile html render
2023-01-14 21:12:13 +08:00
Leven 51a600c8a4 fix redis auto install
fix redis password auto-gen and append to local_settings.py
2023-01-09 23:05:16 +08:00
Leven d10601b9ab fix some css 2022-12-30 10:24:09 +08:00
Leven fa3f47e496 fix some html 2022-12-29 17:19:38 +08:00
Leven 729c9ae571 Merge remote-tracking branch 'github/master' 2022-12-24 14:47:34 +08:00
Leven bf29a8234c fix readme.md 2022-12-24 14:47:19 +08:00
Leven 92bb837d37 fix readme.md 2022-12-23 23:19:44 +08:00
Leven 74160ee9e4 修改钉钉/企业微信直接使用内部应用免密登录的方式来验证,不再支持扫码。
由于一些API的权限发生变化,导致一些关键信息无法获取,所以做以上改变。
2022-12-23 23:17:56 +08:00
Yadeno 0a2135d2b8
Delete resetpwd/__pycache__ directory 2022-12-20 14:36:33 +08:00
Yadeno 9de7c92e09
Delete resetpwd/migrations/__pycache__ directory 2022-12-20 14:36:22 +08:00
Yadeno f354430154
Delete pwdselfservice/__pycache__ directory 2022-12-20 14:36:08 +08:00
Leven c5bc154924 修改钉钉/企业微信直接使用内部应用免密登录的方式来验证,不再支持扫码。
由于一些API的权限发生变化,导致一些关键信息无法获取,所以做以上改变。
删除了无用的代码,其它没啥变化,没太多时间重写,先就这么着吧。
2022-12-20 13:30:36 +08:00
Leven 2e886dc6e8 修改钉钉/企业微信直接使用内部应用免密登录的方式来验证,不再支持扫码。
由于一些API的权限发生变化,导致一些关键信息无法获取,所以做以上改变。
2022-12-17 00:23:41 +08:00
Leven 6b90cd3be7 oauth2 support 2022-12-16 11:57:40 +08:00
Leven 4ed8c1e0e2 修改utils.ad_ops self.doamin:
如果doamin\\user中doamin部分被写成域名格式, 只提取DOMAIN部分
2021-12-24 22:57:35 +08:00
Leven b589a49072 修复通过自动部署脚本部署之后,无法正常打开的问题
uwsgi.ini和uwsgiserver中的配置修改成通过脚本变量自动替换。
如果是手动安装的,自行修改配置即可,相信能手动部署的也是有一定Linux基础的同学。
2021-09-23 09:02:40 +08:00
向乐🌌 75b3916191 修改format2username方法,返回元组作为结果判断。
悠resetpwd.views 将所有format2username兼容处理,防止当用户名为空时,能正确抛出异常给前端页面。
修复resetpwd.views callback_check中一个BUG(如果用户不存在或未激活,会存在通过format2username拿不到用户名的情况,在这里就直接抛出user_info整个json)
2021-08-05 09:35:56 +08:00
向乐🌌 4f8fcb68b4 添加了新的模板,旧模板也保留了,如果想切换旧样式,请自行把渲染的html模板改成不带v1的。 2021-07-06 10:16:16 +08:00
Leven be0b4fdc16 Initial commit 2021-06-26 06:55:29 +00:00
向乐🌌 a9751d47b4 BUG FIX: ops_account返回必须使用渲染 2021-06-26 14:53:18 +08:00
向乐🌌 72fe535f3c BUG FIX 2021-05-22 17:27:42 +08:00
向乐🌌 870cc106ba BUG FIX: ops_account返回必须使用渲染 2021-05-22 15:19:28 +08:00
向乐🌌 9c8a82417e 修复utils目录自定义模块中的处理逻辑return结果不正确,导致Django无法正常在前台显示结果的BUG 2021-05-21 13:47:26 +08:00
向乐🌌 28661b0d9c dynamic create crypto_key when django running 2021-05-20 13:11:55 +08:00
向乐🌌 ca84457c1f 修复一些BUG
将修改密码的代码逻辑做复用处理,简化代码。
2021-05-20 12:02:11 +08:00
向乐🌌 4ad2a126d8 update readme.md 2021-05-19 17:18:13 +08:00
向乐🌌 89b1c0de46 ### 2021/05/19 -- 更新:
+ 添加了企业微信支持,修改pwdselfservice/local_settings.py中的SCAN_CODE_TYPE = 'DING'或SCAN_CODE_TYPE = 'WEWORK',区分使用哪个应用扫码验证
+ 添加Reids缓存Token支持,如果不配置Redis则使用MemoryStorage缓存到内存中
2021-05-19 17:07:26 +08:00
向乐🌌 00d1d9a03c update 2021-04-27 14:19:15 +08:00
向乐🌌 87348ae6a1 默认的
django=2.2 version
2021-04-27 14:16:01 +08:00
向乐🌌 5e09b59eca 添加当通过钉钉回调获取用户信息失败时
输出准确的错误信息
2021-04-27 14:12:49 +08:00
向乐🌌 80fd63526e update readme.md 2021-04-23 15:54:23 +08:00
向乐🌌 d4ebe7f217 update readme.md 2021-04-23 15:51:16 +08:00
向乐🌌 36903b64e2 update readme.md 2021-04-23 15:48:27 +08:00
向乐🌌 bc04829070 ### 本次升级、修复,请使用最新版:
+ 升级Python版本为3.8
+ 升级Django到3.2
+ 修复用户名中使用\被转义的问题
+ 重写了dingding模块,因为dingding开发者平台接口鉴权的一些变动,之前的一些接口不能再使用,本次重写。
+ 重写了ad模块,修改账号的一些判断逻辑。
+ 重写了用户账号的格式兼容,现在用户账号可以兼容:username、DOMAIN\username、username@abc.com这三种格式。
+ 优化了整体的代码逻辑,去掉一些冗余重复的代码。
2021-04-23 15:37:54 +08:00
向乐🌌 d8ac7552a6 默认的
django=2.2 version
2021-04-19 08:41:22 +08:00
向乐🌌 ffbe96425d update license 2020-08-18 11:39:50 +08:00
向乐🌌 27096e9326 因为有些域控没有开启SSL支持,故会提示无法连接到域控。
在readme.md文件中添加了如何处理这类问题的操作方法。
2020-08-18 11:24:34 +08:00
capricornxl cd52c6be26 update readme 2020-04-13 22:25:04 +08:00
126 changed files with 3247 additions and 2067 deletions

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [Xiangle] [Xiangle]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Binary file not shown.

440
auto-install.sh Normal file → Executable file
View File

@ -1,7 +1,33 @@
#!/bin/bash
echo -e "此脚本为快速部署目前只做了Centos版本的如果是其它系统请自行修改下相关命令\n请准备一个新的环境运行\n本脚本会快速安装相关的环境和所需要的服务\n如果你运行脚本的服务器中已经存在如Nginx、Python3等可能会破坏掉原有的应用配置。"
##Check IP
SCRIPT=$(readlink -f $0)
CWD=$(dirname ${SCRIPT})
mkdir -p ${CWD}/.status
os_distro=''
os_version=''
get_selinux=''
gen_password=$(echo "$(hostname)$(date)" |base64 |cut -b 1-24)
function get_os_basic_info() {
if [[ -f /etc/lsb-release ]]; then
os_distro=$(lsb_release -d |awk '{print $2}')
os_version=$(lsb_release -d -s |awk '{print $2}')
os_version_prefix=$(echo ${os_version} |cut -b 1-2)
elif [[ -f /etc/redhat-release ]]; then
os_distro=$(cat /etc/redhat-release |awk '{print $1}')
os_version=$(cat /etc/redhat-release |awk '{print $4}')
os_version_prefix=$(echo ${os_version} |cut -b 1)
get_selinux=$(getenforce)
if [[ ${get_selinux} =~ enforcing|Enforcing ]];then
echo "请先禁用SELINUX~~! ..."
exit 1
fi
else
echo "不能识别的操作系统请选择使用Ubuntu或Centos! ..."
exit 1
fi
}
function check_ip() {
local IP=$1
VALID_CHECK=$(echo $IP|awk -F. '$1<=255&&$2<=255&&$3<=255&&$4<=255{print "yes"}')
@ -16,7 +42,6 @@ function check_ip() {
fi
}
##Check domain
function check_domain() {
local DOMAIN=$1
if echo $DOMAIN |grep -P "(?=^.{4,253}$)(^(?:[a-zA-Z0-9](?:(?:[a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$)" >/dev/null; then
@ -26,7 +51,6 @@ function check_domain() {
fi
}
##Check Port
function check_port() {
local PORT=$1
VALID_CHECK=$(echo $PORT|awk '$1<=65535&&$1>=1{print "yes"}')
@ -41,11 +65,46 @@ function check_port() {
fi
}
function safe_installer() {
local _run_cmd="$@"
if [[ ${os_distro} =~ (CentOS|Redhat) ]]; then
sudo yum makecache
sudo yum install -y ${_run_cmd}
elif [[ ${os_distro} =~ (Ubuntu|Debian) ]]; then
sudo apt-get update
sudo apt-get install -y ${_run_cmd}
else
echo "未适配的操作系统 ${os_distro}"
exit 1
fi
if [[ $? -ne 0 ]]; then
echo "安装 [${_run_cmd}] 失败"
exit 1
fi
}
check_status() {
local status=$1
if [[ ${status} -ne 0 ]]; then
echo "出现错误,请检查后再试 ..."
exit 1
fi
}
get_os_basic_info
echo "============================================================================="
echo " 此脚本为快速部署,支持[Ubuntu, Debian, Centos]
请准备一个新的环境运行,本脚本会快速安装相关的环境和所需要的服务
如果你运行脚本的服务器中已经存在如Nginx、Python3等可能会破坏掉原有的应用配置"
echo " 当前目录:${CWD}"
echo " 操作系统发行版本:${os_distro}, 系统版本:${os_version} ..."
echo "============================================================================="
while :; do echo
echo "请确认你此台服务器是全新干净的,以防此脚本相关操作对正在运行的服务造成影响(不可逆)。"
echo "请确认你此台服务器是全新干净的,以防此脚本相关操作对正在运行的服务造成影响(不可逆) ..."
read -p "请确认是否继续执行,输入 [y/n]: " ensure_yn
if [[ ! "${ensure_yn}" =~ ^[y,n]$ ]]; then
echo "输入有误,请输入 y 或 n"
echo "输入有误,请输入 y 或 n ..."
else
break
fi
@ -55,172 +114,282 @@ if [[ "${ensure_yn}" = n ]]; then
exit 0
fi
echo "======================================================================="
while :; do echo
read -p "请输入密码自助平台使用的本机IP: " PWD_SELF_SERVICE_IP
check_ip ${PWD_SELF_SERVICE_IP}
check_ip "${PWD_SELF_SERVICE_IP}"
if [[ $? -ne 0 ]]; then
echo "---输入的IP地址格式有误请重新输入"
echo "---输入的IP地址格式有误请重新输入 ..."
else
break
fi
done
echo "======================================================================="
while :; do echo
read -p "请输入密码自助平台使用的端口: " PWD_SELF_SERVICE_PORT
check_port ${PWD_SELF_SERVICE_PORT}
read -p "请输入密码自助平台使用的端口(不要和Nginx[80]一样): " PWD_SELF_SERVICE_PORT
check_port "${PWD_SELF_SERVICE_PORT}"
if [[ $? -ne 0 ]]; then
echo "---输入的端口有误,请重新输入"
echo "---输入的端口有误,请重新输入 ..."
else
break
fi
done
echo "======================================================================="
while :; do echo
read -p "请输入密码自助平台使用域名例如pwd.abc.com: " PWD_SELF_SERVICE_DOMAIN
check_domain ${PWD_SELF_SERVICE_DOMAIN}
read -p "请输入密码自助平台使用域名例如pwd.abc.com不需要加http://或https:// " PWD_SELF_SERVICE_DOMAIN
check_domain "${PWD_SELF_SERVICE_DOMAIN}"
if [[ $? -ne 0 ]]; then
echo "---输入的域名格式有误,请重新输入"
echo "---输入的域名格式有误,请重新输入 ..."
else
break
fi
done
##当前脚本的绝对路径
SHELL_FOLDER=$(dirname $(readlink -f "$0"))
echo
echo "==============================================="
echo "开始部署 ..."
echo "关闭SELINUX"
sudo setenforce 0
sudo sed -i 's@SELINUX=*@SELINUX=disabled@g' /etc/selinux/config
echo "DONE....."
echo "关闭防火墙"
sudo systemctl disable firewalld
sudo systemctl stop firewalld
echo "DONE....."
echo "初始化编译环境----------"
sudo yum install gcc patch libffi-devel python-devel zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel wget psmisc -y
echo "======================================================================="
echo "初始化编译环境完成"
echo "======================================================================="
##Quick install nginx
echo "======================================================================="
echo "安装 Nginx"
sudo cat << EOF > /etc/yum.repos.d/nginx.repo
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/7/\$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
EOF
sudo yum makecache fast
sudo yum install nginx -y
if [[ $? -eq 0 ]]
then
sudo systemctl enable nginx
sudo systemctl start nginx
echo "======================================================================="
echo "nginx 安装成功!"
echo "======================================================================="
else
echo "======================================================================="
echo "nginx 安装失败!"
echo "======================================================================="
exit 1
if [[ ! -f "${CWD}/.status/.init_repo.Done" ]]; then
echo "处理源配置,改成国内源 ..."
if [[ ${os_distro} =~ (CentOS|Centos) ]]; then
if [[ ${os_version_prefix} -lt 9 ]];then
sudo cp -a /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.$(date '+%Y%m%d%H%M%S')
rm -f /etc/yum.repos.d/CentOS-Base.repo
cp -a ${CWD}/conf/CentOS-${os_version_prefix}-reg.repo /etc/yum.repos.d/CentOS-Base.repo
check_status $?
sudo yum makecache
sudo yum install --nogpgcheck -y epel-release
sed -i "s/#baseurl/baseurl/g" /etc/yum.repos.d/epel.repo
sed -i "s/metalink/#metalink/g" /etc/yum.repos.d/epel.repo
sed -i "s@https\?://download.fedoraproject.org/pub@https://repo.huaweicloud.com@g" /etc/yum.repos.d/epel.repo
check_status $?
fi
fi
if [[ ${os_distro} =~ (Ubuntu|ubuntu) ]]; then
sudo cp -a /etc/apt/sources.list /etc/apt/sources.list.bak
sudo sed -i "s@http://.*archive.ubuntu.com@http://repo.huaweicloud.com@g" /etc/apt/sources.list
sudo sed -i "s@http://.*security.ubuntu.com@http://repo.huaweicloud.com@g" /etc/apt/sources.list
check_status $?
fi
if [[ ${os_distro} =~ (Debian|debian) ]]; then
sudo cp -a /etc/apt/sources.list /etc/apt/sources.list.bak
sed -i "s@http://ftp.debian.org@https://repo.huaweicloud.com@g" /etc/apt/sources.list
sed -i "s@http://security.debian.org@https://repo.huaweicloud.com@g" /etc/apt/sources.list
check_status $?
fi
touch "${CWD}/.status/.init_repo.Done"
fi
##install python3
##如果之前用此脚本安装过python3后续就不会再次安装。
if [[ -f "/usr/share/python-3.6.9/bin/python3" ]]
then
echo "己发现Python3将不会安装。"
else
if [[ -f "Python-3.6.9.tar.xz" ]]
then
echo "将安装Python3.6.9"
tar xf Python-3.6.9.tar.xz
cd Python-3.6.9
sudo ./configure --prefix=/usr/share/python-3.6.9 && make && make install
else
echo "脚本目录下没有发现Python3.6.9.tar.xz将会下载python 3.6.9"
sudo wget https://www.python.org/ftp/python/3.6.9/Python-3.6.9.tar.xz
tar xf Python-3.6.9.tar.xz
cd Python-3.6.9
sudo ./configure --prefix=/usr/share/python-3.6.9 && make && make install
if [[ ! -f "${CWD}/.status/.init_package.Done" ]]; then
echo "初始化依赖包 ..."
if [[ ${os_distro} =~ (CentOS|Redhat) ]]; then
sudo yum-complete-transaction --cleanup-only
sudo yum makecache
sudo yum install --nogpgcheck -y @development zlib-devel bzip2 bzip2-devel readline-devel \
sqlite-devel openssl-devel xz-devel libffi-devel ncurses-devel readline-devel tk-devel \
libpcap-devel findutils wget nginx tar initscripts
elif [[ ${os_distro} =~ (Ubuntu|Debian) ]]; then
sudo apt-get install apt-transport-https ca-certificates -y
sudo apt-get update
sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev libffi-dev liblzma-dev python-openssl wget nginx tar initscripts
fi
if [[ $? -eq 0 ]]
then
echo "创建python3和pip3的软件链接"
cd ${SHELL_FOLDER}
sudo ln -svf /usr/share/python-3.6.9/bin/python3 /usr/bin/python3
sudo ln -svf /usr/share/python-3.6.9/bin/pip3 /usr/bin/pip3
echo "======================================================================="
echo "Python3 安装成功!"
echo "======================================================================="
if [[ $? -eq 0 ]]; then
echo "初始化依赖包完成 ..."
touch ${CWD}/.status/.init_package.Done
else
echo "======================================================================="
echo "Python3 安装失败!"
echo "======================================================================="
echo "初始化依赖包失败 ..."
exit 1
fi
fi
if [[ ! -f "${CWD}/.status/.redis.Done" ]]; then
safe_installer redis
if [[ $? -eq 0 ]]; then
if [[ -f /etc/redis.conf ]]; then
REDIS_CONF=/etc/redis.conf
else
REDIS_CONF=/etc/redis/redis.conf
fi
sed -i 's@^requirepass.*@@g' ${REDIS_CONF}
sed -i "/# requirepass foobared/a requirepass ${gen_password}" ${REDIS_CONF}
sed -i "s@REDIS_PASSWORD.*@REDIS_PASSWORD = r'${gen_password}'@g" ${CWD}/conf/local_settings.py
touch ${CWD}/.status/.redis.Done
echo "${gen_password}" >${CWD}/.status/.redis.Done
echo "安装 redis-server 成功"
else
echo "安装 redis-server 失败,请重新运行本脚本再试"
fi
else
gen_password=$(cat ${CWD}/.status/.redis.Done)
fi
redis_service=''
if [[ -f /usr/lib/systemd/system/redis.service ]];then
redis_service=redis
elif [[ -f /usr/lib/systemd/system/redis-server.service ]]; then
redis_service='redis-server'
fi
if [[ -z ${redis_service} ]]; then
echo "Redis服务名未能识别到请自行手动重启本机的Redis服务 ..."
else
rm -f /etc/nginx/conf.d/default.conf
rm -f /etc/nginx/sites-enabled/default.conf
systemctl restart ${redis_service}
fi
NGINX_USER=$(grep -E '^user' /etc/nginx/nginx.conf |sed 's@user @@g' |sed 's@;@@g' |awk '{print $1}')
cat <<'EOF' >/etc/nginx/nginx.conf
worker_processes auto;
pid /run/nginx.pid;
events {
use epoll;
worker_connections 2048;
}
http {
include mime.types;
default_type application/octet-stream;
server_tokens off;
client_header_buffer_size 16k;
client_body_buffer_size 128k;
keepalive_timeout 65;
keepalive_requests 120;
sendfile on;
tcp_nodelay on;
tcp_nopush on;
charset utf-8;
autoindex off;
# gzip
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 3;
gzip_disable "MSIE [1-6]\.";
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png application/json;
gzip_vary on;
gzip_static on;
# upload file
client_max_body_size 0;
proxy_buffering off;
proxy_send_timeout 10m;
proxy_read_timeout 10m;
proxy_connect_timeout 10m;
proxy_request_buffering off;
# log
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log;
# config file
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*.conf;
}
EOF
sed -i "1i\user ${NGINX_USER};" /etc/nginx/nginx.conf
PYTHON_VER='3.8.16'
PYTHON_INSTALL_DIR=/usr/share/python-${PYTHON_VER}
PYTHON_VENV_DIR=${CWD}/pwd_venv
if [[ -f "${CWD}/.status/.python3.Done" ]];then
echo "Python3己部署跳过 ..."
else
if [[ -f "${CWD}/Python-${PYTHON_VER}.tar.xz" ]] && [[ -f "${CWD}/python.${PYTHON_VER}.md5" ]]; then
python3_md5=$(md5sum "${CWD}/Python-${PYTHON_VER}.tar.xz" |awk '{print $1}')
python3_md5_record=$(cat ${CWD}/python.${PYTHON_VER}.md5)
if [[ x"${python3_md5}" != x"${python3_md5_record}" ]]; then
rm -f "${CWD}/Python-${PYTHON_VER}.tar.xz"
rm -f "${CWD}/python.${PYTHON_VER}.md5"
fi
else
echo "无Python${PYTHON_VER}.tar.xz执行下载python ${PYTHON_VER} ..."
rm -f "${CWD}/Python-${PYTHON_VER}.tar.xz"
sudo wget -c -t 10 -T 120 https://repo.huaweicloud.com/python/${PYTHON_VER}/Python-${PYTHON_VER}.tar.xz -O ${CWD}/Python-${PYTHON_VER}.tar.xz
md5sum "${CWD}/Python-${PYTHON_VER}.tar.xz" |awk '{print $1}' > ${CWD}/python.${PYTHON_VER}.md5
if [[ $? -ne 0 ]]; then
echo "下载${PYTHON_VER}/Python-${PYTHON_VER}.tar.xz失败请重新运行本脚本再次重试 ..."
exit 1
fi
fi
echo "执行安装Python${PYTHON_VER} ..."
tar xf ${CWD}/Python-${PYTHON_VER}.tar.xz -C ${CWD}/
cd "${CWD}/Python-${PYTHON_VER}" || exit 1
sudo ./configure --prefix=${PYTHON_INSTALL_DIR} && make && make install
if [[ $? -eq 0 ]]; then
echo "创建python虚拟环境 -> ${PYTHON_VENV_DIR} ..."
rm -rf "${PYTHON_VENV_DIR}"
${PYTHON_INSTALL_DIR}/bin/python3 -m venv --copies "${PYTHON_VENV_DIR}"
touch ${CWD}/.status/.python3.Done
echo "Python3 安装成功 ..."
else
echo "Python3 安装失败,请重试 ..."
exit 1
fi
fi
##修改PIP源为国内
mkdir -p ~/.pip
cat << EOF > ~/.pip/pip.conf
cat <<'EOF' > ~/.pip/pip.conf
[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
index-url=https://pypi.tuna.tsinghua.edu.cn/simple
[install]
trusted-host=pypi.tuna.tsinghua.edu.cn
EOF
cd ${SHELL_FOLDER}
echo "====升级pip================"
/usr/bin/pip3 install --upgrade pip
/usr/bin/pip3 install -r requestment
if [[ $? -eq 0 ]]
then
echo "======================================================================="
echo "Pip3 requestment 安装成功!"
echo "======================================================================="
else
echo "======================================================================="
echo "Pip3 requestment 安装失败!"
echo "======================================================================="
exit 1
if [[ ! -f "${CWD}/.status/.pip3.Done" ]]; then
echo "部署pip依赖 ..."
${PYTHON_VENV_DIR}/bin/pip3 install --upgrade pip
${PYTHON_VENV_DIR}/bin/pip3 install wheel setuptools
${PYTHON_VENV_DIR}/bin/pip3 install -r ${CWD}/requirement
if [[ $? -eq 0 ]]; then
touch ${CWD}/.status/.pip3.Done
echo "Pip3 Requirement 安装成功 ..."
else
echo "Pip3 Requirement 安装失败 ..."
exit 1
fi
fi
##处理配置文件
echo "======================================================================="
echo "处理uwsgi.ini配置文件"
sed -i "s@PWD_SELF_SERVICE_HOME@${SHELL_FOLDER}@g" ${SHELL_FOLDER}/uwsgi.ini
sed -i "s@PWD_SELF_SERVICE_IP@${PWD_SELF_SERVICE_IP}@g" ${SHELL_FOLDER}/uwsgi.ini
sed -i "s@PWD_SELF_SERVICE_PORT@${PWD_SELF_SERVICE_PORT}@g" ${SHELL_FOLDER}/uwsgi.ini
echo "处理uwsgi.ini配置文件完成"
echo "处理uwsgi.ini配置文件 ..."
CPU_NUM=$(cat /proc/cpuinfo | grep processor | wc -l)
sed -i "s@CPU_NUM@${CPU_NUM}@g" ${CWD}/uwsgi.ini
sed -i "s@PYTHON_VENV_DIR@${PYTHON_VENV_DIR}@g" ${CWD}/uwsgi.ini
sed -i "s@PWD_SELF_SERVICE_HOME@${CWD}@g" ${CWD}/uwsgi.ini
sed -i "s@PWD_SELF_SERVICE_IP@${PWD_SELF_SERVICE_IP}@g" ${CWD}/uwsgi.ini
sed -i "s@PWD_SELF_SERVICE_PORT@${PWD_SELF_SERVICE_PORT}@g" ${CWD}/uwsgi.ini
echo "处理uwsgi.ini配置文件完成 ..."
echo
echo "处理uwsgiserver启动脚本"
sed -i "s@PWD_SELF_SERVICE_HOME@${SHELL_FOLDER}@g" ${SHELL_FOLDER}/uwsgiserver
echo "处理uwsgiserver启动脚本 ..."
sed -i "s@PWD_SELF_SERVICE_HOME@${CWD}@g" ${CWD}/uwsgiserver
sed -i "s@PYTHON_VENV_DIR@${PYTHON_VENV_DIR}@g" ${CWD}/uwsgiserver
alias cp='cp'
cp -f ${SHELL_FOLDER}/uwsgiserver /etc/init.d/uwsgiserver
cp -rfp ${CWD}/uwsgiserver /etc/init.d/uwsgiserver
chmod +x /etc/init.d/uwsgiserver
chkconfig uwsgiserver on
echo "处理uwsgiserver启动脚本完成"
systemctl daemon-reload
systemctl enable uwsgiserver
echo "处理uwsgiserver启动脚本完成 ..."
echo
sed -i "s@PWD_SELF_SERVICE_DOMAIN@${PWD_SELF_SERVICE_DOMAIN}@g" ${SHELL_FOLDER}/pwdselfservice/local_settings.py
sed -i "s@PWD_SELF_SERVICE_DOMAIN@${PWD_SELF_SERVICE_DOMAIN}@g" ${CWD}/conf/local_settings.py
##Nginx vhost配置
cat << EOF > /etc/nginx/conf.d/pwdselfservice.conf
mkdir -p /etc/nginx/conf.d
cat <<EOF >/etc/nginx/conf.d/pwdselfservice.conf
server {
listen 80;
server_name ${PWD_SELF_SERVICE_DOMAIN} ${PWD_SELF_SERVICE_IP};
@ -235,21 +404,22 @@ server {
access_log off;
}
EOF
rm -f /etc/nginx/conf.d/default.conf
systemctl restart nginx
systemctl start nginx
systemctl start uwsgiserver
echo
echo "======================================================================="
echo
echo "密码自助服务平台的访问地址是http://${PWD_SELF_SERVICE_DOMAIN}或http://${PWD_SELF_SERVICE_IP}"
echo "请确保以上域名能正常解析,否则使用域名无法访问。"
echo "密码自助服务平台的访问地址是http://${PWD_SELF_SERVICE_DOMAIN}或http://${PWD_SELF_SERVICE_IP} ..."
echo "请确保以上域名能正常解析,否则使用域名无法访问 ..."
echo "如果本机防火墙是开启状态,请自行放行端口: [80, ${PWD_SELF_SERVICE_PORT}]"
echo
echo "Uwsgi启动/etc/init.d/uwsgi start"
echo "Uwsgi停止/etc/init.d/uwsgi stop"
echo "Uwsgi重启/etc/init.d/uwsgi restart"
echo "Uwsgi启动/etc/init.d/uwsgiserver start ..."
echo "Uwsgi停止/etc/init.d/uwsgiserver stop ..."
echo "Uwsgi重启/etc/init.d/uwsgiserver restart ..."
echo
echo "Redis Server密码是${gen_password},可在${REDIS_CONF}中查到 ..."
echo
echo "文件${SHELL_FOLDER}/pwdselfservice/local_setting.py中必要参数需要你自行修改"
echo "此文件中有AD和钉钉的一些参数按自己企业的修改"
echo "文件${CWD}/conf/local_setting.py中配置参数请自行确认下是否完整 ..."
echo
echo "======================================================================="

43
conf/CentOS-7-reg.repo Normal file
View File

@ -0,0 +1,43 @@
# CentOS-Base.repo
#
# The mirror system uses the connecting IP address of the client and the
# update status of each mirror to pick mirrors that are updated to and
# geographically close to the client. You should use this for CentOS updates
# unless you are manually picking other mirrors.
#
# If the mirrorlist= does not work for you, as a fall back you can try the
# remarked out baseurl= line instead.
#
#
[base]
name=CentOS-$releasever - Base - repo.huaweicloud.com
baseurl=https://repo.huaweicloud.com/centos/$releasever/os/$basearch/
#mirrorlist=https://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/centos/RPM-GPG-KEY-CentOS-7
#released updates
[updates]
name=CentOS-$releasever - Updates - repo.huaweicloud.com
baseurl=https://repo.huaweicloud.com/centos/$releasever/updates/$basearch/
#mirrorlist=https://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/centos/RPM-GPG-KEY-CentOS-7
#additional packages that may be useful
[extras]
name=CentOS-$releasever - Extras - repo.huaweicloud.com
baseurl=https://repo.huaweicloud.com/centos/$releasever/extras/$basearch/
#mirrorlist=https://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/centos/RPM-GPG-KEY-CentOS-7
#additional packages that extend functionality of existing packages
[centosplus]
name=CentOS-$releasever - Plus - repo.huaweicloud.com
baseurl=https://repo.huaweicloud.com/centos/$releasever/centosplus/$basearch/
#mirrorlist=https://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=centosplus
gpgcheck=1
enabled=0
gpgkey=https://repo.huaweicloud.com/centos/RPM-GPG-KEY-CentOS-7

52
conf/CentOS-8-reg.repo Normal file
View File

@ -0,0 +1,52 @@
# CentOS-Base.repo
#
# The mirror system uses the connecting IP address of the client and the
# update status of each mirror to pick mirrors that are updated to and
# geographically close to the client. You should use this for CentOS updates
# unless you are manually picking other mirrors.
#
# If the mirrorlist= does not work for you, as a fall back you can try the
# remarked out baseurl= line instead.
#
#
[BaseOS]
name=CentOS-$releasever - Base - repo.huaweicloud.com
baseurl=https://repo.huaweicloud.com/centos-vault/8.5.2111/BaseOS/$basearch/os/
#mirrorlist=https://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=BaseOS&infra=$infra
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/centos/RPM-GPG-KEY-CentOS-Official
#released updates
[AppStream]
name=CentOS-$releasever - AppStream - repo.huaweicloud.com
baseurl=https://repo.huaweicloud.com/centos-vault/8.5.2111/AppStream/$basearch/os/
#mirrorlist=https://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=AppStream&infra=$infra
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/centos/RPM-GPG-KEY-CentOS-Official
[PowerTools]
name=CentOS-$releasever - PowerTools - repo.huaweicloud.com
baseurl=https://repo.huaweicloud.com/centos-vault/8.5.2111/PowerTools/$basearch/os/
#mirrorlist=https://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=PowerTools&infra=$infra
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/centos/RPM-GPG-KEY-CentOS-Official
#additional packages that may be useful
[extras]
name=CentOS-$releasever - Extras - repo.huaweicloud.com
baseurl=https://repo.huaweicloud.com/centos-vault/8.5.2111/extras/$basearch/os/
#mirrorlist=https://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/centos/RPM-GPG-KEY-CentOS-Official
#additional packages that extend functionality of existing packages
[centosplus]
name=CentOS-$releasever - Plus - repo.huaweicloud.com
baseurl=https://repo.huaweicloud.com/centos-vault/8.5.2111/centosplus/$basearch/os/
#mirrorlist=https://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=centosplus
gpgcheck=1
enabled=0
gpgkey=https://repo.huaweicloud.com/centos/RPM-GPG-KEY-CentOS-Official

74
conf/local_settings.py Normal file
View File

@ -0,0 +1,74 @@
# ##########################################################################
# 字符串前面的格式编码不要去掉了,主要是为了解决特殊字符被转义的问题。 #
# ##########################################################################
# ########## AD配置修改为自己的
# AD主机可以是IP或主机域名例如可以是: abc.com或172.16.122.1
LDAP_HOST = r'修改成自己的'
# AD域控的DOMAIN例如比如你的域名是abc.com那么这里的LDAP_DOMAIN就是abc
# NTLM认证必须是domain\username
LDAP_DOMAIN = r'修改成自己的'
# 用于登录AD做用户信息处理的账号需要有修改用户账号密码或信息的权限。
# AD账号例如pwdadmin
LDAP_LOGIN_USER = r'修改成自己的'
# 密码
LDAP_LOGIN_USER_PWD = r'修改为自己的'
# BASE DN账号的查找DN路径例如'DC=abc,DC=com'可以指定到OU之下例如'OU=RD,DC=abc,DC=com'。
# BASE_DN限制得越细搜索用户的目录也就越小一般情况下可以通过SEARCH_FILTER来过滤
BASE_DN = r'修改成自己的'
# ldap的search_filter如果需要修改请保持用户账号部分为 点位符{0} (代码中通过占位符引入账号)
# 例如AD的用户账号属性是sAMAccountName那么匹配的账号请配置成sAMAccountName={0}
# LDAP中用户账号属性可能是uuid那么匹配的账号请配置成uuid={0}
# 如果想限制用户在哪个组的才能使用,可以写成这样:
# r'(&(objectClass=user)(memberof=CN=mis,OU=Groups,OU=OnTheJob,DC=abc,DC=com)(sAMAccountName={0}))', memberof 是需要匹配的组
# 默认配置是AD环境的查询语句可以自行使用Apache Directory Studio测试后再配置
SEARCH_FILTER = r'(&(objectclass=user)(sAMAccountName={0}))'
# 是否启用SSL,
# 注意AD中必须使用SSL才能修改密码这里被坑了N久...,自行部署下AD的证书服务并颁发CA证书重启服务器生效。具体教程百度一下有很多。
# 如果使用Openldap这里根据实际情况调整
LDAP_USE_SSL = True
# 连接的端口如果启用SSL默认是636否则就是389
LDAP_CONN_PORT = 636
# 验证的类型
# 钉钉 / 企业微信,自行修改
# 值是DING / WEWORK
INTEGRATION_APP_TYPE = 'WEWORK'
# ########## 钉钉 《如果不使用钉钉,可不用配置》##########
# 钉钉企业ID <CorpId>,修改为自己的
DING_CORP_ID = '修改为自己的'
# 钉钉企业内部开发内部H5微应用或小程序用于读取企业内部用户信息
DING_AGENT_ID = r'修改为自己的'
DING_APP_KEY = r'修改为自己的'
DING_APP_SECRET = r'修改为自己的'
# 移动应用接入 主要为了实现通过扫码拿到用户的unionid
DING_MO_APP_ID = r'修改为自己的'
DING_MO_APP_SECRET = r'修改为自己的'
# ####### 企业微信《如果不使用企业微信,可不用配置》 ##########
# 企业微信的企业ID
WEWORK_CORP_ID = r'修改为自己的'
# 应用的AgentId
WEWORK_AGENT_ID = r'修改为自己的'
# 应用的Secret
WEWORK_AGNET_SECRET = r'修改为自己的'
# 主页域名钉钉跳转等需要指定域名格式pwd.abc.com。
# 如果是自定义安装,请修改成自己的域名
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
# 平台显示的标题
TITLE = 'Self-Service'
# ####### Redis ##########
REDIS_LOCATION = r'127.0.0.1:6379'
REDIS_PASSWORD = r'PWD_SELF_REDIS_PASSWORD'

View File

@ -12,4 +12,5 @@ if __name__ == '__main__':
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)

View File

@ -0,0 +1,22 @@
import datetime
import sys
import traceback
import logging
from django_redis import get_redis_connection
from utils.storage.kvstorage import KvStorage
logger = logging.getLogger(__name__)
try:
redis_conn = get_redis_connection()
cache_storage = KvStorage(redis_conn)
cache_storage.set('test_redis_connection', str(datetime.datetime))
cache_storage.get('test_redis_connection')
cache_storage.delete('test_redis_connection')
logger.info("Redis连接成功set/get/delete测试通过...")
except Exception as e:
cache_storage = None
logger.error("Redis无法连接请排查Redis配置...")
logger.error("{}".format(traceback.format_exc()))
sys.exit(1)

View File

@ -1,39 +0,0 @@
# AD配置修改为自己的
# AD主机可以是IP或主机域名例如可以是:abc.com或172.16.122.1
AD_HOST = '修改为自己的'
# 用于登录AD做用户信息验证的账号 需要有修改用户账号密码的权限。
# 账号格式使用DOMAIN\USERNAME例如abc\pwdadmin
AD_LOGIN_USER = '修改为自己的'
# 密码
AD_LOGIN_USER_PWD = '修改为自己的'
# BASE DN账号的查找DN路径例如'DC=abc,DC=com'可以指定到OU之下例如'OU=RD,DC=abc,DC=com'。
BASE_DN = '修改为自己的'
# 钉钉配置
# 钉钉接口地址,不可修改
DING_URL = "https://oapi.dingtalk.com/sns"
# 钉钉企业ID修改为自己的
DING_CORP_ID = '修改为自己的'
# 钉钉E应用修改为自己的
DING_AGENT_ID = '修改为自己的'
DING_APP_KEY = '修改为自己的'
DING_APP_SECRET = '修改为自己的'
# 钉钉移动应用接入,修改为自己的
DING_SELF_APP_ID = '修改为自己的'
DING_SELF_APP_SECRET = '修改为自己的'
# Crypty key 通过generate_key生成可不用修改
CRYPTO_KEY = b'dp8U9y7NAhCD3MoNwPzPBhBtTZ1uI_WWSdpNs6wUDgs='
# COOKIE 超时单位是秒,可不用修改
TMPID_COOKIE_AGE = 300
# 主页域名index.html中的钉钉跳转等需要指定域名。
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'

View File

@ -1,95 +1,74 @@
"""
Django settings for pwdselfservice project.
Generated by 'django-admin startproject' using Django 2.1.8.
For more information on this file, see
https://docs.djangoproject.com/en/2.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.1/ref/settings/
"""
import logging.config
import os
from django.utils.log import DEFAULT_LOGGING
APP_ENV = os.getenv('APP_ENV')
if APP_ENV == 'dev':
DEBUG = True
from conf.local_settings_dev import REDIS_LOCATION, REDIS_PASSWORD
else:
DEBUG = False
from conf.local_settings import REDIS_LOCATION, REDIS_PASSWORD
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'nxnm3#&2tat_c2i6%$y74a)t$(3irh^gpwaleoja1kdv30fmcm'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = ['*']
# 创建日志的路径
LOG_PATH = os.path.join(BASE_DIR, 'log')
# 如果地址不存在则会自动创建log文件夹
if not os.path.isdir(LOG_PATH):
os.mkdir(LOG_PATH)
LOGGING = {
# Disable Django's logging setup
LOGGING_CONFIG = None
LOGLEVEL = os.environ.get('LOGLEVEL', 'info').upper()
logging.config.dictConfig({
'version': 1,
'disable_existing_loggers': False,#此选项开启表示禁用部分日志不建议设置为True
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '%(asctime)s %(levelname)s %(pathname)s %(module)s.%(funcName)s %(lineno)d: %(message)s'
#日志格式
},
'simple': {
'format': '%(asctime)s %(levelname)s %(pathname)s %(module)s.%(funcName)s %(lineno)d: %(message)s'
},
},
'filters': {
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',#过滤器只有当setting的DEBUG = True时生效
'default': {
# exact format is not important, this is the minimum information
"format": "%(asctime)s %(module)s %(levelname)s -%(thread)d- %(message)s"
},
'django.server': DEFAULT_LOGGING['formatters']['django.server'],
},
'handlers': {
# console logs to stderr
'console': {
'level': 'DEBUG',
'filters': ['require_debug_true'],
"level": "ERROR",
'class': 'logging.StreamHandler',
'formatter': 'verbose'
'formatter': 'default',
},
'file': {#重点配置部分
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': '%s/log.log' % LOG_PATH,#日志保存文件
'formatter': 'verbose'#日志格式,与上边的设置对应选择
}
'file': {
'level': "DEBUG",
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(LOG_PATH, "all.log"),
'formatter': 'default',
'maxBytes': 1024 * 1024 * 50,
'backupCount': 24,
'encoding': 'utf-8',
},
'django.server': DEFAULT_LOGGING['handlers']['django.server'],
},
'loggers': {
'django': {#日志记录器
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True,
}
# default for all undefined Python modules
'': {
'level': LOGLEVEL,
'handlers': ['console', 'file'],
},
# Default runserver request logging
'django.server': DEFAULT_LOGGING['loggers']['django.server'],
},
}
})
# SESSION
# 只有在settings.SESSION_SAVE_EVERY_REQUEST 为True时才有效
SESSION_SAVE_EVERY_REQUEST = True
# 过期时间分钟
SESSION_COOKIE_AGE = 300
# False 会话cookie可以在用户浏览器中保持有效期。True关闭浏览器则Cookie失效。
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
# 建议配置,阻止 javascript 对会话数据的访问,提高安全性。
# SESSION_COOKIE_HTTPONLY= True
# Application definition
INSTALLED_APPS = [
# 'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
@ -97,12 +76,12 @@ INSTALLED_APPS = [
'resetpwd',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
@ -119,7 +98,6 @@ TEMPLATES = [
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
@ -128,20 +106,22 @@ TEMPLATES = [
WSGI_APPLICATION = 'pwdselfservice.wsgi.application'
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://{}/1".format(REDIS_LOCATION),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": REDIS_PASSWORD,
"COMPRESSOR": "django_redis.compressors.zlib.ZlibCompressor",
"IGNORE_EXCEPTIONS": True,
}
}
}
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
# }
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
# 514 66050是AD中账号被禁用的特定代码这个可以在微软官网查到。
# 可能不是太准确,如果使用者能确定还有其它状态码,可以自行在此处添加
AD_ACCOUNT_DISABLE_CODE = [514, 66050]
AUTH_PASSWORD_VALIDATORS = [
{
@ -158,10 +138,6 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
@ -170,17 +146,11 @@ USE_I18N = True
USE_L10N = True
USE_TZ = False
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
USE_TZ = True
STATIC_URL = '/static/'
STATIC_ROOT = 'static'
# STATIC_ROOT = 'static'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]

View File

@ -4,9 +4,9 @@ import resetpwd.views
urlpatterns = {
path("favicon.ico", RedirectView.as_view(url='static/img/favicon.ico')),
path('', resetpwd.views.resetpwd_index, name='index'),
path('resetcheck', resetpwd.views.resetpwd_check_userinfo, name='resetcheck'),
path('resetpwd', resetpwd.views.resetpwd_reset, name='resetpwd'),
path('resetunlock', resetpwd.views.resetpwd_unlock, name='resetunlock'),
path('resetmsg', resetpwd.views.reset_msg, name='resetmsg'),
path('', resetpwd.views.index, name='index'),
path('auth', resetpwd.views.auth, name='auth'),
path('resetPassword', resetpwd.views.reset_password, name='resetPassword'),
path('unlockAccount', resetpwd.views.unlock_account, name='unlockAccount'),
path('messages', resetpwd.views.messages, name='messages'),
}

View File

@ -1,12 +1,3 @@
"""
WSGI config for pwdselfservice project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application

342
readme.md
View File

@ -1,148 +1,151 @@
# 初学Django时碰到的一个需求因为公司中很多员工在修改密码之后有一些关联的客户端或网页中的旧密码没有更新导致密码在尝试多次之后账号被锁为了减少这种让人头疼的重置解锁密码的操蛋工作自己做了一个自助修改小平台。
## 水平有限,代码写得不好,但是能用,有需要的可以直接拿去用。
### 初学Django时碰到的一个需求因为公司中很多员工在修改密码之后有一些关联的客户端或网页中的旧密码没有更新导致密码在尝试多次之后账号被锁为了减少这种让人头疼的重置解锁密码的操蛋工作自己做了一个自助修改小平台。
### 代码结构不咋样,但是能用,有需要的可以直接拿去用。
#### 场景说明:
因为本公司AD是早期已经在用用户的个人信息不是十分全面例如:用户手机号。
钉钉是后来才开始使用,钉钉默认是使用手机号登录。
这样就造成如果通过手机号来进行钉钉与AD之间的验证视乎行不通。
在这里我就使用了通过扫码后提取钉钉账号的邮箱信息再将邮箱在AD中进行比对来验证用户(邮箱)是否同时在企业的钉钉和企业AD中同时存在并账号状态是激活的。
此处的配置可按自己的实际情况修改。
整个验证逻辑写在resetpwd/views.py
钉钉是后来才开始使用,钉钉默认是使用手机号登录。
用户自行重置密码时如果通过手机号来进行钉钉与AD之间的验证就行不通了。
## 截图
### 逻辑:
>已经与之前不同,现在改成内嵌小应用,不再支持直接通过网页开始,之前的扫码方式有点多此一举的味道。
## <u>_**所能接受的账号规则**_ </u>
无论是钉钉、微信均是通过提取用户邮箱的前缀部分来作为关联AD的账号所以目前的识别逻辑就需要保证邮箱的前缀和AD的登录账号是一致的。
如果您的场景不是这样,请按自己的需求修改源代码适配。
### 提示:
```
AD必须使用SSL才能修改密码这里被坑了N久...
自行部署下AD的证书服务并颁发CA证书重启服务器生效。
具体教程百度一下,有很多。
```
### 本次升级、修复,请使用最新版:
+ 升级Python版本为3.8
+ 升级Django到3.2
+ 修复用户名中使用\被转义的问题
+ 重写了dingding模块因为dingding开发者平台接口鉴权的一些变动之前的一些接口不能再使用本次重写。
+ 重写了ad模块修改账号的一些判断逻辑。
+ 重写了用户账号的格式兼容现在用户账号可以兼容username、DOMAIN\username、username@abc.com这三种格式。
+ 优化了整体的代码逻辑,去掉一些冗余重复的代码。
### 2022/12/16 -- 更新:
+ 修改钉钉、企业微信直接通过企业内部免密登录授权或验证的方式实现用户信息的获取直接通过软件内部工作平台打开废弃扫码方式由于API接口的权限问题一些关键数据已经不再支持通过扫码获取
### 2023/01/15 -- 更新:
+ 兼容PC与移动端的显示使用Layui
+ 修复一些BUG
+ 移除auto-install.sh中的redis部署步骤
+ 抽出应用中的授权验证跳转的代码单独做成一个auth页面可实现选择首页是进入修改密码还是自动跳转重置页面。
+ 调整部分文件说明
+ 添加日志装饰器记录请求日志
+ 优化ad_ops中异常的处理
### 2023/02/10 -- 更新:
+ 修复一些BUG
+ auto-install.sh 重写支持Ubuntu\Centos
+ 添加Redis缓存之前使用内存缓存有问题
**如果想在点击应用之后自动跳转到重置页面请将回调接口配置成YOURDOMAIN.com/auth**
![截图1](screenshot/Snipaste_2019-07-15_20-05-49.jpg)
![截图2](screenshot/Snipaste_2019-07-15_20-06-14.jpg)
## 线上环境需要的基础环境:
+ Python 3.6.x
* Nginx
* Uwsgi
+ Python 3.8.16 (可自行下载源码包放到项目目录下,使用一键安装)
+ Nginx
+ Uwsgi
+ Redis
### 界面效果
<img alt="截图10" width="500" src="screenshot/QQ截图20230116152954.png">
<img alt="截图10" width="500" src="screenshot/212473880-4a59c535-85bb-42d2-a99a-899265c83136.png">
<img alt="截图10" width="500" src="screenshot/212474222-e1c13e1b-bb6f-4523-b040-24a65055d681.png">
### 移动端
<img alt="截图10" width="500" src="screenshot/212474177-dd68b0c9-81cc-4eb0-9196-e760784e3f69.jpg">
<img alt="截图10" width="500" src="screenshot/212474293-0cd60898-22c3-4258-ac4c-dfee52a6cf1e.png">
## 钉钉必要条件:
#### E应用配置
* 在钉钉工作台中通过“自建应用”创建应用选择“企业内部自主开发”在应用首页中获取应用的AgentId、AppKey、AppSecret。
* 应用需要权限:身份验证、消息通知、通讯录只读权限、手机号码信息、邮箱等个人信息、智能人事,范围是全部员工或自行选择
#### 创建企业内部应用
* 在钉钉工作台中通过“自建应用”创建应用,选择“企业内部开发”创建H5微应用,在应用首页中获取应用的AgentId、AppKey、AppSecret。
* 应用需要权限:通讯录只读权限、邮箱等个人信息,范围是全部员工或自行选择
* 应用安全域名和IP一定要配置否则无法返回接口数据。
#### 移动接入应用:
* 登录中开启扫码登录配置回调域名“https://pwd.abc.com/resetcheck”
其中pwd.abc.com请按自己实际域名来并记录相关的appId、appSecret。
> **如果想实现进入应用就自动授权跳转重置页面可将回调域名指定向pwd.abc.com/auth**
参考截图配置:
![截图3](screenshot/h5微应用.png)
![截图4](screenshot/创建H5微应用03.png)
![截图5](screenshot/创建H5微应用04.png)
![截图5](screenshot/创建H5微应用--版本管理与发布.png)
#### 移动接入应用--登录权限:
> 废弃,已经不再需要,如果之前有配置,可以删除!!
# 使用脚本自动快速部署只适合Centos其它发行版本的Linux请自行修改相关命令。
## 我添加了一个快速自动部署脚本,可快速自动部署完成当前项目上线。
把整个项目目录上传到新的服务器上
## 企业微信必要条件:
* 创建应用记录下企业的CorpId应用的ID和Secret。
> **如果想实现进入应用就自动授权跳转重置页面可将回调域名指定向pwd.abc.com/auth**
参考截图:
![截图7](screenshot/微扫码13.png)
![截图8](screenshot/微信小应用01-应用主机.png)
![截图9](screenshot/微信小应用01-应用主页-配置.png)
![截图10](screenshot/微信小应用01-网页授权及J-SDK配置.png)
![截图10](screenshot/微信小应用01-企业可信IP.png)
## 飞书必要条件:
* 暂时没时间,做不了,已经剔除了!
## 如果你觉得这个小工具对你有帮忙的话,可以请我喝杯咖啡~😁😁😁
<img alt="截图10" height="400" src="screenshot/143fce31873f4d7a4ecd7a7b8c6a24c.png" width="300"/>
<img alt="截图10" height="400" src="screenshot/微信图片_20221220140900.png" width="300"/>
## 使用脚本自动部署:
使用脚本自动快速部署只适合Centos其它发行版本的Linux请自行修改相关命令。
### 把整个项目目录上传到新的服务器上
#### 先修改配置文件,按自己实际的配置修改项目配置文件:
修改conf/local_settings.py中的参数按自己的实际参数修改
### 执行部署脚本
```shell
chmod +x auto-install.sh
./auto-install.sh
```
等待所以安装完成即可。
#### 按自己实际的配置修改项目配置参数:
修改pwdselfservice/local_settings.py中的参数按自己的实际参数修改
```` python
# AD配置
# AD主机可以是IP或主机域名例如可以是:abc.com或172.16.122.1
AD_HOST = '修改为自己的'
# 用于登录AD做用户信息验证的账号 需要有修改用户账号密码的权限。
# 账号格式使用DOMAIN\USERNAME例如abc\pwdadmin
AD_LOGIN_USER = '修改为自己的'
# 密码
AD_LOGIN_USER_PWD = '修改为自己的'
# BASE DN账号的查找DN路径例如'DC=abc,DC=com'可以指定到OU之下例如'OU=RD,DC=abc,DC=com'。
BASE_DN = '修改为自己的'
# 钉钉配置
# 钉钉接口地址,不可修改
DING_URL = "https://oapi.dingtalk.com/sns"
# 钉钉企业ID
DING_CORP_ID = '修改为自己的'
# 钉钉E应用
DING_AGENT_ID = '修改为自己的'
DING_APP_KEY = '修改为自己的'
DING_APP_SECRET = '修改为自己的'
# 钉钉移动应用接入
DING_SELF_APP_ID = '修改为自己的'
DING_SELF_APP_SECRET = '修改为自己的'
# Crypty key 通过generate_key生成可不用修改
CRYPTO_KEY = b'dp8U9y7NAhCD3MoNwPzPBhBtTZ1uI_WWSdpNs6wUDgs='
# COOKIE 超时单位是秒,可不用修改
TMPID_COOKIE_AGE = 300
# 主页域名index.html中的钉钉跳转等需要指定域名如果是脚本自动部署以下域名会自动替换。
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
````
等待所有安装完成。
#### 以上配置修改完成之后,则可以通过配置的域名直接访问。
# 手动部署
## 按自己实际的配置修改项目配置参数:
修改pwdselfservice/local_settings.py中的参数按自己的实际参数修改
```` python
# AD配置修改为自己的
# AD主机可以是IP或主机域名例如可以是:abc.com或172.16.122.1
AD_HOST = '修改为自己的'
# 用于登录AD做用户信息验证的账号 需要有修改用户账号密码的权限。
# 账号格式使用DOMAIN\USERNAME例如abc\pwdadmin
AD_LOGIN_USER = '修改为自己的'
# 密码
AD_LOGIN_USER_PWD = '修改为自己的'
# BASE DN账号的查找DN路径例如'DC=abc,DC=com'可以指定到OU之下例如'OU=RD,DC=abc,DC=com'。
BASE_DN = '修改为自己的'
# 钉钉配置
# 钉钉接口地址,不可修改
DING_URL = "https://oapi.dingtalk.com/sns"
# 钉钉企业ID修改为自己的
DING_CORP_ID = '修改为自己的'
# 钉钉E应用修改为自己的
DING_AGENT_ID = '修改为自己的'
DING_APP_KEY = '修改为自己的'
DING_APP_SECRET = '修改为自己的'
# 钉钉移动应用接入,修改为自己的
DING_SELF_APP_ID = '修改为自己的'
DING_SELF_APP_SECRET = '修改为自己的'
# Crypty key 通过generate_key生成可不用修改
CRYPTO_KEY = b'dp8U9y7NAhCD3MoNwPzPBhBtTZ1uI_WWSdpNs6wUDgs='
# COOKIE 超时单位是秒,可不用修改
TMPID_COOKIE_AGE = 300
# 主页域名index.html中的钉钉跳转等需要指定域名。
HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
````
### 自行安装完python3之后使用python3目录下的pip3进行安装依赖
### 我自行安装的Python路径为/usr/local/python3
# 手动部署:
#### 自行安装完python3之后使用python3目录下的pip3进行安装依赖
#### 我自行安装的Python路径为/usr/local/python3
项目目录下的requestment文件里记录了所依赖的相关python模块安装方法
* /usr/local/python3/bin/pip3 install -r requestment
>/usr/local/python3/bin/pip3 install -r requestment
等待所有模块安装完成之后进行下一步。
### 按自己实际的配置修改项目配置参数:
修改conf/local_settings.py中的参数按自己的实际参数修改
```
安装完依赖后,直接执行
/usr/local/python3/bin/python3 manager.py runserver x.x.x.x:8000
即可临时访问项目线上不适用这种方法线上环境请使用uwsgi。
@ -150,98 +153,22 @@ HOME_URL = 'PWD_SELF_SERVICE_DOMAIN'
## 修改uwsig.ini配置:
IP和路径按自己实际路径修改
````ini
[uwsgi]
http-socket = PWD_SELF_SERVICE_IP:PWD_SELF_SERVICE_PORT
chdir = PWD_SELF_SERVICE_HOME
module = pwdselfservice.wsgi:application
master = true
processes = 4
threads = 4
max-requests = 2000
chmod-socket = 755
vacuum = true
#设置缓冲
post-buffering = 4096
#设置静态文件
static-map = /static=PWD_SELF_SERVICE_HOME/static
#设置日志目录
daemonize = PWD_SELF_SERVICE_HOME/log/uwsgi.log
````
```
## 通过uwsgiserver启动
其中PWD_SELF_SERVICE_HOME是你自己的服务器当前项目的目录请自行修改
将以下脚本修改完之后,复制到/etc/init.d/,给予执行权限。
uwsgiserver:
```shell
#!/bin/sh
INI="PWD_SELF_SERVICE_HOME/uwsgi.ini"
UWSGI="/usr/share/python-3.6.9/bin/uwsgi"
PSID=`ps aux | grep "uwsgi"| grep -v "grep" | wc -l`
if [ ! -n "$1" ]
then
content="Usages: sh uwsgiserver [start|stop|restart]"
echo -e "\033[31m $content \033[0m"
exit 0
fi
if [ $1 = start ]
then
if [ `eval $PSID` -gt 4 ]
then
content="uwsgi is running!"
echo -e "\033[32m $content \033[0m"
exit 0
else
$UWSGI $INI
content="Start uwsgi service [OK]"
echo -e "\033[32m $content \033[0m"
fi
elif [ $1 = stop ];then
if [ `eval $PSID` -gt 4 ];then
killall -9 uwsgi
fi
content="Stop uwsgi service [OK]"
echo -e "\033[32m $content \033[0m"
elif [ $1 = restart ];then
if [ `eval $PSID` -gt 4 ];then
killall -9 uwsgi
fi
$UWSGI --ini $INI
content="Restart uwsgi service [OK]"
echo -e "\033[32m $content \033[0m"
else
content="Usages: sh uwsgiserver [start|stop|restart]"
echo -e "\033[31m $content \033[0m"
fi
````
脚本内的路径按自己实际情况修改
## Nginx配置
请自行修改将脚本修改完之后
复制到/etc/init.d/,给予执行权限。
执行/etc/init.d/uwsigserver start 启动
```
## 自行部署Nginx然后添加Nginx配置
#### Nginx配置
Nginx Server配置
* proxy_pass的IP地址改成自己的服务器IP
* 配置可自己写一个vhost或直接加在nginx.conf中
```` nginx
``` nginx
server {
listen 80;
server_name pwd.abc.com;
@ -255,6 +182,5 @@ server {
}
access_log off;
}
````
- 执行Nginx reload操作重新加载配置
```
- 执行Nginx reload操作重新加载配置

View File

@ -1,7 +0,0 @@
Django==2.1.8
dingtalk-sdk>=1.2.2
pycrypto>=2.6
cryptography
ldap3
requests
uwsgi

9
requirement Normal file
View File

@ -0,0 +1,9 @@
Django==3.2
attrs==21.2.0
python-dateutil==2.8.1
dingtalk-sdk==1.3.8
cryptography==3.4.7
ldap3==2.9
django-redis==4.12.1
requests==2.28.1
uwsgi==2.0.21

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,6 +1,5 @@
from django.forms import fields as c_fields
from django import forms as c_forms
from django.core.exceptions import ValidationError
class CheckForm(c_forms.Form):
@ -17,7 +16,7 @@ class CheckForm(c_forms.Form):
)
old_password = c_fields.CharField(error_messages={'required': '确认密码不能为空'})
ensure_password = c_fields.CharField(error_messages={'required': '确认密码不能为空'})
user_email = c_fields.CharField(error_messages={'required': '邮箱不能为空', 'invalid': '邮箱格式错误'})
username = c_fields.CharField(error_messages={'required': '账号不能为空', 'invalid': '账号格式错误'})
def clean(self):
pwd0 = self.cleaned_data.get('old_password')

View File

@ -1,4 +0,0 @@
from django.db import models
from django import forms
from django.contrib import auth

115
resetpwd/utils.py Normal file
View File

@ -0,0 +1,115 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @FileName utils.py
# @Software:
# @Author: Leven Xiang
# @Mail: xiangle0109@outlook.com
# @Date 2021/5/20 8:47
from django.shortcuts import render
import logging
from ldap3.core.exceptions import LDAPException
from django.conf import settings
import os
from utils.tracecalls import decorator_logger
APP_ENV = os.getenv('APP_ENV')
if APP_ENV == 'dev':
from conf.local_settings_dev import *
else:
from conf.local_settings import *
logger = logging.getLogger(__name__)
@decorator_logger(logger, log_head='AccountOps', pretty=True, indent=2, verbose=1)
def code_2_user_detail(ops, home_url, code):
"""
临时授权码换取userinfo
"""
_, s, e = ops.get_user_detail(code=code, home_url=home_url)
return _, s, e
@decorator_logger(logger, log_head='AccountOps', pretty=True, indent=2, verbose=1)
def ops_account(ad_ops, request, msg_template, home_url, username, new_password):
"""
ad 账号操作判断账号状态重置密码或解锁账号
"""
try:
print("ops_account: {}".format(username))
_status, _account = ad_ops.ad_ensure_user_by_account(username=username)
if not _status:
context = {
'global_title': TITLE,
'msg': "账号[%s]在AD中不存在请确认当前钉钉扫码账号绑定的邮箱是否和您正在使用的邮箱一致或者该账号己被禁用\n猜测:您的账号或邮箱是否是带有数字或其它字母区分?" % username,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
_status, account_code = ad_ops.ad_get_user_status_by_account(username)
if _status and account_code in settings.AD_ACCOUNT_DISABLE_CODE:
context = {
'global_title': TITLE,
'msg': "此账号状态为己禁用请联系HR确认账号是否正确。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
elif not _status:
context = {
'global_title': TITLE,
'msg': "错误:{}".format(account_code),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
if new_password:
reset_status, result = ad_ops.ad_reset_user_pwd_by_account(username=username, new_password=new_password)
if reset_status:
# 重置密码并执行一次解锁,防止重置后账号还是锁定状态。
unlock_status, result = ad_ops.ad_unlock_user_by_account(username)
if unlock_status:
context = {
'global_title': TITLE,
'msg': "密码己修改成功,请妥善保管。你可以点击修改密码或直接关闭此页面!",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
else:
context = {
'global_title': TITLE,
'msg': "密码未修改/重置成功,错误信息:{}".format(result),
'button_click': "window.location.href='%s'" % '/auth',
'button_display': "重新认证授权"
}
return render(request, msg_template, context)
else:
unlock_status, result = ad_ops.ad_unlock_user_by_account(username)
if unlock_status:
context = {
'global_title': TITLE,
'msg': "账号己解锁成功。你可以点击返回主页或直接关闭此页面!",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
else:
context = {
'global_title': TITLE,
'msg': "账号未能解锁,错误信息:{}".format(result),
'button_click': "window.location.href='%s'" % '/auth',
'button_display': "重新认证授权"
}
return render(request, msg_template, context)
except LDAPException as l_e:
context = {
'global_title': TITLE,
'msg': "账号未能解锁,错误信息:{}".format(l_e),
'button_click': "window.location.href='%s'" % '/auth',
'button_display': "重新认证授权"
}
return render(request, msg_template, context)

View File

@ -1,144 +0,0 @@
from ldap3 import *
from pwdselfservice.local_settings import *
def __ad_connect():
"""
AD连接器
:return:
"""
username = str(AD_LOGIN_USER).lower()
server = Server(host=AD_HOST, use_ssl=True, port=636, get_info='ALL')
try:
conn = Connection(server, auto_bind=True, user=username, password=AD_LOGIN_USER_PWD, authentication='NTLM')
return conn
except Exception:
raise Exception('Server Error. Could not connect to Domain Controller')
def ad_ensure_user_by_sam(username):
"""
通过sAMAccountName查询某个用户是否在AD中
:param username: 除去@domain.com 的部分
:return: True or False
"""
conn = __ad_connect()
base_dn = BASE_DN
condition = '(&(objectclass=person)(mail=' + username + '))'
attributes = ['sAMAccountName']
result = conn.search(base_dn, condition, attributes=attributes)
conn.unbind()
return result
def ad_ensure_user_by_mail(user_mail_addr):
"""
通过mail查询某个用户是否在AD中
:param user_mail_addr:
:return: True or False
"""
conn = __ad_connect()
base_dn = BASE_DN
condition = '(&(objectclass=person)(mail=' + user_mail_addr + '))'
attributes = ['mail']
result = conn.search(base_dn, condition, attributes=attributes)
conn.unbind()
return result
def ad_get_user_displayname_by_mail(user_mail_addr):
"""
通过mail查询某个用户的显示名
:param user_mail_addr:
:return: user_displayname
"""
conn = __ad_connect()
conn.search(BASE_DN, '(&(objectclass=person)(mail=' + user_mail_addr + '))', attributes=[
'displayName'])
user_displayname = conn.entries[0]['displayName']
conn.unbind()
return user_displayname
def ad_get_user_dn_by_mail(user_mail_addr):
"""
通过mail查询某个用户的完整DN
:param user_mail_addr:
:return: DN
"""
conn = __ad_connect()
conn.search(BASE_DN,
'(&(objectclass=person)(mail=' + user_mail_addr + '))', attributes=['distinguishedName'])
user_dn = conn.entries[0]['distinguishedName']
conn.unbind()
return user_dn
def ad_get_user_status_by_mail(user_mail_addr):
"""
通过mail查询某个用户的账号状态
:param user_mail_addr:
:return: user_account_control code
"""
conn = __ad_connect()
conn.search(BASE_DN,
'(&(objectclass=person)(mail=' + user_mail_addr + '))', attributes=['userAccountControl'])
user_account_control = conn.entries[0]['userAccountControl']
conn.unbind()
return user_account_control
def ad_unlock_user_by_mail(user_mail_addr):
"""
通过mail解锁某个用户
:param user_mail_addr:
:return:
"""
conn = __ad_connect()
user_dn = ad_get_user_dn_by_mail(user_mail_addr)
result = conn.extend.microsoft.unlock_account(user="%s" % user_dn)
conn.unbind()
return result
def ad_reset_user_pwd_by_mail(user_mail_addr, new_password):
"""
通过mail重置某个用户的密码
:param user_mail_addr:
:return:
"""
conn = __ad_connect()
user_dn = ad_get_user_dn_by_mail(user_mail_addr)
result = conn.extend.microsoft.modify_password(user="%s" % user_dn, new_password="%s" % new_password)
conn.unbind()
return result
def ad_modify_user_pwd_by_mail(user_mail_addr, old_password, new_password):
"""
通过mail修改某个用户的密码
:param user_mail_addr:
:return:
"""
conn = __ad_connect()
user_dn = ad_get_user_dn_by_mail(user_mail_addr)
result = conn.extend.microsoft.modify_password(user="%s" % user_dn, new_password="%s" % new_password,
old_password="%s" % old_password)
conn.unbind()
return result
def ad_get_user_locked_status_by_mail(user_mail_addr):
"""
通过mail获取某个用户账号是否被锁定
:param user_mail_addr:
:return: 如果结果是1601-01-01说明账号未锁定返回0
"""
conn = __ad_connect()
conn.search(BASE_DN, '(&(objectclass=person)(mail=' + user_mail_addr + '))', attributes=['lockoutTime'])
locked_status = conn.entries[0]['lockoutTime']
conn.unbind()
if '1601-01-01' in str(locked_status):
return 0
else:
return locked_status

View File

@ -1,22 +0,0 @@
from cryptography.fernet import Fernet
class Crypto(object):
"""docstring for ClassName"""
def __init__(self, key):
self.factory = Fernet(key)
@staticmethod
def generate_key():
key = Fernet.generate_key()
print(key)
# 加密
def encrypt(self, string):
token = str(self.factory.encrypt(string.encode('utf-8')), 'utf-8')
return token
# 解密
def decrypt(self, token):
string = self.factory.decrypt(bytes(token.encode('utf-8'))).decode('utf-8')
return string

View File

@ -1,99 +0,0 @@
from dingtalk.client import *
import requests
from pwdselfservice.local_settings import *
def ding_get_access_token():
"""
获取钉钉access token
:return:
"""
resp = requests.get(
url=DING_URL + "/gettoken",
params=dict(appid=DING_SELF_APP_ID, appsecret=DING_SELF_APP_SECRET)
)
resp = resp.json()
if resp['access_token']:
return resp['access_token']
else:
return None
def ding_get_persistent_code(code, token):
"""
获取钉钉当前用户的unionid
:return:
"""
resp = requests.post(
url="%s/get_persistent_code?access_token=%s" % (DING_URL, token),
json=dict(tmp_auth_code=code),
)
resp = resp.json()
if resp['unionid']:
return resp['unionid']
else:
return None
def ding_client_connect():
"""
钉钉连接器
:return:
"""
client = AppKeyClient(corp_id=DING_CORP_ID, app_key=DING_APP_KEY, app_secret=DING_APP_SECRET)
return client
def ding_get_dept_user_list_detail(dept_id, offset, size):
"""
获取部门中的用户列表详细清单
:param code:
:return:
"""
client = ding_client_connect()
result = client.user.list(department_id=dept_id, offset=offset, size=size)
return result
def ding_get_userinfo_by_code(code):
"""
:param code: requestAuthCode接口中获取的CODE
:return:
"""
client = ding_client_connect()
resutl = client.user.getuserinfo(code)
return resutl
def ding_get_userid_by_unionid(unionid):
"""
:param unionid: 用户在当前钉钉开放平台账号范围内的唯一标识
:return:
"""
client = ding_client_connect()
resutl = client.user.get_userid_by_unionid(unionid)
if resutl['userid']:
return resutl['userid']
else:
return None
def ding_get_org_user_count():
"""
企业员工数量
only_active 是否包含未激活钉钉的人员数量
:return:
"""
client = ding_client_connect()
resutl = client.user.get_org_user_count('only_active')
return resutl
def ding_get_userinfo_detail(user_id):
"""
user_id 用户ID
:return:
"""
client = ding_client_connect()
resutl = client.user.get(user_id)
return resutl

View File

@ -1,34 +0,0 @@
from django.forms import fields as c_fields
from django import forms as c_forms
from django.core.exceptions import ValidationError
class CheckForm(c_forms.Form):
new_password = c_fields.RegexField(
'(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,30}',
# 密码必须同时包含大写、小写、数字和特殊字符其中三项且至少8位
strip=True,
min_length=8,
max_length=30,
error_messages={'required': '新密码不能为空.',
'invalid': '密码必须包含数字,字母、特殊字符',
'min_length': "密码长度不能小于8个字符",
'max_length': "密码长度不能大于30个字符"}
)
old_password = c_fields.CharField(error_messages={'required': '确认密码不能为空'})
ensure_password = c_fields.CharField(error_messages={'required': '确认密码不能为空'})
user_email = c_fields.CharField(error_messages={'required': '邮箱不能为空', 'invalid': '邮箱格式错误'})
def clean(self):
pwd0 = self.cleaned_data.get('old_password')
pwd1 = self.cleaned_data.get('new_password')
pwd2 = self.cleaned_data.get('ensure_password')
if pwd1 == pwd2:
pass
elif pwd0 == pwd1:
# 这里异常模块导入要放在函数里面,放到文件开头有时会报错,找不到
from django.core.exceptions import ValidationError
raise ValidationError('新旧密码不能一样')
else:
from django.core.exceptions import ValidationError
raise ValidationError('新密码和确认密码输入不一致')

View File

@ -1,30 +0,0 @@
from django.shortcuts import render, reverse, HttpResponsePermanentRedirect, redirect
from django.http import *
from django.contrib import messages
from dingtalk import *
from resetpwd.models import *
from .crypto import Crypto
from resetpwd.utils.ad import ad_get_user_locked_status_by_mail, ad_unlock_user_by_mail, ad_reset_user_pwd_by_mail, \
ad_get_user_status_by_mail, ad_ensure_user_by_mail, ad_modify_user_pwd_by_mail
from .dingding import ding_get_userinfo_detail, ding_get_userid_by_unionid, ding_get_userinfo_by_code, \
ding_get_persistent_code, ding_get_access_token
from pwdselfservice.local_settings import *
from resetpwd.form import *
class CustomPasswortValidator(object):
def __init__(self, min_length=1, max_length=30):
self.min_length = min_length
def validate(self, password):
special_characters = "[~\!@#\$%\^&\*\(\)_\+{}\":;'\[\]]"
if not any(char.isdigit() for char in password):
raise ValidationError(_('Password must contain at least %(min_length)d digit.') % {'min_length': self.min_length})
if not any(char.isalpha() for char in password):
raise ValidationError(_('Password must contain at least %(min_length)d letter.') % {'min_length': self.min_length})
if not any(char in special_characters for char in password):
raise ValidationError(_('Password must contain at least %(min_length)d special character.') % {'min_length': self.min_length})
def get_help_text(self):
return ""

View File

@ -1,415 +1,304 @@
from django.shortcuts import render
from django.http import *
from resetpwd.utils.crypto import Crypto
from resetpwd.utils.ad import ad_get_user_locked_status_by_mail, ad_unlock_user_by_mail, ad_reset_user_pwd_by_mail, \
ad_get_user_status_by_mail, ad_ensure_user_by_mail, ad_modify_user_pwd_by_mail
from resetpwd.utils.dingding import ding_get_userinfo_detail, ding_get_userid_by_unionid, \
ding_get_persistent_code, ding_get_access_token
from pwdselfservice.local_settings import *
from .form import CheckForm
import json
import logging
import os
import traceback
msg_template = 'msg.html'
logger = logging.getLogger('django')
from django.shortcuts import render
from utils.ad_ops import AdOps
import urllib.parse as url_encode
from utils.format_username import format2username, get_user_is_active, get_email_from_userinfo
from .form import CheckForm
from .utils import code_2_user_detail, ops_account
from utils.tracecalls import decorator_logger
from pwdselfservice import cache_storage
APP_ENV = os.getenv('APP_ENV')
if APP_ENV == 'dev':
from conf.local_settings_dev import INTEGRATION_APP_TYPE, DING_MO_APP_ID, WEWORK_CORP_ID, WEWORK_AGENT_ID, HOME_URL, \
DING_CORP_ID, TITLE
else:
from conf.local_settings import INTEGRATION_APP_TYPE, DING_MO_APP_ID, WEWORK_CORP_ID, WEWORK_AGENT_ID, HOME_URL, \
DING_CORP_ID, TITLE
msg_template = 'messages.html'
logger = logging.getLogger(__name__)
def resetpwd_index(request):
"""
用户修改密码
:param request:
:return:
"""
class PARAMS(object):
if INTEGRATION_APP_TYPE == 'DING':
corp_id = DING_CORP_ID
app_id = DING_MO_APP_ID
agent_id = None
AUTH_APP = '钉钉'
from utils.dingding_ops import DingDingOps
ops = DingDingOps()
elif INTEGRATION_APP_TYPE == 'WEWORK':
corp_id = None
app_id = WEWORK_CORP_ID
agent_id = WEWORK_AGENT_ID
AUTH_APP = '微信'
from utils.wework_ops import WeWorkOps
ops = WeWorkOps()
scan_params = PARAMS()
_ops = scan_params.ops
@decorator_logger(logger, log_head='Request', pretty=True, indent=2, verbose=1)
def auth(request):
home_url = '%s://%s' % (request.scheme, HOME_URL)
app_id = DING_SELF_APP_ID
corp_id = scan_params.corp_id
app_id = scan_params.app_id
agent_id = scan_params.agent_id
scan_app = scan_params.AUTH_APP
redirect_url = url_encode.quote(home_url + '/resetPassword')
app_type = INTEGRATION_APP_TYPE
global_title = TITLE
if request.method == 'GET':
return render(request, 'auth.html', locals())
else:
logger.error('[异常] 请求方法:%s,请求路径%s' % (request.method, request.path))
@decorator_logger(logger, log_head='Request', pretty=True, indent=2, verbose=1)
def index(request):
home_url = '%s://%s' % (request.scheme, HOME_URL)
scan_app = scan_params.AUTH_APP
global_title = TITLE
if request.method == 'GET':
return render(request, 'index.html', locals())
else:
logger.error('[异常] 请求方法:%s,请求路径:%s' % (request.method, request.path))
if request.method == 'POST':
# 对前端提交的数据进行二次验证,防止恶意提交简单密码或串改账号。
elif request.method == 'POST':
# 对前端提交的数据进行二次验证,防止恶意提交简单密码或改账号。
check_form = CheckForm(request.POST)
if check_form.is_valid():
form_obj = check_form.cleaned_data
user_email = form_obj.get("user_email")
username = form_obj.get("username")
old_password = form_obj.get("old_password")
new_password = form_obj.get("new_password")
else:
msg = check_form.as_p().errors
logger.error('[异常] 请求方法:%s,请求路径:%s,错误信息:%s' % (request.method, request.path, msg))
_msg = check_form
logger.error('[异常] 请求方法:%s,请求路径:%s,错误信息:%s' % (request.method, request.path, _msg))
context = {
'msg': msg,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
'global_title': TITLE,
'msg': _msg,
'button_click': "window.location.href='%s'" % '/auth',
'button_display': "重新认证授权"
}
return render(request, msg_template, context)
if user_email and old_password and new_password:
try:
# 判断账号是否被锁定
if ad_get_user_locked_status_by_mail(user_mail_addr=user_email) is not 0:
context = {
'msg': "此账号己被锁定,请先解锁账号。",
'button_click': "window.history.back()",
'button_display': "返回"
}
return render(request, msg_template, context)
# 514 66050是AD中账号被禁用的特定代码这个可以在微软官网查到。
if ad_get_user_status_by_mail(user_mail_addr=user_email) == 514 or ad_get_user_status_by_mail(
user_mail_addr=user_email) == 66050:
context = {
'msg': "此账号状态为己禁用请联系HR确认账号是否正确。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
try:
result = ad_modify_user_pwd_by_mail(user_mail_addr=user_email, old_password=old_password,
new_password=new_password)
if result:
context = {
'msg': "密码己修改成功,请妥善保管密码。你可直接关闭此页面!",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
else:
context = {
'msg': "密码未修改成功,请确认旧密码是否正确。",
'button_click': "window.history.back()",
'button_display': "返回"
}
return render(request, msg_template, context)
except IndexError:
context = {
'msg': "请确认邮箱账号[%s]是否正确未能在Active Directory中检索到相关信息。" % user_email,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except Exception as e:
context = {
'msg': "出现未预期的错误[%s],请与管理员联系~" % str(e),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except IndexError:
context = {
'msg': "请确认邮箱账号[%s]是否正确未能在Active Directory中检索到相关信息。" % user_email,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except Exception as e:
context = {
'msg': "出现未预期的错误[%s],请与管理员联系~" % str(e),
'button_click': "window.history.back()",
'button_display': "返回"
}
return render(request, msg_template, context)
else:
# 格式化用户名
_, username = format2username(username)
if _ is False:
context = {
'msg': "用户名、旧密码、新密码参数不正确,请重新确认后输入。",
'button_click': "window.history.back()",
'button_display': "返回"
'global_title': TITLE,
'msg': username,
'button_click': "window.location.href='%s'" % '/auth',
'button_display': "重新认证授权"
}
return render(request, msg_template, context)
# 检测账号状态
auth_status, auth_result = AdOps().ad_auth_user(username=username, password=old_password)
if not auth_status:
context = {
'global_title': TITLE,
'msg': str(auth_result),
'button_click': "window.location.href='%s'" % '/auth',
'button_display': "重新认证授权"
}
return render(request, msg_template, context)
return ops_account(AdOps(), request, msg_template, home_url, username, new_password)
else:
context = {
'msg': "请从主页进行修改密码操作或扫码验证用户信息。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
'global_title': TITLE,
'msg': "不被接受的认证信息,请重新尝试认证授权。",
'button_click': "window.location.href='%s'" % '/auth',
'button_display': "重新认证授权"
}
return render(request, msg_template, context)
def resetpwd_check_userinfo(request):
@decorator_logger(logger, log_head='Request', pretty=True, indent=2, verbose=1)
def reset_password(request):
"""
钉钉扫码回调数据对用户在AD中进行验证
扫码之后从钉钉中取出用户的unionid
钉钉扫码并验证信息通过之后在重置密码页面将用户账号进行绑定
:param request:
:return:
"""
home_url = '%s://%s' % (request.scheme, HOME_URL)
code = request.GET.get('code')
if code:
logger.info('[成功] 请求方法:%s,请求路径:%sCODE%s' % (request.method, request.path, code))
else:
logger.error('[异常] 请求方法:%s,请求路径:%s未能拿到CODE。' % (request.method, request.path))
try:
unionid = ding_get_persistent_code(code, ding_get_access_token())
# 判断 unionid 在本企业钉钉中是否存在
if not unionid:
logger.error('[异常] 请求方法:%s,请求路径:%s未能拿到unionid。' % (request.method, request.path))
context = {
'msg': '未能在钉钉企业通讯录中检索到相关信息,请确认当前登录钉钉的账号已在企业中注册!',
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
ding_user_info = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))
try:
# 钉钉中此账号是否可用
if ding_user_info['active']:
crypto = Crypto(CRYPTO_KEY)
# 对unionid进行加密因为unionid基本上固定不变的为了防止unionid泄露而导致重复使用进行加密后再传回。
unionid_cryto = crypto.encrypt(unionid)
# 配置cookie通过cookie把加密后的用户unionid传到重置密码页面并重定向到重置密码页面。
set_cookie = HttpResponseRedirect('resetpwd')
set_cookie.set_cookie('tmpid', unionid_cryto, expires=TMPID_COOKIE_AGE)
return set_cookie
else:
context = {
'msg': '邮箱是[%s]的用户在钉钉中未激活或可能己离职' % ding_user_info['email'],
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except IndexError:
context = {
'msg': "用户不存在或己离职",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except Exception as e:
logger.error('[异常] %s' % str(e))
except KeyError:
context = {
'msg': "错误钉钉临时Code己失效请从主页重新扫码。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
logger.error('[异常] %s' % str(KeyError))
return render(request, msg_template, context)
except Exception as e:
context = {
'msg': "错误[%s],请与管理员联系." % str(e),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
logger.error('[异常] %s' % str(e))
return render(request, msg_template, context)
def resetpwd_reset(request):
"""
钉钉扫码并验证信息之后在重置密码页面将用户邮箱进行绑定
:param request:
:return:
"""
global unionid_crypto
home_url = '%s://%s' % (request.scheme, HOME_URL)
# 从cookie中提取unionid并解密然后对当前unionid的用户进行重置密码
if request.method == 'GET':
try:
unionid_crypto = request.COOKIES.get('tmpid')
except Exception as e:
logger.error('[异常] %s' % str(e))
if not unionid_crypto:
logger.error('[异常] 请求方法:%s,请求路径:%s未能拿到CODE或CODE己超时。' % (request.method, request.path))
code = request.GET.get('code')
username = request.GET.get('username')
# 如果从GET路径中提取到username、code并且在缓存中存在username对应的code值说明已经认证过
if username and code and cache_storage.get(username) == code:
context = {
'msg': "会话己超时,请重新扫码验证用户信息。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
'global_title': TITLE,
'username': username,
'code': code,
}
return render(request, msg_template, context)
# 解密
crypto = Crypto(CRYPTO_KEY)
unionid = crypto.decrypt(unionid_crypto)
# 通过unionid在钉钉中拿到用户的邮箱
user_email = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))['email']
# 如果邮箱在钉钉中能提取,则提交到前端绑定
if user_email:
context = {
'user_email': user_email,
}
return render(request, 'resetpwd.html', context)
# 否则就是钉钉中此用户未配置邮箱,返回相关提示
return render(request, 'reset_password.html', context)
# 否则就是第一次认证用code换取用户信息
else:
context = {
'msg': "%s 您好企业钉钉中未能找到您账号的邮箱配置请联系HR完善信息。" % ding_get_userinfo_detail(ding_get_userid_by_unionid(
unionid))['name'],
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
if not code:
context = {
'global_title': TITLE,
'msg': "临时授权码己失效,请尝试重新认证授权...",
'button_click': "window.location.href='%s'" % '/auth',
'button_display': "重新认证授权"
}
return render(request, msg_template, context)
try:
_status, user_id, user_info = code_2_user_detail(_ops, home_url, code)
if not _status:
return render(request, msg_template, user_id)
# 账号在企业微信或钉钉中是否是激活的
_, res = get_user_is_active(user_info)
if not _:
context = {
'global_title': TITLE,
'msg': '当前扫码的用户未激活或可能己离职,用户信息如下:%s' % user_info,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
# 通过user_info拿到用户邮箱并格式化为username
_, email = get_email_from_userinfo(user_info)
if not _:
context = {
'global_title': TITLE,
'msg': email,
'button_click': "window.location.href='%s'" % '/auth',
'button_display': "重新认证授权"
}
return render(request, msg_template, context)
_, username = format2username(email)
if _ is False:
context = {
'global_title': TITLE,
'msg': username,
'button_click': "window.location.href='%s'" % '/auth',
'button_display': "重新认证授权"
}
return render(request, msg_template, context)
if username:
cache_storage.set(username, code, ttl=300)
context = {
'global_title': TITLE,
'username': username,
'code': code,
}
return render(request, 'reset_password.html', context)
else:
context = {
'global_title': TITLE,
'msg': "{},您好,企业{}中未能找到您账号的邮箱配置请联系HR完善信息。".format(
user_info.get('name'), scan_params.AUTH_APP),
'button_click': "window.location.href='%s'" % '/auth',
'button_display': "重新认证授权"
}
return render(request, msg_template, context)
except Exception as callback_e:
context = {
'global_title': TITLE,
'msg': "错误[%s],请与管理员联系." % str(callback_e),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
logger.error('[异常] %s' % str(callback_e))
return render(request, msg_template, context)
# 重置密码页面,输入新密码后点击提交
elif request.method == 'POST':
new_password = request.POST.get('new_password').strip()
unionid_crypto = request.COOKIES.get('tmpid')
# 对cookie中的unionid进行超时验证如果页面超时就不再做处理。
if not unionid_crypto:
context = {
'msg': "会话己超时,请重新扫码验证用户信息。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
crypto = Crypto(CRYPTO_KEY)
unionid = crypto.decrypt(unionid_crypto)
user_email = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))['email']
if ad_ensure_user_by_mail(user_mail_addr=user_email) is False:
context = {
'msg': "账号[%s]在AD中不存在请确认当前钉钉扫码账号绑定的邮箱是否和您正在使用的邮箱一致或者该邮箱账号己被禁用\n猜测:您的邮箱是否是带有数字或其它字母区分?" % user_email,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
if ad_get_user_status_by_mail(user_mail_addr=user_email) == 514 or ad_get_user_status_by_mail(
user_mail_addr=user_email) == 66050:
context = {
'msg': "此账号状态为己禁用请联系HR确认账号是否正确。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
try:
result = ad_reset_user_pwd_by_mail(user_mail_addr=user_email, new_password=new_password)
if result:
# 重置密码并执行一次解锁,防止重置后账号还是锁定状态。
ad_unlock_user_by_mail(user_email)
username = request.POST.get('username')
code = request.POST.get('code')
if username and code and cache_storage.get(username) == code:
_new_password = request.POST.get('new_password').strip()
try:
return ops_account(ad_ops=AdOps(), request=request, msg_template=msg_template, home_url=home_url,
username=username, new_password=_new_password)
except Exception as reset_e:
context = {
'msg': "密码己重置成功,请妥善保管。你可以点击返回主页或直接关闭此页面!",
'global_title': TITLE,
'msg': "错误[%s],请与管理员联系." % str(reset_e),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
logger.error('[异常] %s' % str(reset_e))
return render(request, msg_template, context)
else:
context = {
'msg': "密码未重置成功确认密码是否满足AD的复杂性要求。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except IndexError:
else:
context = {
'msg': "请确认邮箱账号[%s]是否正确未能在AD中检索到相关信息。" % user_email,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
'global_title': TITLE,
'msg': "认证已经失效,可尝试从重新认证授权。",
'button_click': "window.location.href='%s'" % '/auth',
'button_display': "重新认证授权"
}
return render(request, msg_template, context)
except Exception as e:
context = {
'msg': "出现未预期的错误[%s],请与管理员联系~" % str(e),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
else:
context = {
'msg': "请从主页开始进行操作。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
def resetpwd_unlock(request):
@decorator_logger(logger, log_head='Request', pretty=True, indent=2, verbose=1)
def unlock_account(request):
"""
解锁账号
:param request:
:return:
"""
home_url = '%s://%s' % (request.scheme, HOME_URL)
if request.method == 'GET':
unionid_crypto = request.COOKIES.get('tmpid')
if not unionid_crypto:
code = request.GET.get('code')
username = request.GET.get('username')
if username and code and cache_storage.get(username) == code:
context = {
'msg': "会话己超时,请重新扫码验证用户信息。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
'global_title': TITLE,
'username': username,
'code': code,
}
return render(request, msg_template, context)
crypto = Crypto(CRYPTO_KEY)
unionid = crypto.decrypt(unionid_crypto)
user_email = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))['email']
context = {
'user_email': user_email,
}
return render(request, 'resetpwd.html', context)
elif request.method == 'POST':
unionid_crypto = request.COOKIES.get('tmpid')
if not unionid_crypto:
context = {
'msg': "会话己超时,请重新扫码验证用户信息。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
crypto = Crypto(CRYPTO_KEY)
unionid = crypto.decrypt(unionid_crypto)
user_email = ding_get_userinfo_detail(ding_get_userid_by_unionid(unionid))['email']
if ad_ensure_user_by_mail(user_mail_addr=user_email) is False:
context = {
'msg': "账号[%s]在AD中未能正确检索到请确认当前钉钉扫码账号绑定的邮箱是否和您正在使用的邮箱一致或者该邮箱账号己被禁用\n猜测:您的邮箱是否是带有数字或其它字母区分?" %
user_email,
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
return render(request, 'unlock.html', context)
else:
context = {
'global_title': TITLE,
'msg': "{},您好,当前会话可能已经过期,请再试一次吧。".format(username),
'button_click': "window.location.href='%s'" % '/auth',
'button_display': "重新认证授权"
}
return render(request, msg_template, context)
if request.method == 'POST':
username = request.POST.get('username')
code = request.POST.get('code')
if username and code and cache_storage.get(username) == code:
try:
result = ad_unlock_user_by_mail(user_email)
if result:
context = {
'msg': "账号己解锁成功。你可以点击返回主页或直接关闭此页面!",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
else:
context = {
'msg': "账号未能解锁请联系管理员确认该账号在AD的是否己禁用。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
except IndexError:
return ops_account(AdOps(), request, msg_template, home_url, username, None)
except Exception as reset_e:
context = {
'msg': "请确认邮箱账号[%s]是否正确未能在AD中检索到相关信息。" % user_email,
'global_title': TITLE,
'msg': "错误[%s],请与管理员联系." % str(reset_e),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
logger.error('{}' .format(traceback.format_exc()))
return render(request, msg_template, context)
except Exception as e:
context = {
'msg': "出现未预期的错误[%s],请与管理员联系~" % str(e),
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
else:
context = {
'msg': "请从主页开始进行操作。",
'button_click': "window.location.href='%s'" % home_url,
'button_display': "返回主页"
}
return render(request, msg_template, context)
else:
context = {
'global_title': TITLE,
'msg': "认证已经失效,请尝试从重新进行认证授权。",
'button_click': "window.location.href='%s'" % '/auth',
'button_display': "重新认证授权"
}
return render(request, msg_template, context)
def reset_msg(request):
msg = request.GET.get('msg')
@decorator_logger(logger, log_head='Request', pretty=True, indent=2, verbose=1)
def messages(request):
_msg = request.GET.get('msg')
button_click = request.GET.get('button_click')
button_display = request.GET.get('button_display')
context = {
'msg': msg,
'global_title': TITLE,
'msg': _msg,
'button_click': button_click,
'button_display': button_display
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

BIN
screenshot/h5微应用.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

BIN
screenshot/微扫码13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
screenshot/微扫码14.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
screenshot/微扫码15.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

BIN
screenshot/微扫码16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
screenshot/扫码成功.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

View File

@ -1,135 +0,0 @@
.sk-fading-circle {
margin: 100px auto;
width: 40px;
height: 40px;
position: relative;
}
.sk-fading-circle .sk-circle {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
.sk-fading-circle .sk-circle:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #333;
border-radius: 100%;
-webkit-animation: sk-circleFadeDelay 1.2s infinite ease-in-out both;
animation: sk-circleFadeDelay 1.2s infinite ease-in-out both;
}
.sk-fading-circle .sk-circle2 {
-webkit-transform: rotate(30deg);
-ms-transform: rotate(30deg);
transform: rotate(30deg);
}
.sk-fading-circle .sk-circle3 {
-webkit-transform: rotate(60deg);
-ms-transform: rotate(60deg);
transform: rotate(60deg);
}
.sk-fading-circle .sk-circle4 {
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.sk-fading-circle .sk-circle5 {
-webkit-transform: rotate(120deg);
-ms-transform: rotate(120deg);
transform: rotate(120deg);
}
.sk-fading-circle .sk-circle6 {
-webkit-transform: rotate(150deg);
-ms-transform: rotate(150deg);
transform: rotate(150deg);
}
.sk-fading-circle .sk-circle7 {
-webkit-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg);
}
.sk-fading-circle .sk-circle8 {
-webkit-transform: rotate(210deg);
-ms-transform: rotate(210deg);
transform: rotate(210deg);
}
.sk-fading-circle .sk-circle9 {
-webkit-transform: rotate(240deg);
-ms-transform: rotate(240deg);
transform: rotate(240deg);
}
.sk-fading-circle .sk-circle10 {
-webkit-transform: rotate(270deg);
-ms-transform: rotate(270deg);
transform: rotate(270deg);
}
.sk-fading-circle .sk-circle11 {
-webkit-transform: rotate(300deg);
-ms-transform: rotate(300deg);
transform: rotate(300deg);
}
.sk-fading-circle .sk-circle12 {
-webkit-transform: rotate(330deg);
-ms-transform: rotate(330deg);
transform: rotate(330deg);
}
.sk-fading-circle .sk-circle2:before {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}
.sk-fading-circle .sk-circle3:before {
-webkit-animation-delay: -1s;
animation-delay: -1s;
}
.sk-fading-circle .sk-circle4:before {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}
.sk-fading-circle .sk-circle5:before {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}
.sk-fading-circle .sk-circle6:before {
-webkit-animation-delay: -0.7s;
animation-delay: -0.7s;
}
.sk-fading-circle .sk-circle7:before {
-webkit-animation-delay: -0.6s;
animation-delay: -0.6s;
}
.sk-fading-circle .sk-circle8:before {
-webkit-animation-delay: -0.5s;
animation-delay: -0.5s;
}
.sk-fading-circle .sk-circle9:before {
-webkit-animation-delay: -0.4s;
animation-delay: -0.4s;
}
.sk-fading-circle .sk-circle10:before {
-webkit-animation-delay: -0.3s;
animation-delay: -0.3s;
}
.sk-fading-circle .sk-circle11:before {
-webkit-animation-delay: -0.2s;
animation-delay: -0.2s;
}
.sk-fading-circle .sk-circle12:before {
-webkit-animation-delay: -0.1s;
animation-delay: -0.1s;
}
@-webkit-keyframes sk-circleFadeDelay {
0%, 39%, 100% { opacity: 0; }
40% { opacity: 1; }
}
@keyframes sk-circleFadeDelay {
0%, 39%, 100% { opacity: 0; }
40% { opacity: 1; }
}

View File

@ -1,119 +0,0 @@
a, body, button, dd, div, dl, dt, h1, h2, h3, h4, h5, h6, input, li, ol, p, td, textarea, ul { margin: 0; padding: 0; }
body, button, input, select, textarea { font: 9pt/1.5 tahoma,arial,Hiragino Sans GB,\5b8b\4f53,sans-serif; }
button, h1, h2, h3, h4, h5, h6, input, select, textarea { font-size: 100%; }
/*background-image: linear-gradient(160deg, #2f548e 20%,#043559 80%);*/
html{height: 100%; background-image: linear-gradient(160deg, #2f548e 20%,#043559 80%);}
ol, ul { list-style: none;}
a { color: #666; text-decoration: none; }
a:hover { color: #043559; text-decoration: underline; }
body { font-size: 9pt; height: 100%;
font-family: 'microsoft yahei', sans-serif; min-width: 750pt; margin: 0; overflow: hidden}
img { border: 0; vertical-align: top; }
textarea { resize: none; }
a, button, input, select, textarea { outline: 0; }
a, button { cursor: pointer; }
button { border: none; }
.errorlist {font-size: 16px; color: #333333}
.pagewrap {height: 100% }
.main { position: relative; margin-top:0; width: 100%; height: 100%}
.header {height: 100px;margin-bottom: 5%;margin-left: 50px; background: url(/static/img/logo.png) left center no-repeat; }
.header h1 a { display: block; }
.content { overflow: hidden; margin-left: 10% }
.content .con_left { float: left; height: 450px; width: 50%; margin-top: 65px}
/*.content .con_left .box {position: absolute; width: 400px; height:400px; left: 50%; right: 50%; margin-left: -100px; margin-right: -100px;}*/
.content .con_left p { padding: 0 0 3px; width: 10pc; color: #040000; font-size: 1pc; font-family: 'microsoft yahei', sans-serif; }
.content .con_left a { padding: 0 0 0 2pc; color: #2f548e; text-decoration: underline; font-size: 1pc; font-family: 'microsoft yahei', sans-serif; }
.content .con_right { float: left; margin: 65px 0 0; width: 28pc; height: 450px; border: 1px solid #dedede; background: #fff; }
.content .con_right .con_r_top { padding: 0 0 0 39px; width: 409px; height: 110px; border-top: 8px solid #2e558e; }
.content .con_right .con_r_top .left, .content .con_right .con_r_top .right { float: left; padding: 35px 0 0; width: 186px; height: 35px; text-align: center; text-decoration: none; font-size: 18px; font-family: "微软雅黑"; }
.content .con_right .con_r_top .left { border-bottom: 2px solid #dedede; color: #999; }
.content .con_right ul .con_r_left .erweima { text-align: center; }
.content .con_right ul .con_r_left p {color: #2f548e; font-size: 25px; font-family: 'microsoft yahei', sans-serif; }
.content .con_right ul .con_r_left .user input { margin: 0 0 21px -1px; padding-left: 7px; width: 324px; height: 33px; border: 1px solid #dedede; color: #999; font-size: 14px; font-family: "微软雅黑"; line-height: 2pc; }
.content .con_right ul .con_r_left .user { padding: 0 0 0 39px; }
.content .con_right ul .con_r_left .user ul{font-size: 16px; color: #333333}
.content .con_right ul .con_r_left .user li{font-size: 16px; color: #333333}
.content .con_right ul .con_r_left .user .user-icon { float: left; width: 36px; height: 35px; background: url(../img/user-icon.jpg) left top no-repeat; }
.content .con_right ul .con_r_left .user .mima-icon { float: left; width: 36px; height: 35px; background: url(../img/mima-icon.jpg) left top no-repeat; }
.content .con_right ul .con_r_left .user .unlock-icon { float: left; width: 36px; height: 35px; background: url(../img/unlock.jpg) left top no-repeat; }
.content .con_right ul .con_r_left p { overflow: hidden; padding: 0 39px 37px; color: #666; font-size: 13px; font-family: 'microsoft yahei', sans-serif; }
.content .con_right ul .con_r_left p .mima { float: left; padding-left: 5px; text-decoration: none; }
.content .con_right ul .con_r_left p .zhuce { float: right; text-decoration: none; }
.content .con_right ul .con_r_left button { margin: 0 0 0 75pt; width: 250px; height: 44px; background: #2e558e; color: #fff; font-size: 1pc; font-family: 'microsoft yahei', sans-serif; }
.content .con_right .con_r_top .right { border-bottom: 2px solid #2e558e; color: #333; }
.content .con_right ul .con_r_right .user input { margin: 0 0 21px -1px; padding-left: 7px; width: 324px; height: 33px; border: 1px solid #dedede; color: #999; font-size: 14px; font-family: "微软雅黑"; line-height: 2pc; }
.content .con_right ul .con_r_right .user { padding: 0 0 0 39px; }
.content .con_right ul .con_r_right .user ul{font-size: 16px; color: #333333}
.content .con_right ul .con_r_right .user li{font-size: 16px; color: #333333}
.content .con_right ul .con_r_right .user .user-icon { float: left; width: 36px; height: 35px; background: url(../img/user-icon.jpg) left top no-repeat; }
.content .con_right ul .con_r_right .user .mima-icon { float: left; width: 36px; height: 35px; background: url(../img/mima-icon.jpg) left top no-repeat; }
.content .con_right ul .con_r_right .user .unlock-icon { float: left; width: 36px; height: 35px; background: url(../img/unlock.jpg) left top no-repeat; }
.content .con_right ul .con_r_right p { overflow: hidden; padding: 0 39px 37px; color: #666; font-size: 13px; font-family: 'microsoft yahei', sans-serif; }
.content .con_right ul .con_r_right p .mima { float: left; padding-left: 5px; text-decoration: none; }
.content .con_right ul .con_r_right p .zhuce { float: right; text-decoration: none; }
.content .con_right ul .con_r_right button { margin: 0 0 0 75pt; width: 250px; height: 44px; background: #2e558e; color: #fff; font-size: 1pc; font-family: 'microsoft yahei', sans-serif; }
.content .con_right ul .con_r_left { display: none; }
.con_right ul .con_r_left .erweima { position: relative; margin: 0 auto; width: 365px; height: 330px; }
.qrcode { position: absolute; top: 0; left: 0; width: 174px; height: 11pc; }
.divimg { position: absolute; top: 50%; left: 50%; z-index: 100; overflow: hidden; margin-top: -15px; margin-left: -30px; padding: 1px; width: 60px; height: 30px; border: 1px solid #eee; border-radius: .5rem; background: #fff; opacity: .9; filter: alpha(opacity=90); -moz-opacity: .9; }
.content .con_right ul .con_r_right .user .yanzheng { width: 150px; margin: 0 5px 10px 1px; padding-left: 5px; }
.content .con_right ul .con_r_right .user .next { font-size: 12px; width: 40px; height: 33px; float: right; margin-right: 40px; }
.content .con_right .con_r_top { *height: 90px; }
.autoWidth{margin:0 auto;min-width:1000px;max-width:1200px}
.auto{margin:0 auto;min-width:1000px;max-width:1200px}
@media screen and (max-width:1233px){.auto{padding-left:10px}
}
.clearfix:after,.clearfix:before{display:table;line-height:0;content:""}
.clearfix:after{clear:both}
.clear-float{clear:both}
.footer{background-color:#009fd9;font-family: 'microsoft yahei', sans-serif; }
.footer-floor1{width:100%;padding:36px 0 60px}
.footer-list{width:69%;height:100%;float:left}
.footer-list ul{float:left;margin-right:13%}
.footer-list .flist-4{margin-right:0}
.footer-list li{line-height:32px}
.footer-list li a{color:#b6e2f2;font-size:12px;text-decoration:none}
.footer-list li a:hover{text-decoration:underline;color:#fff}
.footer-list .flist-title{font-size:16px;color:#fff;margin-bottom:15px}
.footer-floor2{width:100%;border-top:1px solid #4cc3ed;padding:20px 0;text-align:center}
.footer-floor2 p{text-align:center;color:#b6e2f2;font-size:12px;line-height:30px}
.footer-floor2 p span{font-family:PingFangSC-Light,'helvetica neue','hiragino sans gb',tahoma,'microsoft yahei ui','microsoft yahei',sans-serif}
.footer-floor2 a{color:#b6e2f2}
.footer-floor2 a:hover{color:#a8d0e0;text-decoration:underline}
.foot-link{margin:0 15px;text-decoration:none;color:#b6e2f2}
.foot-link:hover{text-decoration:underline}
.footer-right{width:300px;float:right}
.telephone{width:100%;height:32px;line-height:32px;color:#fff}
.telephone .tel-number{font-size:30px;font-weight:400;text-align:right}
.official-plat{width:100%;height:100%;margin-top:20px;position:relative}
.official-plat ul{float:right;margin-top:7px}
.official-plat ul li{height:45px}
.official-plat ul a{display:inline-block;height:32px;width:100%;line-height:32px;color:#fff;text-decoration:none;font-size:12px}
.official-plat>p{display:inline-block;width:132px;height:132px;border:1px solid #ddd;background-color:#fff}
#wx-corner{border:10px solid transparent;border-left:10px solid #fff;position:absolute;top:12px;right:-20px;z-index:10}
#wb-corner{border:10px solid transparent;border-left:10px solid #fff;position:absolute;top:58px;right:-20px;z-index:10}
.five-superiority{width:100%;border-bottom:1px solid #27aede;padding:10px 0 20px}
.five-superiority-list li{float:left;width:20%;height:36px;text-align:center;border-left:1px solid #27aede}
.five-superiority-list li:first-child{border-left:none}
.five-superiority-list li a{display:inline-block;position:relative;width:100%;height:36px;line-height:36px;background:no-repeat 2% center;text-indent:2em;color:#fff;font-size:16px}
.five-superiority-list li a:hover{color:#bfe7f5}
.five-superiority-list li a.superiority-text{text-indent:4em}
.compensate_ico .superiority-icon{background-position:0 0}
.compensate_ico:hover .superiority-icon{background-position:0 -50px}
.retreat_ico .superiority-icon{background-position:0 -100px}
.retreat_ico:hover .superiority-icon{background-position:0 -150px}
.technology_ico .superiority-icon{background-position:0 -200px}
.technology_ico:hover .superiority-icon{background-position:0 -250px}
.prepare_ico .superiority-icon{background-position:0 -300px}
.prepare_ico:hover .superiority-icon{background-position:0 -350px}
.service_ico .superiority-icon{background-position:0 -400px}
.service_ico:hover .superiority-icon{background-position:0 -450px}
.marquee-box{overflow:hidden;width:100%;position:absolute;left:0;top:0}
.marquee{width:8000%;height:60px}
.wave-list-box{float:left}
.wave-list-box ul{float:left;height:60px;overflow:hidden;zoom:1}
.wave-list-box ul li{height:60px;width:100%;float:left;line-height:30px;list-style:none}
.wave-box{position:relative;height:60px;background:#fff}

View File

@ -1,119 +0,0 @@
a, body, button, dd, div, dl, dt, h1, h2, h3, h4, h5, h6, input, li, ol, p, td, textarea, ul { margin: 0; padding: 0; }
body, button, input, select, textarea { font: 9pt/1.5 tahoma,arial,Hiragino Sans GB,\5b8b\4f53,sans-serif; }
button, h1, h2, h3, h4, h5, h6, input, select, textarea { font-size: 100%; }
/*background-image: linear-gradient(160deg, #2f548e 20%,#043559 80%);*/
html{height: 100%; background-image: linear-gradient(160deg, #2f548e 20%,#043559 80%);}
ol, ul { list-style: none;}
a { color: #666; text-decoration: none; }
a:hover { color: #043559; text-decoration: underline; }
body { font-size: 9pt; height: 100%;
font-family: 'microsoft yahei', sans-serif; min-width: 750pt; margin: 0; overflow: hidden}
img { border: 0; vertical-align: top; }
textarea { resize: none; }
a, button, input, select, textarea { outline: 0; }
a, button { cursor: pointer; }
button { border: none; }
.errorlist {font-size: 16px; color: #333333}
.pagewrap {height: 100% }
.main { position: relative; margin-top:0; width: 100%; height: 100%}
.header {height: 100px;margin-bottom: 5%;margin-left: 50px; background: url(/static/img/rgec.png) left center no-repeat; }
.header h1 a { display: block; }
.content { overflow: hidden; margin-left: 10% }
.content .con_left { float: left; height: 450px; width: 50%; margin-top: 65px}
/*.content .con_left .box {position: absolute; width: 400px; height:400px; left: 50%; right: 50%; margin-left: -100px; margin-right: -100px;}*/
.content .con_left p { padding: 0 0 3px; width: 10pc; color: #040000; font-size: 1pc; font-family: 'microsoft yahei', sans-serif; }
.content .con_left a { padding: 0 0 0 2pc; color: #2f548e; text-decoration: underline; font-size: 1pc; font-family: 'microsoft yahei', sans-serif; }
.content .con_right { float: left; margin: 65px 0 0; width: 28pc; height: 450px; border: 1px solid #dedede; background: #fff; }
.content .con_right .con_r_top { padding: 0 0 0 39px; width: 409px; height: 110px; border-top: 8px solid #2e558e; }
.content .con_right .con_r_top .left, .content .con_right .con_r_top .right { float: left; padding: 35px 0 0; width: 186px; height: 35px; text-align: center; text-decoration: none; font-size: 18px; font-family: "微软雅黑"; }
.content .con_right .con_r_top .left { border-bottom: 2px solid #dedede; color: #999; }
.content .con_right ul .con_r_left .erweima { text-align: center; }
.content .con_right ul .con_r_left p {color: #2f548e; font-size: 25px; font-family: 'microsoft yahei', sans-serif; }
.content .con_right ul .con_r_left .user input { margin: 0 0 1px -1px; padding-left: 7px; width: 324px; height: 33px; border: 1px solid #dedede; color: #999; font-size: 14px; font-family: "微软雅黑"; line-height: 2pc; }
.content .con_right ul .con_r_left .user { padding: 0 0 0 39px; }
.content .con_right ul .con_r_left .user ul{font-size: 16px; color: #333333}
.content .con_right ul .con_r_left .user li{font-size: 16px; color: #333333}
.content .con_right ul .con_r_left .user .user-icon { float: left; width: 36px; height: 35px; background: url(../img/user-icon.jpg) left top no-repeat; }
.content .con_right ul .con_r_left .user .mima-icon { float: left; width: 36px; height: 35px; background: url(../img/mima-icon.jpg) left top no-repeat; }
.content .con_right ul .con_r_left .user .unlock-icon { float: left; width: 36px; height: 35px; background: url(../img/unlock.jpg) left top no-repeat; }
.content .con_right ul .con_r_left p { overflow: hidden; padding: 0 39px 37px; color: #666; font-size: 13px; font-family: 'microsoft yahei', sans-serif; }
.content .con_right ul .con_r_left p .mima { float: left; padding-left: 5px; text-decoration: none; }
.content .con_right ul .con_r_left p .zhuce { float: right; text-decoration: none; }
.content .con_right ul .con_r_left button { margin: 0 0 0 75pt; width: 250px; height: 44px; background: #2e558e; color: #fff; font-size: 1pc; font-family: 'microsoft yahei', sans-serif; }
.content .con_right .con_r_top .right { border-bottom: 2px solid #2e558e; color: #333; }
.content .con_right ul .con_r_right .user input { margin: 0 0 1px -1px; padding-left: 7px; width: 324px; height: 33px; border: 1px solid #dedede; color: #999; font-size: 14px; font-family: "微软雅黑"; line-height: 2pc; }
.content .con_right ul .con_r_right .user { padding: 0 0 0 39px; }
.content .con_right ul .con_r_right .user ul{font-size: 16px; color: #333333}
.content .con_right ul .con_r_right .user li{font-size: 16px; color: #333333}
.content .con_right ul .con_r_right .user .user-icon { float: left; width: 36px; height: 35px; background: url(../img/user-icon.jpg) left top no-repeat; }
.content .con_right ul .con_r_right .user .mima-icon { float: left; width: 36px; height: 35px; background: url(../img/mima-icon.jpg) left top no-repeat; }
.content .con_right ul .con_r_right .user .unlock-icon { float: left; width: 36px; height: 35px; background: url(../img/unlock.jpg) left top no-repeat; }
.content .con_right ul .con_r_right p { overflow: hidden; padding: 0 39px 37px; color: #666; font-size: 13px; font-family: 'microsoft yahei', sans-serif; }
.content .con_right ul .con_r_right p .mima { float: left; padding-left: 5px; text-decoration: none; }
.content .con_right ul .con_r_right p .zhuce { float: right; text-decoration: none; }
.content .con_right ul .con_r_right button { margin: 0 0 0 75pt; width: 250px; height: 44px; background: #2e558e; color: #fff; font-size: 1pc; font-family: 'microsoft yahei', sans-serif; }
.content .con_right ul .con_r_left { display: none; }
.con_right ul .con_r_left .erweima { position: relative; margin: 0 auto; width: 365px; height: 330px; }
.qrcode { position: absolute; top: 0; left: 0; width: 174px; height: 11pc; }
.divimg { position: absolute; top: 50%; left: 50%; z-index: 100; overflow: hidden; margin-top: -15px; margin-left: -30px; padding: 1px; width: 60px; height: 30px; border: 1px solid #eee; border-radius: .5rem; background: #fff; opacity: .9; filter: alpha(opacity=90); -moz-opacity: .9; }
.content .con_right ul .con_r_right .user .yanzheng { width: 150px; margin: 0 5px 10px 1px; padding-left: 5px; }
.content .con_right ul .con_r_right .user .next { font-size: 12px; width: 40px; height: 33px; float: right; margin-right: 40px; }
.content .con_right .con_r_top { *height: 90px; }
.autoWidth{margin:0 auto;min-width:1000px;max-width:1200px}
.auto{margin:0 auto;min-width:1000px;max-width:1200px}
@media screen and (max-width:1233px){.auto{padding-left:10px}
}
.clearfix:after,.clearfix:before{display:table;line-height:0;content:""}
.clearfix:after{clear:both}
.clear-float{clear:both}
.footer{background-color:#009fd9;font-family: 'microsoft yahei', sans-serif; }
.footer-floor1{width:100%;padding:36px 0 60px}
.footer-list{width:69%;height:100%;float:left}
.footer-list ul{float:left;margin-right:13%}
.footer-list .flist-4{margin-right:0}
.footer-list li{line-height:32px}
.footer-list li a{color:#b6e2f2;font-size:12px;text-decoration:none}
.footer-list li a:hover{text-decoration:underline;color:#fff}
.footer-list .flist-title{font-size:16px;color:#fff;margin-bottom:15px}
.footer-floor2{width:100%;border-top:1px solid #4cc3ed;padding:20px 0;text-align:center}
.footer-floor2 p{text-align:center;color:#b6e2f2;font-size:12px;line-height:30px}
.footer-floor2 p span{font-family:PingFangSC-Light,'helvetica neue','hiragino sans gb',tahoma,'microsoft yahei ui','microsoft yahei',sans-serif}
.footer-floor2 a{color:#b6e2f2}
.footer-floor2 a:hover{color:#a8d0e0;text-decoration:underline}
.foot-link{margin:0 15px;text-decoration:none;color:#b6e2f2}
.foot-link:hover{text-decoration:underline}
.footer-right{width:300px;float:right}
.telephone{width:100%;height:32px;line-height:32px;color:#fff}
.telephone .tel-number{font-size:30px;font-weight:400;text-align:right}
.official-plat{width:100%;height:100%;margin-top:20px;position:relative}
.official-plat ul{float:right;margin-top:7px}
.official-plat ul li{height:45px}
.official-plat ul a{display:inline-block;height:32px;width:100%;line-height:32px;color:#fff;text-decoration:none;font-size:12px}
.official-plat>p{display:inline-block;width:132px;height:132px;border:1px solid #ddd;background-color:#fff}
#wx-corner{border:10px solid transparent;border-left:10px solid #fff;position:absolute;top:12px;right:-20px;z-index:10}
#wb-corner{border:10px solid transparent;border-left:10px solid #fff;position:absolute;top:58px;right:-20px;z-index:10}
.five-superiority{width:100%;border-bottom:1px solid #27aede;padding:10px 0 20px}
.five-superiority-list li{float:left;width:20%;height:36px;text-align:center;border-left:1px solid #27aede}
.five-superiority-list li:first-child{border-left:none}
.five-superiority-list li a{display:inline-block;position:relative;width:100%;height:36px;line-height:36px;background:no-repeat 2% center;text-indent:2em;color:#fff;font-size:16px}
.five-superiority-list li a:hover{color:#bfe7f5}
.five-superiority-list li a.superiority-text{text-indent:4em}
.compensate_ico .superiority-icon{background-position:0 0}
.compensate_ico:hover .superiority-icon{background-position:0 -50px}
.retreat_ico .superiority-icon{background-position:0 -100px}
.retreat_ico:hover .superiority-icon{background-position:0 -150px}
.technology_ico .superiority-icon{background-position:0 -200px}
.technology_ico:hover .superiority-icon{background-position:0 -250px}
.prepare_ico .superiority-icon{background-position:0 -300px}
.prepare_ico:hover .superiority-icon{background-position:0 -350px}
.service_ico .superiority-icon{background-position:0 -400px}
.service_ico:hover .superiority-icon{background-position:0 -450px}
.marquee-box{overflow:hidden;width:100%;position:absolute;left:0;top:0}
.marquee{width:8000%;height:60px}
.wave-list-box{float:left}
.wave-list-box ul{float:left;height:60px;overflow:hidden;zoom:1}
.wave-list-box ul li{height:60px;width:100%;float:left;line-height:30px;list-style:none}
.wave-box{position:relative;height:60px;background:#fff}

View File

@ -1,66 +1,54 @@
*{margin:0;padding:0;box-sizing:border-box;list-style:none}
html{height: 100%; width:100%}
body{font-family:"Microsoft Yahei";min-width:1000px}
a{outline:0;text-decoration:none}
strong{font-weight:400}
.strong{font-weight:700}
::selection{background:#1EACDF;color:#fff}
img{border:0}
::-moz-selection{background:#1EACDF;color:#fff}
::-webkit-selection{background:#1EACDF;color:#fff}
.autoWidth{margin:0 auto;min-width:1000px;max-width:1200px}
.auto{margin:0 auto;min-width:1000px;max-width:1200px}
@media screen and (max-width:1233px){.auto{padding-left:10px}
html, body {
width: 100%;
height: 100%;
}
body {
font-family: "Microsoft Yahei", serif;
background: #fafafa;
}
a:link {
text-decoration: none;
}
a:hover {
cursor:pointer;
}
.a-middle-text {
text-align: center;
}
.grid {
padding: 1px;
line-height: 50px;
margin-bottom: 15px;
}
.middle-header {
margin-top: 30px;
line-height: 50px;
margin-bottom: 5%;
border-color: #b7b7b7;
font-weight: bold;
text-align: center;
}
.middle-header-title {
margin-top: 30px;
line-height: 50px;
border-color: #b7b7b7;
font-weight: bold;
}
.clearfix:after,.clearfix:before{display:table;line-height:0;content:""}
.clearfix:after{clear:both}
.clear-float{clear:both}
.layui-panel {
padding: 10px 20px;
}
.footer{background-color:#009fd9;font-family:"Microsoft Yahei"}
.footer-floor1{width:100%;padding:36px 0 60px}
.footer-list{width:69%;height:100%;float:left}
.footer-list ul{float:left;margin-right:13%}
.footer-list .flist-4{margin-right:0}
.footer-list li{line-height:32px}
.footer-list li a{color:#b6e2f2;font-size:12px;text-decoration:none}
.footer-list li a:hover{text-decoration:underline;color:#fff}
.footer-list .flist-title{font-size:16px;color:#fff;margin-bottom:15px}
.footer-floor2{width:100%;border-top:1px solid #4cc3ed;padding:20px 0;text-align:center}
.footer-floor2 p{text-align:center;color:#b6e2f2;font-size:12px;line-height:30px}
.footer-floor2 p span{font-family:PingFangSC-Light,'helvetica neue','hiragino sans gb',tahoma,'microsoft yahei ui','microsoft yahei',simsun,sans-serif}
.footer-floor2 a{color:#b6e2f2}
.footer-floor2 a:hover{color:#a8d0e0;text-decoration:underline}
.foot-link{margin:0 15px;text-decoration:none;color:#b6e2f2}
.foot-link:hover{text-decoration:underline}
.footer-right{width:300px;float:right}
.official-plat{width:100%;height:100%;margin-top:20px;position:relative}
.official-plat ul{float:right;margin-top:7px}
.official-plat ul li{height:45px}
.official-plat ul a{display:inline-block;height:32px;width:100%;line-height:32px;color:#fff;text-decoration:none;font-size:12px}
.official-plat>p{display:inline-block;width:132px;height:132px;border:1px solid #ddd;background-color:#fff}
#wx-corner{border:10px solid transparent;border-left:10px solid #fff;position:absolute;top:12px;right:-20px;z-index:10}
#wb-corner{border:10px solid transparent;border-left:10px solid #fff;position:absolute;top:58px;right:-20px;z-index:10}
.five-superiority{width:100%;border-bottom:1px solid #27aede;padding:10px 0 20px}
.five-superiority-list li{float:left;width:20%;height:36px;text-align:center;border-left:1px solid #27aede}
.five-superiority-list li:first-child{border-left:none}
.five-superiority-list li a{display:inline-block;position:relative;width:100%;height:36px;line-height:36px;background:no-repeat 2% center;text-indent:2em;color:#fff;font-size:16px}
.five-superiority-list li a:hover{color:#bfe7f5}
.five-superiority-list li a.superiority-text{text-indent:4em}
.compensate_ico .superiority-icon{background-position:0 0}
.compensate_ico:hover .superiority-icon{background-position:0 -50px}
.retreat_ico .superiority-icon{background-position:0 -100px}
.retreat_ico:hover .superiority-icon{background-position:0 -150px}
.technology_ico .superiority-icon{background-position:0 -200px}
.technology_ico:hover .superiority-icon{background-position:0 -250px}
.prepare_ico .superiority-icon{background-position:0 -300px}
.prepare_ico:hover .superiority-icon{background-position:0 -350px}
.service_ico .superiority-icon{background-position:0 -400px}
.service_ico:hover .superiority-icon{background-position:0 -450px}
.marquee-box{overflow:hidden;width:100%;position:absolute;left:0;top:0}
.marquee{width:8000%;height:60px}
.wave-list-box{float:left}
.wave-list-box ul{float:left;height:60px;overflow:hidden;zoom:1}
.wave-list-box ul li{height:60px;width:100%;float:left;line-height:30px;list-style:none}
.wave-box{position:relative;height:60px;background:#fff}
.layui-elem-fieldset {
margin-top: 10%;
border-color: #b7b7b7;
border-radius: 6px;
background: #ffffff;
}

View File

@ -1,66 +0,0 @@
*{margin:0;padding:0;box-sizing:border-box;list-style:none}
html{height: 100%; width:100%}
body{font-family:"Microsoft Yahei";min-width:1000px}
a{outline:0;text-decoration:none}
strong{font-weight:400}
.strong{font-weight:700}
::selection{background:#1EACDF;color:#fff}
img{border:0}
::-moz-selection{background:#1EACDF;color:#fff}
::-webkit-selection{background:#1EACDF;color:#fff}
.autoWidth{margin:0 auto;min-width:1000px;max-width:1200px}
.auto{margin:0 auto;min-width:1000px;max-width:1200px}
@media screen and (max-width:1233px){.auto{padding-left:10px}
}
.clearfix:after,.clearfix:before{display:table;line-height:0;content:""}
.clearfix:after{clear:both}
.clear-float{clear:both}
.footer{background-color:#009fd9;font-family:"Microsoft Yahei"}
.footer-floor1{width:100%;padding:36px 0 60px}
.footer-list{width:69%;height:100%;float:left}
.footer-list ul{float:left;margin-right:13%}
.footer-list .flist-4{margin-right:0}
.footer-list li{line-height:32px}
.footer-list li a{color:#b6e2f2;font-size:12px;text-decoration:none}
.footer-list li a:hover{text-decoration:underline;color:#fff}
.footer-list .flist-title{font-size:16px;color:#fff;margin-bottom:15px}
.footer-floor2{width:100%;border-top:1px solid #4cc3ed;padding:20px 0;text-align:center}
.footer-floor2 p{text-align:center;color:#b6e2f2;font-size:12px;line-height:30px}
.footer-floor2 p span{font-family:PingFangSC-Light,'helvetica neue','hiragino sans gb',tahoma,'microsoft yahei ui','microsoft yahei',simsun,sans-serif}
.footer-floor2 a{color:#b6e2f2}
.footer-floor2 a:hover{color:#a8d0e0;text-decoration:underline}
.foot-link{margin:0 15px;text-decoration:none;color:#b6e2f2}
.foot-link:hover{text-decoration:underline}
.footer-right{width:300px;float:right}
.official-plat{width:100%;height:100%;margin-top:20px;position:relative}
.official-plat ul{float:right;margin-top:7px}
.official-plat ul li{height:45px}
.official-plat ul a{display:inline-block;height:32px;width:100%;line-height:32px;color:#fff;text-decoration:none;font-size:12px}
.official-plat>p{display:inline-block;width:132px;height:132px;border:1px solid #ddd;background-color:#fff}
#wx-corner{border:10px solid transparent;border-left:10px solid #fff;position:absolute;top:12px;right:-20px;z-index:10}
#wb-corner{border:10px solid transparent;border-left:10px solid #fff;position:absolute;top:58px;right:-20px;z-index:10}
.five-superiority{width:100%;border-bottom:1px solid #27aede;padding:10px 0 20px}
.five-superiority-list li{float:left;width:20%;height:36px;text-align:center;border-left:1px solid #27aede}
.five-superiority-list li:first-child{border-left:none}
.five-superiority-list li a{display:inline-block;position:relative;width:100%;height:36px;line-height:36px;background:no-repeat 2% center;text-indent:2em;color:#fff;font-size:16px}
.five-superiority-list li a:hover{color:#bfe7f5}
.five-superiority-list li a.superiority-text{text-indent:4em}
.compensate_ico .superiority-icon{background-position:0 0}
.compensate_ico:hover .superiority-icon{background-position:0 -50px}
.retreat_ico .superiority-icon{background-position:0 -100px}
.retreat_ico:hover .superiority-icon{background-position:0 -150px}
.technology_ico .superiority-icon{background-position:0 -200px}
.technology_ico:hover .superiority-icon{background-position:0 -250px}
.prepare_ico .superiority-icon{background-position:0 -300px}
.prepare_ico:hover .superiority-icon{background-position:0 -350px}
.service_ico .superiority-icon{background-position:0 -400px}
.service_ico:hover .superiority-icon{background-position:0 -450px}
.marquee-box{overflow:hidden;width:100%;position:absolute;left:0;top:0}
.marquee{width:8000%;height:60px}
.wave-list-box{float:left}
.wave-list-box ul{float:left;height:60px;overflow:hidden;zoom:1}
.wave-list-box ul li{height:60px;width:100%;float:left;line-height:30px;list-style:none}
.wave-box{position:relative;height:60px;background:#fff}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1 +0,0 @@
"use strict";window.bubbly=function(t){var n=t||{},o=function(){return Math.random()},r=n.canvas||document.createElement("canvas"),e=r.width,a=r.height;null===r.parentNode&&(r.setAttribute("style","position:fixed;z-index:-1;left:0;top:0;min-width:100vw;min-height:100vh;"),e=r.width=window.innerWidth,a=r.height=window.innerHeight,document.body.appendChild(r));var i=r.getContext("2d");i.shadowColor=n.shadowColor||"#fff",i.shadowBlur=n.blur||4;var l=i.createLinearGradient(0,0,e,a);l.addColorStop(0,n.colorStart||"#2AE"),l.addColorStop(1,n.colorStop||"#17B");for(var c=n.bubbles||Math.floor(.02*(e+a)),u=[],d=0;d<c;d++)u.push({f:(n.bubbleFunc||function(){return"hsla(0, 0%, 100%, "+.1*o()+")"}).call(),x:o()*e,y:o()*a,r:(n.radiusFunc||function(){return 4+o()*e/25}).call(),a:(n.angleFunc||function(){return o()*Math.PI*2}).call(),v:(n.velocityFunc||function(){return.1+.5*o()}).call()});!function t(){if(null===r.parentNode)return cancelAnimationFrame(t);!1!==n.animate&&requestAnimationFrame(t),i.globalCompositeOperation="source-over",i.fillStyle=l,i.fillRect(0,0,e,a),i.globalCompositeOperation=n.compose||"lighter",u.forEach(function(t){i.beginPath(),i.arc(t.x,t.y,t.r,0,2*Math.PI),i.fillStyle=t.f,i.fill(),t.x+=Math.cos(t.a)*t.v,t.y+=Math.sin(t.a)*t.v,t.x-t.r>e&&(t.x=-t.r),t.x+t.r<0&&(t.x=e+t.r),t.y-t.r>a&&(t.y=-t.r),t.y+t.r<0&&(t.y=a+t.r)})}()};

View File

@ -1,85 +0,0 @@
$(function () {
$(".content .con_right .left").click(function (e) {
$(this).css({ "color": "#333333", "border-bottom": "2px solid #2e558e" });
$(".content .con_right .right").css({ "color": "#999999", "border-bottom": "2px solid #dedede" });
$(".content .con_right ul .con_r_left").css("display", "block");
$(".content .con_right ul .con_r_right").css("display", "none");
});
$(".content .con_right .right").click(function (e) {
$(this).css({ "color": "#333333", "border-bottom": "2px solid #2e558e" });
$(".content .con_right .left").css({ "color": "#999999", "border-bottom": "2px solid #dedede" });
$(".content .con_right ul .con_r_right").css("display", "block");
$(".content .con_right ul .con_r_left").css("display", "none");
});
$('#btn_modify').click(function () {
// ^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[!@#$\%\^\&\*\(\)])[0-9a-zA-Z!@#$\%\^\&\*\(\)]{8,32}$ 要求密码了里面包含字母、数字、特殊字符。
// (?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,30} 密码必须同时包含大写、小写、数字和特殊字符其中三项且至少8位
// (?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)(?=.*?[!#@*&.])[a-zA-Z\d!#@*&.]*{8,30}$
// 判断密码满足大写字母,小写字母,数字和特殊字符,其中四种组合都需要包含
// (?=.*[0-9])(?=.*[a-zA-Z]).{8,30} 大小写字母+数字
regex_mail = new RegExp('^\\w+((-\\w+)|(\\.\\w+))*\\@[A-Za-z0-9]+((\\.|-)[A-Za-z0-9]+)*\\.[A-Za-z0-9]+$')
regex_pwd = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,30}');
if ($.trim($('#user_email').val()) === '') {
alert('请输入邮箱账号');
return false;
} else if (!regex_mail.test($.trim($('#user_email').val()))) {
alert('请输入正确的邮箱账号。\n');
return false;
} else if ($.trim($('#old_password').val()) === '') {
alert('请输入旧密码');
return false;
} else if ($.trim($('#new_password').val()) === '') {
alert('请输入新密码');
return false;
} else if ($.trim($('#new_password').val()) === '1qaz@WSX') {
alert('密码1qaz@WSX为初始密码禁止使用请重新输入新密码。\n密码必须同时包含大写、小写、数字和特殊字符其中三项且至少8位。');
return false;
} else if (!regex_pwd.test($.trim($('#new_password').val()))) {
alert('密码不符合复杂度规则,请重新输入新密码。\n密码必须同时包含大写、小写、数字和特殊字符其中三项且至少8位。');
return false;
} else if ($.trim($('#ensure_password').val()) === '') {
alert('请再次输入新密码');
return false;
} else if ($.trim($('#new_password').val()) === $.trim($('#old_password').val())) {
alert('新旧密码不能一样');
return false;
} else if ($.trim($('#ensure_password').val()) !== $.trim($('#new_password').val())) {
alert('两次输入的新密码不一致');
return false;
} else {
return true;
}
});
$('#btn_reset').click(function () {
let regex_mail = new RegExp('^\\w+((-\\w+)|(\\.\\w+))*\\@[A-Za-z0-9]+((\\.|-)[A-Za-z0-9]+)*\\.[A-Za-z0-9]+$')
let regex_pwd = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,30}');
if ($.trim($('#user_email').val()) === '') {
alert('请输入邮箱账号');
return false;
} else if (!regex_mail.test($.trim($('#user_email').val()))) {
alert('请输入正确的邮箱账号。\n');
return false;
} else if ($.trim($('#new_password').val()) === '') {
alert('请输入密码');
return false;
} else if ($.trim($('#ensure_password').val()) === '') {
alert('请再次输入新密码');
return false;
} else if ($.trim($('#new_password').val()) === '1qaz@WSX') {
alert('密码1qaz@WSX为初始密码禁止使用请重新输入新密码。\n密码必须同时包含大写、小写、数字和特殊字符其中三项且至少8位。');
return false;
} else if (!regex_pwd.test($.trim($('#new_password').val()))) {
alert('密码不符合复杂度规则,请重新输入新密码。\n密码必须同时包含大写、小写、数字和特殊字符其中三项且至少8位。\n例如1qaz@WSX');
return false;
} else if ($.trim($('#ensure_password').val()) !== $.trim($('#new_password').val())) {
alert('两次输入的新密码不一致');
return false;
} else {
return true;
}
});
})

View File

@ -1,18 +0,0 @@
!function (window, document) {
function d(a) {
var e, c = document.createElement("iframe"),
d = "https://login.dingtalk.com/login/qrcode.htm?goto=" + a.goto ;
d += a.style ? "&style=" + encodeURIComponent(a.style) : "",
d += a.href ? "&href=" + a.href : "",
c.src = d,
c.frameBorder = "0",
c.allowTransparency = "true",
c.scrolling = "no",
c.width = a.width ? a.width + 'px' : "365px",
c.height = a.height ? a.height + 'px' : "400px",
e = document.getElementById(a.id),
e.innerHTML = "",
e.appendChild(c)
}
window.DDLogin = d
}(window, document);

File diff suppressed because one or more lines are too long

1
static/js/html5.min.js vendored Normal file
View File

@ -0,0 +1 @@
(function(e,t){function n(e,t){var n=e.createElement("p"),i=e.getElementsByTagName("head")[0]||e.documentElement;return n.innerHTML="x<style>"+t+"</style>",i.insertBefore(n.lastChild,i.firstChild)}function i(){var e=m.elements;return"string"==typeof e?e.split(" "):e}function r(e){var t={},n=e.createElement,r=e.createDocumentFragment,o=r();e.createElement=function(e){m.shivMethods||n(e);var i;return i=t[e]?t[e].cloneNode():g.test(e)?(t[e]=n(e)).cloneNode():n(e),i.canHaveChildren&&!f.test(e)?o.appendChild(i):i},e.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+i().join().replace(/\w+/g,function(e){return t[e]=n(e),o.createElement(e),'c("'+e+'")'})+");return n}")(m,o)}function o(e){var t;return e.documentShived?e:(m.shivCSS&&!d&&(t=!!n(e,"article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio{display:none}canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden]{display:none}audio[controls]{display:inline-block;*display:inline;*zoom:1}mark{background:#FF0;color:#000}")),h||(t=!r(e)),t&&(e.documentShived=t),e)}function a(e){for(var t,n=e.getElementsByTagName("*"),r=n.length,o=RegExp("^(?:"+i().join("|")+")$","i"),a=[];r--;)t=n[r],o.test(t.nodeName)&&a.push(t.applyElement(s(t)));return a}function s(e){for(var t,n=e.attributes,i=n.length,r=e.ownerDocument.createElement(b+":"+e.nodeName);i--;)t=n[i],t.specified&&r.setAttribute(t.nodeName,t.nodeValue);return r.style.cssText=e.style.cssText,r}function l(e){for(var t,n=e.split("{"),r=n.length,o=RegExp("(^|[\\s,>+~])("+i().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),a="$1"+b+"\\:$2";r--;)t=n[r]=n[r].split("}"),t[t.length-1]=t[t.length-1].replace(o,a),n[r]=t.join("}");return n.join("{")}function c(e){for(var t=e.length;t--;)e[t].removeNode()}function u(e){var t,i,r=e.namespaces,o=e.parentWindow;return!y||e.printShived?e:(r[b]===void 0&&r.add(b),o.attachEvent("onbeforeprint",function(){for(var r,o,s,c=e.styleSheets,u=[],d=c.length,h=Array(d);d--;)h[d]=c[d];for(;s=h.pop();)if(!s.disabled&&v.test(s.media)){for(r=s.imports,d=0,o=r.length;o>d;d++)h.push(r[d]);try{u.push(s.cssText)}catch(p){}}u=l(u.reverse().join("")),i=a(e),t=n(e,u)}),o.attachEvent("onafterprint",function(){c(i),t.removeNode(!0)}),e.printShived=!0,e)}var d,h,p=e.html5||{},f=/^<|^(?:button|form|map|select|textarea|object|iframe)$/i,g=/^<|^(?:a|b|button|code|div|fieldset|form|h1|h2|h3|h4|h5|h6|i|iframe|img|input|label|li|link|ol|option|p|param|q|script|select|span|strong|style|table|tbody|td|textarea|tfoot|th|thead|tr|ul)$/i;(function(){var n=t.createElement("a");n.innerHTML="<xyz></xyz>",d="hidden"in n,d&&"function"==typeof injectElementWithStyles&&injectElementWithStyles("#modernizr{}",function(t){t.hidden=!0,d="none"==(e.getComputedStyle?getComputedStyle(t,null):t.currentStyle).display}),h=1==n.childNodes.length||function(){try{t.createElement("a")}catch(e){return!0}var n=t.createDocumentFragment();return n.cloneNode===void 0||n.createDocumentFragment===void 0||n.createElement===void 0}()})();var m={elements:p.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:p.shivCSS!==!1,shivMethods:p.shivMethods!==!1,type:"default",shivDocument:o};e.html5=m,o(t);var v=/^$|\b(?:all|print)\b/,b="html5shiv",y=!h&&function(){var n=t.documentElement;return t.namespaces!==void 0&&t.parentWindow!==void 0&&n.applyElement!==void 0&&n.removeNode!==void 0&&e.attachEvent!==void 0}();m.type+=" print",m.shivPrint=u,u(t)})(this,document);

File diff suppressed because one or more lines are too long

5
static/js/respond.min.js vendored Normal file
View File

@ -0,0 +1,5 @@
/*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl
* Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT
* */
!function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='&shy;<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b<s.length;b++){var c=s[b],e=c.href,f=c.media,g=c.rel&&"stylesheet"===c.rel.toLowerCase();e&&g&&!o[e]&&(c.styleSheet&&c.styleSheet.rawCssText?(v(c.styleSheet.rawCssText,e,f),o[e]=!0):(!/^([a-zA-Z:]*\/\/)/.test(e)&&!r||e.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&("//"===e.substring(0,2)&&(e=a.location.protocol+e),d.push({href:e,media:f})))}w()};x(),c.update=x,c.getEmValue=t,a.addEventListener?a.addEventListener("resize",b,!1):a.attachEvent&&a.attachEvent("onresize",b)}}(this);

View File

@ -1,41 +0,0 @@
$(document).ready(function () {
$(".official-plat ul li:first-child").hover(function () {
$(".weixin").show();
$(".weibo").hide();
});
$("li[title='点击打开官方微博']").hover(function () {
$(".weixin").hide();
$(".weibo").show();
});
//href="#a_null"的统一设置为无效链接
$("a[href='#a_null']").click(function () {
return false;
});
});
//波浪动画
$(function () {
var marqueeScroll = function (id1, id2, id3, timer) {
var $parent = $("#" + id1);
var $goal = $("#" + id2);
var $closegoal = $("#" + id3);
$closegoal.html($goal.html());
function Marquee() {
if (parseInt($parent.scrollLeft()) - $closegoal.width() >= 0) {
$parent.scrollLeft(parseInt($parent.scrollLeft()) - $goal.width());
}
else {
$parent.scrollLeft($parent.scrollLeft() + 1);
}
}
setInterval(Marquee, timer);
}
var marqueeScroll1 = new marqueeScroll("marquee-box", "wave-list-box1", "wave-list-box2", 20);
var marqueeScroll2 = new marqueeScroll("marquee-box3", "wave-list-box4", "wave-list-box5", 40);
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-view{display:block;position:relative;margin:10px 0;padding:0;border:1px solid #eee;border-left-width:6px;background-color:#fafafa;color:#333;font-family:Courier New;font-size:13px}.layui-code-title{position:relative;padding:0 10px;height:40px;line-height:40px;border-bottom:1px solid #eee;font-size:12px}.layui-code-title>.layui-code-about{position:absolute;right:10px;top:0;color:#b7b7b7}.layui-code-about>a{padding-left:10px}.layui-code-view>.layui-code-ol,.layui-code-view>.layui-code-ul{position:relative;overflow:auto}.layui-code-view>.layui-code-ol>li{position:relative;margin-left:45px;line-height:20px;padding:0 10px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view>.layui-code-ol>li:first-child,.layui-code-view>.layui-code-ul>li:first-child{padding-top:10px}.layui-code-view>.layui-code-ol>li:last-child,.layui-code-view>.layui-code-ul>li:last-child{padding-bottom:10px}.layui-code-view>.layui-code-ul>li{position:relative;line-height:20px;padding:0 10px;list-style-type:none;*list-style-type:none;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-dark{border:1px solid #0c0c0c;border-left-color:#3f3f3f;background-color:#0c0c0c;color:#c2be9e}.layui-code-dark>.layui-code-title{border-bottom:none}.layui-code-dark>.layui-code-ol>li,.layui-code-dark>.layui-code-ul>li{background-color:#3f3f3f;border-left:none}.layui-code-dark>.layui-code-ul>li{margin-left:6px}.layui-code-demo .layui-code{visibility:visible!important;margin:-15px;border-top:none;border-right:none;border-bottom:none}.layui-code-demo .layui-tab-content{padding:15px;border-top:none}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 299 KiB

Some files were not shown because too many files have changed in this diff Show More