├── .gitignore
├── LICENSE
├── ReadMe.rst
├── apps
├── __init__.py
├── account
│ ├── __init__.py
│ ├── docs.py
│ ├── handlers.py
│ └── urls.py
├── admin
│ ├── __init__.py
│ ├── decos.py
│ ├── handlers.py
│ └── urls.py
├── auth2
│ ├── __init__.py
│ ├── decos.py
│ ├── docs.py
│ ├── handlers.py
│ └── urls.py
├── base
│ ├── __init__.py
│ ├── handlers.py
│ └── urls.py
├── dashboard
│ ├── __init__.py
│ ├── handlers.py
│ └── urls.py
├── project
│ ├── __init__.py
│ ├── handlers.py
│ └── urls.py
├── share
│ ├── __init__.py
│ ├── docs.py
│ ├── handlers.py
│ └── urls.py
├── testdata
│ ├── __init__.py
│ ├── docs.py
│ ├── handlers.py
│ └── urls.py
├── webapp
│ ├── __init__.py
│ ├── handlers.py
│ └── urls.py
└── ws
│ ├── __init__.py
│ ├── handlers.py
│ └── urls.py
├── config.py
├── config_api.py
├── init_app.py
├── myapplication.py
├── nginx_config
├── test-api.conf
└── test.conf
├── requirements.txt
├── route.py
├── service.sh
├── start.py
└── static
├── favicon.ico
└── templates
└── code
└── argument.codetpl
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 |
57 | # Flask stuff:
58 | instance/
59 | .webassets-cache
60 |
61 | # Scrapy stuff:
62 | .scrapy
63 |
64 | # Sphinx documentation
65 | docs/_build/
66 |
67 | # PyBuilder
68 | target/
69 |
70 | # Jupyter Notebook
71 | .ipynb_checkpoints
72 |
73 | # pyenv
74 | .python-version
75 |
76 | # celery beat schedule file
77 | celerybeat-schedule
78 |
79 | # SageMath parsed files
80 | *.sage.py
81 |
82 | # dotenv
83 | .env
84 |
85 | # virtualenv
86 | .venv
87 | venv/
88 | ENV/
89 |
90 | # Spyder project settings
91 | .spyderproject
92 | .spyproject
93 |
94 | # Rope project settings
95 | .ropeproject
96 |
97 | # mkdocs documentation
98 | /site
99 |
100 | # mypy
101 | .mypy_cache/
102 |
103 | # idea
104 | .idea/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/ReadMe.rst:
--------------------------------------------------------------------------------
1 | =================
2 | ReadMe
3 | =================
4 |
5 | .. contents:: 目录
6 |
7 |
8 |
9 |
10 | 注意事项
11 | ================
12 |
13 | - 依赖dtlib项目
14 | - python3.5以上
15 | - 首次部署时要使用init_app.py初始化
16 | - 依赖mongodb
17 |
18 | 根目录
19 | ==============
20 |
21 |
22 | .. csv-table::
23 | :header: 目录名称,功能介绍
24 |
25 | apitests,接口测试代码
26 | apps,所有的web系统
27 | config,本系统运行所需要的配置目录
28 | lib,本系统调用的一些公共工具
29 | static,静态文件目录
30 | scripts,中间写的一些脚本
31 |
32 |
33 |
34 | 主要模块
35 | ==============
36 |
37 |
38 |
39 | .. csv-table::
40 | :header: 模块名称,备注
41 |
42 | admin,后台运营和管理
43 | jenkins,持续集成相关功能
44 | source,企业的一些资源登记
45 | auth2.0,和扫码相关的服务
46 | ws,websocket 功能
47 |
48 | 发布日志
49 | ===========
50 |
51 | 4.18.5.18.1
52 | ---------------
53 |
54 | - 新增Tag功能,用于区分同一个项目不同执行环境
55 | - 修复一些bug
56 |
57 | 4.18.4.24.1
58 | ---------------
59 |
60 | - 去除对aiomotorengine的依赖,改用motor 1.2.1
61 | - 升级对Mongo3.6的支持,去除对Mongo3.2及以下版本的支持
62 | - 支持Python3.6
63 |
64 | 4.18.4.19.1
65 | ---------------
66 |
67 | - 新增电视展示功能
68 | - 默认开启跨域共享资源
69 | - 修复一些bug
70 |
71 | 3.17.5.29.1
72 | ---------------
73 |
74 | - 重大改变,所有和mongodb相关的时间全部采用utc时间来储存
75 | - 所有的同步和异步的装饰器进行了统一
76 |
77 | 3.17.5.4.2
78 | --------------------
79 |
80 | - 创建默认的project,对于新注册用户
81 | - 新注册用户,现在默认是激活状态
82 | - 加入组织的用户,现在不用审核,直接激活
83 |
84 | 3.17.5.4.1
85 | --------------
86 |
87 | - 增加了base_doc内容
88 | - 删除了一些不需要的内容
89 |
90 | 3.16.11.22.1
91 | --------------------
92 |
93 | - 使用硬编码数据重构了代码
94 | - 添加测试项目,增加了mark项目描述字段
95 | - 添加了管理员账号对注册账号的统计功能
96 | - 手机风控数据收集接口
97 | - 修改了安全测试这一块的字段内容
98 | - 操作日志也加上组织的tag
99 | - 加了手机信息收集
100 | - 手机信息收集加了一些请求头信息
101 | - 加入了auth应用信息
102 |
103 | 3.16.11.16.5
104 | --------------------
105 |
106 | - 完成手机授权功能
107 |
108 | 3.16.11.08.1
109 | ------------------------
110 |
111 | - 加入了微信的移动端的认证
112 |
113 |
114 | 3.16.10.28.1
115 | --------------------
116 |
117 | - 把token的相关表进行了更改
118 | - 增加了各种平台的交叉认证方式
119 |
120 |
121 | 3.16.10.25.1
122 | -------------------
123 |
124 | - 增加了测试的应用模块,和token
125 | - 修复了wechat和user之间的关系表
126 | - 修复user无法获取组织的bug
127 | - 增加移动端的token的一节
128 |
129 |
130 | 3.16.10.11.1
131 | -----------------
132 |
133 | - log_session需要完全删除掉的bug
134 | - 增加用户ID修改和昵称修改的接口
135 |
136 |
137 | 2.16.09.30.2
138 | ----------------------
139 |
140 | - 完成了组织邀请码的机制
141 | - 头部加入了json描述和cookie值
142 | - ensure_ascii 解决json对中文的编码问题
143 | - 将一个数据放在一张表里面
144 | - 设置头部charset,Content-Type,Cookie中的token
145 | - 进行结果过滤,没显示details
146 | - 解决了一个总有多余的字段产生的,激活接口的bug
147 | - 接口调用统计,目前只统计全网的
148 |
149 | 2.16.09.19.11
150 | -------------------
151 |
152 | - 用户注册时,会默认生成组织,组织关系,app等数据
153 | - 单独的decos,api,单独的session
154 | - 修复了一些bug
155 | - 可用的版本
156 | - 获取用户所有的组织信息
157 | - 启动了本地的redis session,因为内存的session有问题,本地成功,但是服务不能
158 |
159 |
160 |
161 | 2.16.09.07.2
162 | ------------------
163 |
164 | - 修复token的bug
165 | - 修复登出的bug
166 |
167 | 2.16.09.07.1
168 | ----------------------
169 |
170 | - 不能使用tornadsession来支持session
171 | - 因为使用token就表明是默认的
172 |
173 | 2.16.7.22.1
174 | ----------------
175 |
176 | - 加入了torndsession来支持session
177 | - logsession是自己实现的一个登录的api-token体系
178 |
179 | 2.16.7.21.1
180 | -----------------
181 |
182 | - 修复了线程池不为1的时候,异步redis的io的身份认证的bug
183 | - 加入了session存储到mongodb中
184 | - 结构进行了调整,增加了account的应用
185 | - 迁移了接口测试数据的保存脚本
186 |
187 | 2.16.5.24.2
188 | ----------------
189 |
190 | - 解决了ioloop的替换的问题asyncio
191 | - 解决了aiomotorengine的集成问题
192 | - 迁移过来了微信扫码登录的功能
193 | - 完成了redis同步和异步io的demo程序
194 |
195 | 2.16.5.17.1
196 | -----------------------
197 |
198 | - 完成了多核心的服务器的启动
199 |
200 |
201 | 2.16.5.12.1
202 | --------------------
203 |
204 | - 新增py3项目
205 |
206 |
207 |
--------------------------------------------------------------------------------
/apps/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x-utest/xtest-server/4d7c05266dd42ebf4a1ddc416eb615410b40b5fb/apps/__init__.py
--------------------------------------------------------------------------------
/apps/account/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x-utest/xtest-server/4d7c05266dd42ebf4a1ddc416eb615410b40b5fb/apps/account/__init__.py
--------------------------------------------------------------------------------
/apps/account/docs.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | """
4 | from aiomotorengine import BooleanField, StringField
5 |
6 | from dtlib.tornado.base_docs import UserBaseDocument
7 |
8 |
9 | class LanAppUser(UserBaseDocument):
10 | """
11 | 内网应用的用户
12 | """
13 | __collection__ = "lan_app_users"
14 | is_active = BooleanField()
15 |
16 |
--------------------------------------------------------------------------------
/apps/account/handlers.py:
--------------------------------------------------------------------------------
1 | # import pymongo
2 | import motor
3 | from dtlib.tornado.base_hanlder import MyUserBaseHandler
4 | from bson import ObjectId
5 | from dtlib import jsontool
6 | from dtlib.aio.decos import my_async_jsonp
7 | from dtlib.dtlog import dlog
8 | from dtlib.randtool import get_uuid1_key
9 | import hashlib
10 | # from dtlib.tornado.account_docs import Organization, UserOrgRelation, User
11 | from dtlib.tornado.decos import token_required
12 | # from dtlib.tornado.docs import UserDetailInfo, UserRegInfo, FeedBackMsg
13 | # from dtlib.tornado.status_cls import UserStatus, ResCode
14 | # from dtlib.tornado.ttl_docs import WebToken, MobileToken, PcAuthMobileToken, AccessToken, TtlOrgInviteCode
15 | from dtlib.utils import list_have_none_mem, get_rand_salt, hashlibmd5with_salt
16 | from dtlib.web.constcls import ConstData
17 | from dtlib.web.decos import deco_jsonp
18 | from dtlib.web.tools import get_std_json_response
19 | from dtlib.web.valuedict import ClientTypeDict
20 |
21 | # from apps.account.docs import LanAppUser
22 | from config import HTTP_PORT, mongodb_cfg, SERVER_PROCESS
23 | # from config_api import capt_image_domain
24 | # from xt_base.document.auth2_docs import MobileAuthPcToken
25 | # from dtlib.tornado.utils import user_id_is_legal, create_dft_organization, create_org_app, create_dft_org_rel
26 | import traceback
27 |
28 | from dtlib.tornado.utils import set_default_rc_tag
29 | from dtlib.tornado.status_cls import UserStatus, UserRegWay
30 |
31 |
32 | class CheckEnv(MyUserBaseHandler):
33 | """
34 | 检查环境
35 | """
36 |
37 | @my_async_jsonp
38 | async def get(self):
39 | tbl_out = {
40 | 'http_port': HTTP_PORT,
41 | 'process_num ': SERVER_PROCESS,
42 | 'db_server': mongodb_cfg.host,
43 | 'mongo_port': mongodb_cfg.port
44 | }
45 | try:
46 | con = motor.MotorClient(host=mongodb_cfg.host, port=mongodb_cfg.port)
47 | db = con[mongodb_cfg.db_name]
48 | res = await db.authenticate(mongodb_cfg.user_name, mongodb_cfg.user_pwd)
49 | if res is True:
50 | tbl_out['connect'] = True
51 | user_id = "admin"
52 | db = self.get_async_mongo()
53 | user_col = db.g_users
54 | res = await user_col.find_one({'user_id': user_id})
55 | if res:
56 | return ConstData.msg_exist
57 | else:
58 | tbl_out['connect'] = False
59 | except:
60 | traceback.print_exc()
61 | tbl_out['connect'] = "mongodb connect error"
62 |
63 | return get_std_json_response(data=jsontool.dumps(tbl_out, ensure_ascii=False))
64 |
65 |
66 | class AccountInit(MyUserBaseHandler):
67 | @my_async_jsonp
68 | async def get(self):
69 | """
70 | 初始化账号
71 | :return:
72 | """
73 | user_id = "admin"
74 | passwd = "admin@2018"
75 | u_name = 'your nickname'
76 |
77 | db = self.get_async_mongo()
78 | user_col = db.g_users
79 | user_reg_col = db.user_reg_info
80 |
81 | res = await user_col.find_one({'user_id': user_id})
82 | if res:
83 | return ConstData.msg_exist
84 | rand_salt = get_rand_salt()
85 | new_user = {
86 | 'user_id': user_id,
87 | 'salt': rand_salt,
88 | 'nickname': u_name,
89 | 'passwd': hashlibmd5with_salt(passwd, rand_salt)
90 | }
91 | new_user = set_default_rc_tag(new_user)
92 | new_user.update(self.set_template())
93 | user_res = await user_col.insert(new_user)
94 | # new_user = await new_user.save()
95 | # """:type:User"""
96 |
97 | new_user_reg_info = {
98 | 'user': user_res,
99 | 'u_name': u_name
100 | }
101 |
102 | new_user_reg_info.update(self.set_http_tag())
103 | new_user_reg_info = set_default_rc_tag(new_user_reg_info)
104 | await user_reg_col.insert(new_user_reg_info)
105 | # await user_reg_info.save()
106 |
107 | org = await self.create_dft_organization(new_user_reg_info, is_default=True)
108 | await self.create_dft_org_rel(new_user_reg_info, org, is_default=False, is_current=True)
109 | res_dict = await self.create_org_app(org)
110 | res_dict['user'] = new_user['user_id']
111 | res_dict['password'] = passwd
112 | invite_json = jsontool.dumps(res_dict, ensure_ascii=False)
113 | return get_std_json_response(data=invite_json)
114 |
115 | @staticmethod
116 | def set_template():
117 | return dict(
118 | status=UserStatus.init,
119 | active=True,
120 | reg_way=UserRegWay.web
121 | )
122 |
123 | async def create_dft_organization(self, user, is_default=False):
124 | """
125 | 创建默认的组织
126 | :type user: User
127 | :parameter is_default:是否是模板组织
128 | :return:
129 | """
130 | # 为新用户建立默认的小组
131 | db = self.get_async_mongo()
132 | org_col = db.organization
133 | # default_org = Organization()
134 |
135 | new_org = dict(
136 | is_default=is_default,
137 | owner=user['user'],
138 | name = 'group template', # 默认组织名称和用户昵称一样,后面提供修改接口
139 | home_page = 'http://www.my-org-page.org'
140 | )
141 | new_org = set_default_rc_tag(new_org)
142 | # default_org.owner_name = user.nickname # 冗余
143 | org_id = await org_col.insert(new_org)
144 | new_org['_id'] = org_id
145 | return new_org
146 |
147 | async def create_dft_org_rel(self, user, org,
148 | is_default=False,
149 | is_current=False,
150 | is_activate=True,
151 | is_owner=True
152 | ):
153 | """
154 | 为组织和用户建立关联
155 | :param user:
156 | :type user:User
157 | :type org:Organization
158 | :param org:
159 | :return:
160 | """
161 | # 建立它们的默认关系
162 | db = self.get_async_mongo()
163 | user_org_rel_col = db.user_org_rel
164 |
165 | new_rel = dict(
166 | organization=org['_id'],
167 | user=user['user'],
168 | is_default=is_default,
169 | is_current=is_current,
170 | is_owner=is_owner,
171 | is_active=is_activate
172 | )
173 | new_rel = set_default_rc_tag(new_rel)
174 | # user_org_rel.user_name = user.nickname # 冗余
175 | return await user_org_rel_col.insert(new_rel)
176 |
177 | async def create_org_app(self, default_org):
178 | """
179 | 为组织创建应用app
180 | :param default_org:
181 | :type org:Organization
182 | :return:
183 | """
184 |
185 | # default_test_app = TestDataApp()
186 | db = self.get_async_mongo()
187 | app_col = db.test_data_app
188 | new_app = dict(
189 | app_id=get_uuid1_key(),
190 | app_key=hashlib.md5(get_uuid1_key().encode(encoding='utf-8')).hexdigest(),
191 | organization=default_org['_id'],
192 | o_name=default_org['name'],
193 | is_default=True # 是默认设置的
194 | )
195 |
196 | new_app = set_default_rc_tag(new_app)
197 | app_id = await app_col.insert(new_app)
198 | new_app['_id'] = app_id
199 | return new_app
200 |
201 |
202 | class GetAuthUserOrganization(MyUserBaseHandler):
203 | @token_required()
204 | @my_async_jsonp
205 | async def get(self):
206 | """
207 | 用户当前视图下的组织信息
208 | :return:
209 | """
210 | res = await self.get_organization()
211 | db = self.get_async_mongo()
212 | org_col = db.organization
213 | org_res = await org_col.find_one({'_id': ObjectId(res)})
214 | return get_std_json_response(data=jsontool.dumps(org_res, ensure_ascii=False))
215 |
216 |
217 | class UserLogin(MyUserBaseHandler):
218 | """
219 | 用户登录,目前感觉还没有用到 2016-10-25
220 | todo: 后面在启用的时候,还需要继续fixbug
221 | """
222 |
223 | @deco_jsonp()
224 | async def post(self):
225 |
226 | post_dict = self.get_post_body_dict()
227 | user_id = post_dict.get(ConstData.user_form, None)
228 | passwd = post_dict.get(ConstData.passwd_form, None)
229 |
230 | if list_have_none_mem(*[user_id, passwd]):
231 | return ConstData.msg_args_wrong
232 |
233 | db = self.get_async_mongo()
234 | user_res = await db['g_users'].find_one({'user_id': user_id, 'is_del': False},
235 | {'_id': 1, 'passwd': 1, 'salt': 1, 'is_lock': 1})
236 | if not user_res:
237 | return ConstData.msg_fail
238 |
239 | try:
240 | if user_res['is_lock'] is True:
241 | return ConstData.msg_forbidden
242 | except:
243 | pass
244 |
245 | user_pass = user_res['passwd']
246 | user_salt = user_res['salt']
247 | _id = user_res['_id']
248 |
249 | md5_password = hashlibmd5with_salt(passwd, user_salt)
250 |
251 | # auth_res = await User.auth(user_id, passwd)
252 | if md5_password == user_pass:
253 | # if auth_res:
254 | token_res = await self.create_token_session(_id, client_type=ClientTypeDict.browser)
255 | data = {
256 | "token": token_res['token']
257 | }
258 | return get_std_json_response(msg="success", data=jsontool.dumps(data))
259 | else:
260 | return ConstData.msg_fail
261 |
262 |
263 | class UserLogout(MyUserBaseHandler):
264 | """
265 | 用户登录出,根据session来判断的,api,user,wechat都可以用这个
266 | """
267 |
268 | @token_required()
269 | @my_async_jsonp
270 | async def get(self):
271 | # 记录日志
272 |
273 | await self.log_out()
274 |
275 | return ConstData.msg_succeed
276 |
277 |
278 | class SetAuthUserInfo(MyUserBaseHandler):
279 | """
280 | 设置当前登录用户的信息,昵称,可以反复修改
281 | """
282 |
283 | @token_required()
284 | @my_async_jsonp
285 | async def post(self):
286 | req_dict = self.get_post_body_dict()
287 | # passwd = req_dict.get('passwd', None)
288 | nickname = req_dict.get('nick_name', None)
289 |
290 | if list_have_none_mem(*[nickname]):
291 | return ConstData.msg_args_wrong
292 |
293 | db = self.get_async_mongo()
294 | user_col = db.g_users
295 |
296 | current_auth_user = await user_col.find_one({'_id': self.cache_session['user']})
297 |
298 | current_auth_user['nickname'] = nickname
299 | await user_col.update({'_id': self.cache_session['user']},
300 | {'$set': {'nickname': nickname}}, upsert=False)
301 | # current_auth_user = await current_auth_user.save()
302 |
303 | res_dict = dict(
304 | user_id=current_auth_user['user_id'],
305 | nickname=current_auth_user['nickname'],
306 | rc_time=current_auth_user['rc_time']
307 | )
308 |
309 | return get_std_json_response(data=jsontool.dumps(res_dict, ensure_ascii=False))
310 |
311 |
312 | class GetAuthUserInfo(MyUserBaseHandler):
313 | """
314 | 获取当前登录用户的信息
315 | """
316 |
317 | @token_required()
318 | @my_async_jsonp
319 | async def get(self):
320 | current_user_id = self.cache_session['user']
321 | # """:type:User"""
322 |
323 | mongo_conn = self.get_async_mongo()
324 |
325 | user_col = mongo_conn['g_users']
326 |
327 | current_auth_user = await user_col.find_one({'_id': ObjectId(current_user_id)})
328 | # print("user_col.find_one({'user_id': %s})" % current_user_id)
329 |
330 | if not current_auth_user:
331 | return ConstData.msg_fail
332 |
333 | res_dict = dict(
334 | user_id=current_auth_user["user_id"],
335 | nickname=current_auth_user["nickname"],
336 | rc_time=current_auth_user["rc_time"],
337 | # status=current_auth_user["status"],
338 | )
339 |
340 | res_data = jsontool.dumps(res_dict, ensure_ascii=False)
341 | dlog.debug(res_data)
342 |
343 | return get_std_json_response(data=res_data)
344 |
345 |
346 | class UpdateUserDetailInfo(MyUserBaseHandler):
347 | @token_required()
348 | @my_async_jsonp
349 | async def post(self):
350 | """
351 | 创建用户的详细信息,联系信息
352 | :return:
353 | """
354 |
355 | post_body_dict = self.get_post_body_dict()
356 |
357 | qq = post_body_dict.get('qq', None)
358 | email = post_body_dict.get('email', None)
359 | phone = post_body_dict.get('phone', None)
360 |
361 | db = self.get_async_mongo()
362 | user_detail_col = db.user_detail_info
363 |
364 | user = self.get_current_session_user()
365 | user_detail_res = await user_detail_col.find_one({'user': user})
366 | user_detail = dict(
367 | qq=qq,
368 | email=email,
369 | phone=phone
370 | )
371 | if user_detail_res is None: # 如果为空则创建一个
372 | user_col = db.g_users
373 | user_res = await user_col.find_one({'_id': user})
374 | new_user_detail = dict(
375 | user=user,
376 | u_name=user_res['nickname']
377 | )
378 | new_user_detail = set_default_rc_tag(new_user_detail)
379 | user_detail.update(new_user_detail)
380 |
381 | user_detail_col.update({'user': user}, {'$set': user_detail}, upsert=True)
382 | return ConstData.msg_succeed
383 |
384 |
385 | class GetUserDetailInfo(MyUserBaseHandler):
386 | @token_required()
387 | @my_async_jsonp
388 | async def get(self):
389 | """
390 | 获取用户的联系信息
391 | :return:
392 | """
393 |
394 | db = self.get_async_mongo()
395 | user_col = db.user_detail_info
396 | user_detail = await user_col.find_one({'user': self.get_current_session_user()})
397 |
398 | if user_detail is None: # 如果为空则创建一个
399 | user_detail = {}
400 | return get_std_json_response(data=jsontool.dumps(user_detail))
401 |
402 | # class GetAuthUserAllOrganizations(MyUserBaseHandler):
403 | # # TODO: change to motor
404 | # @token_required()
405 | # @my_async_jsonp
406 | # async def get(self):
407 | # """
408 | # 本用户的所有的组织信息
409 | # :return:
410 | # """
411 | # # user_id = self.get_user_id()
412 | # current_user = self.get_current_session_user()
413 | #
414 | # if current_user is None:
415 | # return ConstData.msg_none
416 | #
417 | # # 获取所有的组织关系
418 | # org_rels = await UserOrgRelation.objects.filter(user=current_user).find_all()
419 | # """:type:list[UserOrgRelation]"""
420 | #
421 | # org_dict_list = []
422 | #
423 | # for item in org_rels:
424 | # org = item.organization
425 | # """:type:Organization"""
426 | # org_dict = org.to_dict()
427 | # org_dict['is_current'] = item.is_current
428 | # org_dict_list.append(org_dict)
429 | #
430 | # # res = await self.get_organization()
431 | # return get_std_json_response(data=jsontool.dumps(org_dict_list, ensure_ascii=False))
432 |
433 | # region Organization CRUBL
434 |
435 | # class CreateDefaultOrganization(MyUserBaseHandler):
436 | # """
437 | # 创建默认的模板Organization
438 | # """
439 | #
440 | # @token_required()
441 | # @my_async_jsonp
442 | # async def get(self):
443 | # user = self.get_current_session_user()
444 | # org = await create_dft_organization(user)
445 | # await create_dft_org_rel(user, org, is_default=False, is_current=True)
446 | # res = await create_org_app(org)
447 | # invite_json = jsontool.dumps(res.to_dict(), ensure_ascii=False)
448 | # return get_std_json_response(data=invite_json)
449 | #
450 | #
451 | # class CreateOrganization(MyUserBaseHandler):
452 | # # TODO: change to motor
453 | # """
454 | # 创建Organization
455 | # """
456 | #
457 | # async def post(self):
458 | # # 表单提取
459 | #
460 | # name = self.get_argument("name", None)
461 | # home_page = self.get_argument("home_page", None)
462 | # # owner = self.get_argument("owner", None)
463 | #
464 | # # 空值检查
465 | # if list_have_none_mem(*[name, home_page,
466 | # # owner,
467 | # ]):
468 | # return ConstData.msg_args_wrong
469 | #
470 | # # 数据表赋值
471 | # doc_obj = Organization()
472 | #
473 | # doc_obj.name = name
474 | # doc_obj.home_page = home_page
475 | # doc_obj.owner_id = self.cache_session.user_id
476 | # doc_obj.set_default_rc_tag()
477 | # await doc_obj.save()
478 | #
479 | # return ConstData.msg_succeed
480 | #
481 | #
482 | # class GetInviteCode(MyUserBaseHandler):
483 | # # TODO: change to motor
484 | # @token_required()
485 | # @my_async_jsonp
486 | # async def get(self):
487 | # """
488 | # 生成邀请码
489 | # 1. 查询ttl状态码
490 | # 2. 如果没有则生成一条
491 | # 3. 如果有则用原来的
492 | # #. 生成一条邀请链接
493 | # :return:
494 | # """
495 | # # 表单提取
496 | #
497 | # org_id = self.get_argument("org_id", None)
498 | #
499 | # # 空值检查
500 | # if list_have_none_mem(*[org_id,
501 | # ]):
502 | # return ConstData.msg_args_wrong
503 | #
504 | # # 数据表赋值
505 | # org = await Organization.objects.get(id=ObjectId(str(org_id)))
506 | # """:type:Organization"""
507 | # invite_obj = await TtlOrgInviteCode.objects.get(organization=org)
508 | # """:type:TtlOrgInviteCode"""
509 | # if invite_obj is None:
510 | # new_invite = TtlOrgInviteCode()
511 | # new_invite.invite_code = get_uuid1_key() # 生成一个邀请码
512 | # new_invite.organization = org
513 | # new_invite.o_name = org.name
514 | # new_invite.set_default_rc_tag()
515 | # await new_invite.save()
516 | # invite_json = jsontool.dumps(new_invite.to_dict(), ensure_ascii=False)
517 | # return get_std_json_response(data=invite_json)
518 | #
519 | # invite_json = jsontool.dumps(invite_obj.to_dict(), ensure_ascii=False)
520 | # return get_std_json_response(data=invite_json)
521 | #
522 | #
523 | # class GetOrgInviteLink(MyUserBaseHandler):
524 | # # TODO: change to motor
525 | # @token_required()
526 | # @my_async_jsonp
527 | # async def get(self):
528 | # """
529 | # 生成邀请码
530 | # 1. 查询ttl状态码
531 | # 2. 如果没有则生成一条
532 | # 3. 如果有则用原来的
533 | # #. 生成一条邀请链接
534 | # :return:
535 | # """
536 | # # 表单提取
537 | #
538 | # # org_id = self.get_argument("org_id", None)
539 | #
540 | # # 空值检查
541 | # # if list_have_none_mem(*[org_id,
542 | # # ]):
543 | # # return ConstData.msg_args_wrong
544 | #
545 | # # 数据表赋值
546 | # org = await self.get_organization()
547 | # """:type:Organization"""
548 | #
549 | # # org = await Organization.objects.get(id=ObjectId(str(org_id)))
550 | #
551 | # invite_obj = await TtlOrgInviteCode.objects.get(organization=org)
552 | # """:type:TtlOrgInviteCode"""
553 | # if invite_obj is None:
554 | # new_invite = TtlOrgInviteCode()
555 | # new_invite.invite_code = get_uuid1_key() # 生成一个邀请码
556 | # new_invite.organization = org
557 | # new_invite.o_name = org.name
558 | # new_invite.set_default_rc_tag()
559 | # invite_obj = await new_invite.save()
560 | #
561 | # invite_link = 'http://api.apiapp.cc/account/accept-org-invite-by-link/?invite_code=%s' \
562 | # % invite_obj.invite_code
563 | #
564 | # invite_dict = invite_obj.to_dict()
565 | # invite_dict['link'] = invite_link
566 | #
567 | # invite_json = jsontool.dumps(invite_dict, ensure_ascii=False)
568 | # return get_std_json_response(data=invite_json)
569 | #
570 | #
571 | # class AcceptOrgInviteByLink(MyUserBaseHandler):
572 | # # TODO: change to motor
573 | # @my_async_jsonp
574 | # async def get(self):
575 | # """
576 | # 对于有cookie的用户,加入组织
577 | # :return:
578 | # """
579 | #
580 | # log_session = await self.set_session_by_token(cookie_enable=True)
581 | #
582 | # if log_session is None:
583 | # return ConstData.msg_unauthorized
584 | #
585 | # # todo 后续完成一个新接口,对于有cookie的用户,可以直接通过打开链接的方式来加入到组织,
586 | # # 如果用户没有注册的话,就先让注册,会涉及专门的注册增长线路
587 | # invite_code = self.get_argument('invite_code', None)
588 | #
589 | # if list_have_none_mem(*[invite_code, ]):
590 | # return ConstData.msg_args_wrong
591 | #
592 | # invite_code_obj = await TtlOrgInviteCode.objects.get(invite_code=str(invite_code))
593 | # """":type:TtlOrgInviteCode"""
594 | #
595 | # if invite_code_obj is None:
596 | # return ConstData.msg_none
597 | #
598 | # # 根据要求建立此用户和组织之间的关系
599 | # current_user = self.get_current_session_user()
600 | # """:type:User"""
601 | #
602 | # org = invite_code_obj.organization
603 | # """:type:Organization"""
604 | #
605 | # # 如果已经有关系,就不需要建立关系了
606 | # new_user_org_rel = await UserOrgRelation.objects.get(user=current_user, organization=org)
607 | # """:type:UserOrgRelation"""
608 | #
609 | # if new_user_org_rel is None:
610 | # # 新建组织关系
611 | # new_user_org_rel = UserOrgRelation()
612 | # new_user_org_rel.user = current_user
613 | # new_user_org_rel.user_name = current_user.nickname
614 | # new_user_org_rel.organization = org
615 | # new_user_org_rel.org_name = org.name
616 | # new_user_org_rel.is_current = False
617 | # new_user_org_rel.is_default = False
618 | # new_user_org_rel.is_owner = False
619 | # new_user_org_rel.is_active = True # 未被审核的时候,就没被激活,早期为了进入系统便捷,便不需要审核
620 | # new_user_org_rel.set_default_rc_tag()
621 | # new_user_org_rel = await new_user_org_rel.save()
622 | # """:type:UserOrgRelation"""
623 | #
624 | # # 到此处,可以保证有一个组织关系了,不管是旧的已经存在的还是新的
625 | #
626 | # if new_user_org_rel.is_current: # 如果这个组织就是当前组织
627 | # msg = '当前用户就在此邀请组织里面了'
628 | # return get_std_json_response(code=ResCode.ok, msg=msg)
629 | #
630 | # # 如果用户不在邀请组织里面
631 | # new_user_org_rel.is_current = True
632 | #
633 | # # 需要先将旧的is_current的组织状态去除
634 | # old_current_rels = await UserOrgRelation.objects.filter(user=current_user, is_current=True).find_all()
635 | # """:type:list[UserOrgRelation]"""
636 | # if len(old_current_rels) != 1:
637 | # msg = 'more than one current data,please contact the admin'
638 | # return get_std_json_response(code=403, msg=msg)
639 | #
640 | # old_current_rel = old_current_rels[0]
641 | # old_current_rel.is_current = False
642 | #
643 | # await new_user_org_rel.save()
644 | # await old_current_rel.save()
645 | #
646 | # return ConstData.msg_succeed
647 | #
648 | #
649 | # class ExitCurrentOrg(MyUserBaseHandler):
650 | # # TODO: change to motor
651 | # """
652 | # 退出当前组织:
653 | # 1. 删除自己和当前组织的关系(is_del置位)
654 | # 2. 设置自己的当前组织为自己默认创建的组织
655 | # """
656 | #
657 | # @token_required()
658 | # @my_async_jsonp
659 | # async def get(self):
660 | # current_user = self.get_current_session_user()
661 | # current_org = await self.get_organization()
662 | # """:type:Organization"""
663 | #
664 | # user_org_rel = await UserOrgRelation.objects.get(
665 | # user=current_user,
666 | # organization=current_org,
667 | # is_current=True)
668 | # """:type:UserOrgRelation"""
669 | #
670 | # assert user_org_rel is not None, '当前用户的组织信息不存在'
671 | #
672 | # if user_org_rel.is_owner:
673 | # # 如果當前组织就是原始创建的组织,则不允许退出
674 | # msg = 'default org , can not exit'
675 | # return get_std_json_response(code=ResCode.forbidden, data={}, msg=msg)
676 | #
677 | # user_org_rel.is_current = False # 切换组织
678 | # user_org_rel.is_del = True # 删除关系
679 | #
680 | # dft_rel = await UserOrgRelation.objects.get(user=current_user, is_owner=True)
681 | # """:type:UserOrgRelation"""
682 | #
683 | # dft_rel.is_current = True
684 | # dft_rel = await dft_rel.save()
685 | # await user_org_rel.save()
686 | #
687 | # if dft_rel is None:
688 | # return ConstData.msg_fail
689 | #
690 | # return ConstData.msg_succeed
691 | #
692 | #
693 | # class InviteUserToOrg(MyUserBaseHandler):
694 | # # TODO: change to motor
695 | # """
696 | # 邀请用户到Organization
697 | # """
698 | #
699 | # @token_required()
700 | # @my_async_jsonp
701 | # async def get(self):
702 | # """
703 | # 1. 根据传入的串和当前打开此串的用户token来建立关系
704 | # 对于无cookie的用户可以使用此功能
705 | # :return:
706 | # """
707 | #
708 | # # todo 后续完成一个新接口,对于有cookie的用户,可以直接通过打开链接的方式来加入到组织,如果用户没有注册的话,就先让注册,会涉及专门的注册增长线路
709 | # invite_code = self.get_argument('invite_code', None)
710 | #
711 | # if list_have_none_mem(*[invite_code, ]):
712 | # return ConstData.msg_args_wrong
713 | #
714 | # invite_code_obj = await TtlOrgInviteCode.objects.get(invite_code=str(invite_code))
715 | # """":type:TtlOrgInviteCode"""
716 | #
717 | # if invite_code_obj is None:
718 | # return ConstData.msg_none
719 | #
720 | # # 根据要求建立此用户和组织之间的关系
721 | # current_user = self.get_current_session_user()
722 | # """:type:User"""
723 | # org = invite_code_obj.organization
724 | # """:type:Organization"""
725 | #
726 | # # 如果已经有关系,就不需要建立关系了
727 | # new_user_org_rel = await UserOrgRelation.objects.get(user=current_user, organization=org)
728 | # if new_user_org_rel is not None:
729 | # return ConstData.msg_exist
730 | #
731 | # # 如果不存在,则建立关系。需要先将旧的is_current的组织状态去除
732 | # old_current_rels = await UserOrgRelation.objects.filter(user=current_user, is_current=True).find_all()
733 | # """:type:list[UserOrgRelation]"""
734 | # if len(old_current_rels) != 1:
735 | # msg = 'more than one current data,please contact the admin'
736 | # return get_std_json_response(code=403, msg=msg)
737 | #
738 | # old_current_rel = old_current_rels[0]
739 | # old_current_rel.is_current = False
740 | #
741 | # # 新建组织关系
742 | # new_user_org_rel = UserOrgRelation()
743 | # new_user_org_rel.user = current_user
744 | # new_user_org_rel.user_name = current_user.nickname
745 | # new_user_org_rel.organization = org
746 | # new_user_org_rel.org_name = org.name
747 | # new_user_org_rel.is_current = True
748 | # new_user_org_rel.is_default = False
749 | # new_user_org_rel.is_owner = False
750 | # new_user_org_rel.is_active = True # 未被审核的时候,就没被激活,早期为了进入系统便捷,便不需要审核
751 | # new_user_org_rel.set_default_rc_tag()
752 | #
753 | # await old_current_rel.save()
754 | # await new_user_org_rel.save()
755 | #
756 | # return ConstData.msg_succeed
757 | #
758 | #
759 | # class ReadOrganization(MyUserBaseHandler):
760 | # # TODO: change to motor
761 | # """
762 | # 读取Organization
763 | # """
764 | #
765 | # async def get(self):
766 | # callback = self.get_argument('callback', None)
767 | # res = await Organization.objects.filter().find_all()
768 | # result = []
769 | # for item in res:
770 | # result.append(item.to_dict())
771 | # data = get_std_json_response(code=200, data=jsontool.dumps(result))
772 | # data = '%s(%s)' % (callback, data)
773 | # self.write(data)
774 | #
775 | #
776 | # class UpdateOrganization(MyUserBaseHandler):
777 | # # TODO: change to motor
778 | # """
779 | # 更改Organization名称
780 | # """
781 | #
782 | # @token_required()
783 | # @deco_jsonp()
784 | # async def post(self):
785 | # post_dict = self.get_post_body_dict()
786 | # id = post_dict.get('id', None)
787 | # name = post_dict.get('name', None)
788 | #
789 | # if list_have_none_mem(*[id, name]):
790 | # return ConstData.msg_args_wrong
791 | #
792 | # update_org = await Organization.objects.get(id=ObjectId(str(id)))
793 | #
794 | # current_user = self.get_current_session_user()
795 | # if update_org.owner != current_user:
796 | # return ConstData.msg_forbidden
797 | #
798 | # update_org.name = name
799 | # await update_org.save()
800 | # res_dict = dict(
801 | # data=update_org
802 | # )
803 | #
804 | # return get_std_json_response(data=jsontool.dumps(res_dict, ensure_ascii=False))
805 | #
806 | #
807 | # class DeleteOrganization(MyUserBaseHandler):
808 | # """
809 | # 创建Organization
810 | # """
811 | #
812 | # def get(self):
813 | # self.write('get')
814 | #
815 | # def post(self):
816 | # self.write('post')
817 | #
818 | #
819 | # class GetOrgMember(MyUserBaseHandler):
820 | # # TODO: change to motor
821 | # """
822 | # 获取Organization中所有的成员,并显示激活状态
823 | # """
824 | #
825 | # @token_required()
826 | # @deco_jsonp()
827 | # async def get(self):
828 | # org_id = self.get_argument('org_id', None)
829 | #
830 | # if list_have_none_mem(*[org_id]):
831 | # return ConstData.msg_args_wrong
832 | #
833 | # # cur_org = await Organization.objects.get(id=ObjectId(str(org_id)))
834 | # curr_org = await Organization.get_by_id(ObjectId(str(org_id)))
835 | # if curr_org is None:
836 | # return ConstData.msg_none
837 | #
838 | # current_rels = await UserOrgRelation.objects.filter(organization=curr_org).find_all()
839 | # """:type:list[UserOrgRelation]"""
840 | #
841 | # if len(current_rels) == 0:
842 | # return ConstData.msg_none
843 | #
844 | # user_list = []
845 | # """:type:list[dict]"""
846 | #
847 | # for current_rel in current_rels:
848 | # user = current_rel.user
849 | # """:type:User"""
850 | # user_dict = dict(
851 | # user_id=user.user_id,
852 | # name=user.nickname,
853 | # is_active=current_rel.is_active
854 | # )
855 | # user_list.append(user_dict)
856 | #
857 | # return get_std_json_response(data=jsontool.dumps(user_list, ensure_ascii=False))
858 | #
859 | #
860 | # class AuditOrgMember(MyUserBaseHandler):
861 | # # TODO: change to motor
862 | # """
863 | # 对用户加入组织的申请进行激活操作:确立有效的关系
864 | # """
865 | #
866 | # @token_required()
867 | # @my_async_jsonp
868 | # async def get(self):
869 | # audit = self.get_argument('audit', None) # 审核结果
870 | # rel_id = self.get_argument('rel_id', None) # 需要审核的关系ID信息
871 | #
872 | # if list_have_none_mem(*[audit, rel_id]):
873 | # return ConstData.msg_args_wrong
874 | #
875 | # audit = True if audit == '1' else False
876 | #
877 | # curr_org = await self.get_organization()
878 | # if curr_org is None:
879 | # return ConstData.msg_none
880 | #
881 | # current_rels = await UserOrgRelation.objects.filter(
882 | # organization=curr_org,
883 | # id=ObjectId(str(rel_id))
884 | # ).find_all()
885 | # """:type:list[UserOrgRelation]"""
886 | #
887 | # if len(current_rels) != 1:
888 | # return ConstData.msg_args_wrong
889 | #
890 | # current_rel = current_rels[0]
891 | # current_rel.is_active = audit
892 | # await current_rel.save()
893 | #
894 | # return ConstData.msg_succeed
895 |
896 |
897 | # endregion
898 |
899 |
900 | # code trash (2018-04-23)
901 |
902 | # class CreateUser(MyUserBaseHandler):
903 | # # TODO: change to motor
904 | # """
905 | # 注册用户
906 | # 已启用管理员创建用户模式(2018-04-23)
907 | # """
908 | #
909 | # @deco_jsonp()
910 | # async def post(self):
911 | #
912 | # post_dict = self.get_post_body_dict()
913 | # user_id = post_dict.get(ConstData.user_form, None)
914 | # passwd = post_dict.get(ConstData.passwd_form, None)
915 | # u_name = post_dict.get('u_name', None)
916 | #
917 | # if list_have_none_mem(*[user_id, passwd]):
918 | # return ConstData.msg_args_wrong
919 | #
920 | # if u_name is None:
921 | # u_name = 'your nickname'
922 | #
923 | # res = await User.is_exist(user_id)
924 | # if res is False:
925 | # return ConstData.msg_exist
926 | #
927 | # rand_salt = get_rand_salt()
928 | # new_user = User()
929 | # new_user.user_id = user_id
930 | # new_user.salt = rand_salt
931 | # new_user.nickname = u_name
932 | # new_user.passwd = hashlibmd5with_salt(passwd, rand_salt)
933 | # new_user.set_default_rc_tag()
934 | # new_user.set_template()
935 | # new_user = await new_user.save()
936 | # """:type:User"""
937 | #
938 | # user_reg_info = UserRegInfo()
939 | # user_reg_info.user = new_user
940 | # user_reg_info.u_name = new_user.nickname
941 | # user_reg_info.set_http_tag(http_req=self)
942 | # user_reg_info.set_default_rc_tag()
943 | # await user_reg_info.save()
944 | # return ConstData.msg_succeed
945 | #
946 | # class SetAuthUserPwd(MyUserBaseHandler):
947 | # # TODO: change to motor
948 | # """
949 | # 设置当前登录用户的ID,只能修改一次
950 | # """
951 | #
952 | # @token_required()
953 | # @my_async_jsonp
954 | # async def post(self):
955 | # req_dict = self.get_post_body_dict()
956 | # new_user_pwd = req_dict.get('new_pwd', None)
957 | # old_user_id = req_dict.get('old_pwd', None)
958 | #
959 | # if list_have_none_mem(*[old_user_id, new_user_pwd]):
960 | # return ConstData.msg_args_wrong
961 | #
962 | # # 做一些密码复杂性和合法性检查
963 | # if not user_id_is_legal(old_user_id):
964 | # return ConstData.msg_args_wrong
965 | #
966 | # # 检查用户名是否已经存在
967 | # exist_user = await User.objects.get(user_id=old_user_id)
968 | # if exist_user is not None:
969 | # return ConstData.msg_exist
970 | #
971 | # # 没有修改前,可以使用user_id来检查
972 | # current_auth_user = await User.objects.get(id=self.cache_session.user.get_id())
973 | # """:type:User"""
974 | # # mis_wechat_user = await MisWeChatUser.objects.get(user_id=self.cache_log_session.user_id)
975 | # # """:type:MisWeChatUser"""
976 | #
977 | # # 非初始状态,不允许修改
978 | # if current_auth_user.status != UserStatus.init:
979 | # return ConstData.msg_forbidden
980 | #
981 | # current_auth_user.user_id = old_user_id
982 | # current_auth_user.status = UserStatus.user_id_changed
983 | # current_auth_user = await current_auth_user.save()
984 | #
985 | # # # 修改和微信相关联的信息
986 | # # mis_wechat_user.user_id = new_user_id
987 | # # await mis_wechat_user.save()
988 | #
989 | # # 更新session里面的内容
990 | # old_session = await WebToken.objects.get(id=self.cache_session.get_id())
991 | # """:type:WebToken"""
992 | # old_session.user_id = old_user_id
993 | # await old_session.save()
994 | #
995 | # res_dict = dict(
996 | # user_id=current_auth_user.user_id,
997 | # nickname=current_auth_user.nickname,
998 | # rc_time=current_auth_user.rc_time
999 | # )
1000 | #
1001 | # return get_std_json_response(data=jsontool.dumps(res_dict, ensure_ascii=False))
1002 | #
1003 | #
1004 | # class SetAuthUserId(MyUserBaseHandler):
1005 | # # TODO: change to motor
1006 | # """
1007 | # 设置当前登录用户的ID,只能修改一次
1008 | # """
1009 | #
1010 | # @token_required()
1011 | # @my_async_jsonp
1012 | # async def post(self):
1013 | # req_dict = self.get_post_body_dict()
1014 | # new_user_id = req_dict.get('new_user_id', None)
1015 | #
1016 | # if list_have_none_mem(*[new_user_id, ]):
1017 | # return ConstData.msg_args_wrong
1018 | #
1019 | # # 做一些密码复杂性和合法性检查
1020 | # if not user_id_is_legal(new_user_id):
1021 | # return ConstData.msg_args_wrong
1022 | #
1023 | # # 检查用户名是否已经存在
1024 | # exist_user = await User.objects.get(user_id=new_user_id)
1025 | # if exist_user is not None:
1026 | # return ConstData.msg_exist
1027 | #
1028 | # # 没有修改前,可以使用user_id来检查
1029 | # current_auth_user = await User.objects.get(id=self.cache_session.user.get_id())
1030 | # """:type:User"""
1031 | # # mis_wechat_user = await MisWeChatUser.objects.get(user_id=self.cache_log_session.user_id)
1032 | # # """:type:MisWeChatUser"""
1033 | #
1034 | # # 非初始状态,不允许修改
1035 | # if current_auth_user.status != UserStatus.init.value:
1036 | # return ConstData.msg_forbidden
1037 | #
1038 | # current_auth_user.user_id = new_user_id
1039 | # current_auth_user.status = UserStatus.user_id_changed.value
1040 | # current_auth_user = await current_auth_user.save()
1041 | #
1042 | # # # 修改和微信相关联的信息
1043 | # # mis_wechat_user.user_id = new_user_id
1044 | # # await mis_wechat_user.save()
1045 | #
1046 | # # 更新session里面的内容
1047 | # old_session = await WebToken.objects.get(id=self.cache_session.get_id())
1048 | # """:type:WebToken"""
1049 | # old_session.user_id = new_user_id
1050 | # await old_session.save()
1051 | #
1052 | # res_dict = dict(
1053 | # user_id=current_auth_user.user_id,
1054 | # nickname=current_auth_user.nickname,
1055 | # rc_time=current_auth_user.rc_time
1056 | # )
1057 | #
1058 | # return get_std_json_response(data=jsontool.dumps(res_dict, ensure_ascii=False))
1059 | #
1060 | #
1061 | # class GetMobileToken(MyUserBaseHandler):
1062 | # # TODO: change to motor
1063 | # """
1064 | # 从PC引导到手机上,然后创建一个refresh_token
1065 | #
1066 | # 1. 如果不存在手机refresh_token,新增加一个,如果已经存在,则更新时间
1067 | # 2. 返回refresh_token给手机端,让手机来获取 access_token
1068 | # 3. 获取了 mobile_token 之后,表示pam_token已经被使用,则删除掉
1069 | # """
1070 | #
1071 | # @my_async_jsonp
1072 | # async def get(self):
1073 | # qrtoken = self.get_argument('qrtoken', None)
1074 | # if qrtoken is None:
1075 | # return ConstData.msg_args_wrong
1076 | #
1077 | # pam_token = await PcAuthMobileToken.objects.get(token=qrtoken)
1078 | # """:type:PcAuthMobileToken"""
1079 | # if pam_token is None:
1080 | # return ConstData.msg_anonymous
1081 | #
1082 | # user = pam_token.user
1083 | #
1084 | # mobile_token = await self.create_token_session(user, MobileToken,
1085 | # client_type=ClientTypeDict.mobile
1086 | # )
1087 | # """:type:MobileToken"""
1088 | #
1089 | # if mobile_token is not None:
1090 | # await pam_token.complete_del()
1091 | #
1092 | # return get_std_json_response(data=jsontool.dumps(mobile_token.to_dict()))
1093 | #
1094 | #
1095 | # class MobileAuthPc(MyUserBaseHandler):
1096 | # # TODO: change to motor
1097 | # """
1098 | # 登录的手机给PC授权
1099 | # """
1100 | #
1101 | # @token_required()
1102 | # @my_async_jsonp
1103 | # async def get(self):
1104 | #
1105 | # qrtoken = self.get_argument('qrtoken', None)
1106 | #
1107 | # user = self.get_current_session_user() # 当前手机登录的用户
1108 | #
1109 | # if qrtoken is None:
1110 | # return ConstData.msg_args_wrong
1111 | #
1112 | # map_token = await MobileAuthPcToken.objects.get(token=qrtoken)
1113 | # """:type:MobileAuthPcToken"""
1114 | #
1115 | # if map_token is None:
1116 | # return ConstData.msg_none
1117 | #
1118 | # # 授权,用户签名
1119 | # map_token.user = user
1120 | # map_token.user_id = user.user_id
1121 | # map_token.u_name = user.nickname
1122 | #
1123 | # await map_token.save() # 成功签名后保存
1124 | # return ConstData.msg_succeed
1125 |
1126 |
1127 | # class GetMobileAccessToken(MyUserBaseHandler):
1128 | # # TODO: change to motor
1129 | # """
1130 | # 通过 refresh_token 来获取 access_token
1131 | #
1132 | # - 根据 refresh_token 获取 access_token
1133 | # - 如果不存在,则建立移动端的 token
1134 | # - 如果存在,则更新时间
1135 | # - 不管是否存在,都会返回一个 access_token
1136 | # """
1137 | #
1138 | # @my_async_jsonp
1139 | # async def get(self):
1140 | # refresh_token = self.get_argument('rtoken', None)
1141 | # if refresh_token is None:
1142 | # return ConstData.msg_args_wrong
1143 | #
1144 | # refresh_session = await MobileToken.objects.get(token=refresh_token)
1145 | # """:type:MobileToken"""
1146 | # if refresh_session is None:
1147 | # return ConstData.msg_anonymous
1148 | #
1149 | # user = refresh_session.user
1150 | #
1151 | # acc_token = await self.create_token_session(user, AccessToken, client_type=ClientTypeDict.mobile)
1152 | # """:type:AccessToken"""
1153 | #
1154 | # return get_std_json_response(data=jsontool.dumps(acc_token.to_dict()))
1155 | #
1156 | #
1157 | # class LanApp(MyUserBaseHandler):
1158 | # # TODO: change to motor
1159 | # """
1160 | # 重定向到内网app
1161 | # """
1162 | #
1163 | # @token_required()
1164 | # @my_async_jsonp
1165 | # async def get(self):
1166 | # token = self.get_token()
1167 | # lan_user = await LanAppUser.objects.get(user=self.get_current_session_user())
1168 | # """:type:LanAppUser"""
1169 | # if lan_user is None:
1170 | # lan_user = LanAppUser()
1171 | # lan_user.is_active = False
1172 | # lan_user.set_user_tag(http_req=self)
1173 | # lan_user.set_default_rc_tag()
1174 | # lan_user = await lan_user.save()
1175 | #
1176 | # if not lan_user.is_active:
1177 | # res_dict = dict(
1178 | # msg='请联系管理员激活内部用户使用权限'
1179 | # )
1180 | #
1181 | # std_res = get_std_json_response(data=jsontool.dumps(res_dict, ensure_ascii=False))
1182 | #
1183 | # return std_res
1184 | #
1185 | # lan_url = '%s/dist/index.html?token=%s' % (capt_image_domain, token)
1186 | #
1187 | # res_dict = dict(
1188 | # url=lan_url
1189 | # )
1190 | #
1191 | # std_res = get_std_json_response(data=jsontool.dumps(res_dict))
1192 | # return std_res
1193 | #
1194 | #
1195 | # class Feedback(MyUserBaseHandler):
1196 | # # TODO: change to motor
1197 | # """
1198 | # 用户反馈信息
1199 | # """
1200 | #
1201 | # @token_required()
1202 | # @my_async_jsonp
1203 | # async def post(self):
1204 | # body_dict = self.get_post_body_dict()
1205 | # msg = body_dict.get('msg', None)
1206 | #
1207 | # if list_have_none_mem(*[msg]):
1208 | # return ConstData.msg_args_wrong
1209 | #
1210 | # if len(msg) == 0: # 如果是空字符串,也不允许
1211 | # return ConstData.msg_args_wrong
1212 | #
1213 | # feedback = FeedBackMsg()
1214 | # feedback.msg = msg
1215 | # feedback.label = 'improve'
1216 | # feedback.set_user_tag(http_req=self)
1217 | # feedback.set_default_rc_tag()
1218 | # feedback.set_template()
1219 | #
1220 | # await feedback.save()
1221 | # return ConstData.msg_succeed
1222 |
--------------------------------------------------------------------------------
/apps/account/urls.py:
--------------------------------------------------------------------------------
1 | from apps.account.handlers import *
2 | from dtlib import filetool
3 |
4 | app_path = filetool.get_parent_folder_name(__file__) # set the relative path in one place
5 |
6 | url = [
7 | # 用户注册登录
8 | (r"/%s/check-env/" % app_path, CheckEnv),
9 | (r"/%s/account-init/" % app_path, AccountInit),
10 | # (r"/%s/create-new-user/" % app_path, CreateUser), # 注册用户
11 | (r"/%s/user-login/" % app_path, UserLogin), # 登入
12 | (r"/%s/user-logout/" % app_path, UserLogout), # 登出
13 |
14 | # 用户信息操作
15 | (r"/%s/get-auth-user-info/" % app_path, GetAuthUserInfo), # 获取当前登录的用户信息
16 | (r"/%s/set-auth-user-info/" % app_path, SetAuthUserInfo), # 设置和修改当前登录的用户信息,可以反复修改
17 | # (r"/%s/set-auth-user-id/" % app_path, SetAuthUserId), # 修改用户ID,只能修改一次,取代默认生成的杂乱的ID
18 | # (r"/%s/set-auth-user-pwd/" % app_path, SetAuthUserPwd), # 设置用户密码
19 |
20 | (r"/%s/get-auth-user-org/" % app_path, GetAuthUserOrganization), # 获取当前用户的当前视图下的组织信息
21 | # (r"/%s/get-auth-user-all-orgs-info/" % app_path, GetAuthUserAllOrganizations), # 获取当前登录的用户的所有的组织信息
22 |
23 | # 用户详细信息操作
24 | (r"/%s/update-user-detail/" % app_path, UpdateUserDetailInfo), # 新增或者修改用户详细信息
25 | (r"/%s/get-user-detail/" % app_path, GetUserDetailInfo), # 新增或者修改用户详细信息
26 |
27 |
28 | # 组织信息的操作
29 | # todo 后面权限要收回到组织管理员
30 | # (r"/%s/create-organization/" % app_path, CreateOrganization),
31 | # (r"/%s/create-default-organization/" % app_path, CreateDefaultOrganization),
32 | # (r"/%s/read-organization/" % app_path, ReadOrganization),
33 | # (r"/%s/update-organization/" % app_path, UpdateOrganization), # 修改组织群
34 | # (r"/%s/delete-organization/" % app_path, DeleteOrganization),
35 | # (r"/%s/get-org-member/" % app_path, GetOrgMember), # 获取组织成员
36 | # (r"/%s/audit-org-member/" % app_path, AuditOrgMember), # 审核用户加入组织的申请
37 |
38 |
39 | # 组织邀请机制
40 | # 复制邀请码产生的加入
41 | # (r"/%s/get-invite-code/" % app_path, GetInviteCode), # todo 后面要删除掉 获取邀请码,如果没有则创建一个
42 | # (r"/%s/invite-user-to-org/" % app_path, InviteUserToOrg), # todo 后面要删除掉 点击此链接,就可以加入此组织
43 | # # 在PC上点击链接产生的邀请
44 | # (r"/%s/get-org-invite-link/" % app_path, GetOrgInviteLink), # 点击此链接,就可以加入此组织
45 | # (r"/%s/accept-org-invite-by-link/" % app_path, AcceptOrgInviteByLink), # 直接打开链接加入组织
46 | # (r"/%s/exit-current-org/" % app_path, ExitCurrentOrg), # 退出当前组织,返回到自己的默认组织
47 | # todo 通过手机扫描二维码产生的邀请加入
48 |
49 | # 移动端
50 | # (r"/%s/mobile-auth-pc-login/" % app_path, MobileAuthPc), # 由手机扫码,对此token签名授权
51 | # (r"/%s/get-mobile-access-token/" % app_path, GetMobileAccessToken), # 由refresh_token获取access_token
52 |
53 | # 其它接口
54 | # (r"/%s/lan-app/" % app_path, LanApp), # 重定向到内网app
55 |
56 | # (r"/%s/feedback/" % app_path, Feedback), # 登录用户提交反馈信息
57 |
58 | ]
59 |
--------------------------------------------------------------------------------
/apps/admin/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x-utest/xtest-server/4d7c05266dd42ebf4a1ddc416eb615410b40b5fb/apps/admin/__init__.py
--------------------------------------------------------------------------------
/apps/admin/decos.py:
--------------------------------------------------------------------------------
1 | # 和权限相关的装饰器
2 | import functools
3 | from dtlib.web.constcls import ConstData
4 |
5 |
6 | def admin_required(is_async=True):
7 | def _async_token_required(method):
8 | @functools.wraps(method)
9 | async def wrapper(self, *args, **kwargs):
10 | """
11 | 限制用户必须为超管
12 | :param self:
13 | :param args:
14 | :param kwargs:
15 | :return:
16 | """
17 | log_session = await self.set_session_by_token(cookie_enable=False)
18 | if log_session is None:
19 | self.write(ConstData.msg_unauthorized)
20 | return
21 |
22 | user = log_session['user'] # 获取当前用户
23 | db = self.get_async_mongo()
24 | user_col = db.g_users
25 | user_res = await user_col.find_one({'_id': user})
26 | if user_res is None or user_res['user_id'] != 'admin':
27 | self.write(ConstData.msg_forbidden)
28 | return
29 |
30 | if is_async:
31 | await method(self, *args, **kwargs)
32 | else:
33 | method(self, *args, **kwargs)
34 |
35 | return wrapper
36 |
37 | return _async_token_required
38 |
--------------------------------------------------------------------------------
/apps/admin/handlers.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | from dtlib import jsontool
4 | from dtlib.web.decos import deco_jsonp
5 | from dtlib.aio.decos import my_async_paginator, my_async_jsonp
6 | from dtlib.aio.base_mongo import wrap_default_rc_tag
7 | from dtlib.dtlog import dlog
8 | from dtlib.timetool import get_current_utc_time
9 | # from dtlib.tornado.account_docs import MisWeChatUser, User
10 | from dtlib.tornado.decos import token_required
11 | # from dtlib.tornado.docs import ApiCallCounts, LogHistory
12 | # from dtlib.tornado.ttl_docs import WebToken
13 | from dtlib.utils import list_have_none_mem, hashlibmd5with_salt
14 | from dtlib.web.constcls import ConstData
15 | from dtlib.web.tools import get_std_json_response
16 | from pymongo import DESCENDING
17 |
18 | # from apps.admin.decos import perm_check
19 | from dtlib.tornado.base_hanlder import MyUserBaseHandler
20 | from dtlib.tornado.utils import get_org_data
21 | from dtlib.tornado.utils import user_id_is_legal
22 | # from xt_base.document.acl_docs import UserGrpRel
23 | from dtlib.utils import list_have_none_mem, get_rand_salt, hashlibmd5with_salt
24 | from bson import ObjectId
25 | from apps.admin.decos import admin_required
26 |
27 |
28 | class ListOrganization(MyUserBaseHandler):
29 | """
30 | 创建Organization
31 | """
32 |
33 | # todo
34 |
35 | def get(self):
36 | self.write('get')
37 |
38 | def post(self):
39 | self.write('post')
40 |
41 |
42 | class ResetAdminPassword(MyUserBaseHandler):
43 | """
44 | 重置 admin 用户的密码
45 | """
46 |
47 | # @deco_jsonp
48 | @my_async_jsonp
49 | async def post(self):
50 | super_token = "3fb13a601c4111e8801f448a5b61a7f0bcb70841"
51 | req_dict = self.get_post_body_dict()
52 | new_user_pwd = req_dict.get('new_pwd', None)
53 | s_token_recv = req_dict.get('s_token', None)
54 | if list_have_none_mem(*[s_token_recv, new_user_pwd]):
55 | return ConstData.msg_args_wrong
56 | if super_token != s_token_recv:
57 | return ConstData.msg_forbidden
58 | # 做一些密码复杂性和合法性检查
59 | if not user_id_is_legal(new_user_pwd):
60 | return ConstData.msg_args_wrong
61 | db = self.get_async_mongo()
62 | user_col = db.g_users
63 | old_session = await user_col.find_one({'user_id': 'admin'})
64 | await user_col.update({'user_id': 'admin'},
65 | {'$set': {'passwd': hashlibmd5with_salt(new_user_pwd, old_session['salt'])}})
66 | return ConstData.msg_succeed
67 |
68 |
69 | class AddUser(MyUserBaseHandler):
70 | """
71 | 添加用户
72 | """
73 |
74 | def __init__(self, *args, **kwargs):
75 | super(AddUser, self).__init__(*args, **kwargs)
76 |
77 | @admin_required()
78 | @deco_jsonp()
79 | async def post(self, *args, **kwargs):
80 | post_json = self.get_post_body_dict()
81 | user_id = post_json.get('user_id', None)
82 | password = post_json.get('password', None)
83 | nickname = post_json.get('nickname', "默认昵称")
84 |
85 | if list_have_none_mem(*[user_id, password]) or user_id == 'admin':
86 | return ConstData.msg_args_wrong
87 | mongo_conn = self.get_async_mongo()
88 | mycol = mongo_conn['g_users']
89 |
90 | res = await mycol.find_one({
91 | 'user_id': user_id
92 | })
93 | _id = None
94 | if res:
95 | if res['is_del'] is False:
96 | return ConstData.msg_exist
97 | _id = res['_id']
98 |
99 | rand_salt = get_rand_salt()
100 | data = dict(
101 | nickname=nickname,
102 | user_id=user_id,
103 | passwd=hashlibmd5with_salt(password, rand_salt),
104 | salt=rand_salt,
105 | )
106 |
107 | data = wrap_default_rc_tag(data) # 加上默认的标签
108 | if _id:
109 | await mycol.update({'_id': _id}, {'$set': data}, upsert=True)
110 | else:
111 | _id = await mycol.insert(data)
112 |
113 | # todo: select orgnization
114 | user_org_col = mongo_conn['user_org_rel']
115 | res = await user_org_col.find_one({
116 | 'user': _id
117 | })
118 | data = dict(
119 | user_name=nickname,
120 | user=_id,
121 | # organization=org_id,
122 | is_active=True,
123 | # org_name=org_name,
124 | is_default=False,
125 | is_owner=False,
126 | is_current=True
127 | )
128 | if res is None:
129 | org_col = mongo_conn['organization']
130 | res = await org_col.find_one({
131 | 'is_del': False,
132 | })
133 | org_name = res['name']
134 | org_id = res['_id']
135 | else:
136 | org_name = res['org_name']
137 | org_id = res['organization']
138 | data['org_name'] = org_name
139 | data['organization'] = org_id
140 |
141 | data = wrap_default_rc_tag(data) # 加上默认的标签
142 | await user_org_col.update({'user': _id}, {'$set': data}, upsert=True)
143 | return ConstData.msg_succeed
144 |
145 |
146 | class DeleteUser(MyUserBaseHandler):
147 | """
148 | 删除用户, 安全删除
149 | """
150 |
151 | def __init__(self, *args, **kwargs):
152 | super(DeleteUser, self).__init__(*args, **kwargs)
153 |
154 | @admin_required()
155 | @deco_jsonp()
156 | async def post(self, *args, **kwargs):
157 | # todo: judge is admin
158 | post_json = self.get_post_body_dict()
159 | _id = post_json.get('_id', None)
160 | _id = ObjectId(_id)
161 |
162 | if list_have_none_mem(*[_id]):
163 | return ConstData.msg_args_wrong
164 | mongo_conn = self.get_async_mongo()
165 | mycol = mongo_conn['g_users']
166 |
167 | res = await mycol.find_one({
168 | '_id': _id
169 | })
170 | if res:
171 | if res['is_del'] is True or res['user_id'] == 'admin':
172 | return ConstData.msg_args_wrong
173 |
174 | data = dict(
175 | is_del=True
176 | )
177 | # todo: set del_time
178 | await mycol.update({'_id': ObjectId(_id)}, {'$set': data}, upsert=False)
179 |
180 | # update user_org_rel
181 | user_org_col = mongo_conn['user_org_rel']
182 | res = await user_org_col.find_one({
183 | 'user': _id
184 | })
185 | if res:
186 | data = dict(
187 | is_del=True
188 | )
189 | await user_org_col.update({'user': _id}, {'$set': data}, upsert=False)
190 |
191 | return ConstData.msg_succeed
192 |
193 |
194 | class LockUser(MyUserBaseHandler):
195 | """
196 | 锁定用户, 禁止登录
197 | """
198 |
199 | def __init__(self, *args, **kwargs):
200 | super(LockUser, self).__init__(*args, **kwargs)
201 |
202 | @admin_required()
203 | @deco_jsonp()
204 | async def post(self, *args, **kwargs):
205 | # todo: judge is admin
206 | post_json = self.get_post_body_dict()
207 | _id = post_json.get('_id', None)
208 | is_lock = post_json.get('is_lock', None)
209 |
210 | if list_have_none_mem(*[_id, is_lock]):
211 | return ConstData.msg_args_wrong
212 | mongo_conn = self.get_async_mongo()
213 | mycol = mongo_conn['g_users']
214 |
215 | res = await mycol.find_one({
216 | '_id': _id
217 | })
218 | if res:
219 | if res['is_del'] is True or res['user_id'] == 'admin':
220 | return ConstData.msg_args_wrong
221 | if not isinstance(is_lock, bool):
222 | return ConstData.msg_args_wrong
223 | data = dict(
224 | is_lock=is_lock
225 | )
226 | await mycol.update({'_id': ObjectId(_id)}, {'$set': data}, upsert=False)
227 | return ConstData.msg_succeed
228 |
229 |
230 | class GetUserList(MyUserBaseHandler):
231 | """
232 | 获取用户列表
233 | """
234 |
235 | def __init__(self, *args, **kwargs):
236 | super(GetUserList, self).__init__(*args, **kwargs)
237 |
238 | @admin_required()
239 | @deco_jsonp()
240 | async def get(self, *args, **kwargs):
241 | """
242 | admin用户请求该接口可以获取所有用户列表
243 | :param self:
244 | :param args:
245 | :param kwargs:col_name
246 | :return:
247 | """
248 | # todo: admin
249 | show_field = dict(
250 | _id=1,
251 | user_id=1,
252 | nickname=1,
253 | is_lock=1
254 | )
255 | mongo_conn = self.get_async_mongo()
256 | mycol = mongo_conn['g_users']
257 |
258 | user_list = mycol.find({
259 | "is_del": False
260 | }, show_field) # 升序排列
261 | user_cnt = await user_list.count()
262 | user_list = await user_list.to_list(user_cnt)
263 | return get_std_json_response(data=jsontool.dumps(user_list))
264 |
265 | # code trash (2018-04-23 yx)
266 | # class ListUserInfo(MyUserBaseHandler):
267 | # """
268 | # 获取所有的用户的信息
269 | # """
270 | #
271 | # @token_required()
272 | # @deco_jsonp
273 | # @perm_check
274 | # @my_async_paginator
275 | # async def get(self):
276 | # active = self.get_argument('active', None)
277 | #
278 | # if active is '1':
279 | # return await User.objects.filter(active=True).order_by('rc_time', direction=DESCENDING).find_all()
280 | # elif active is '0':
281 | # return await User.objects.filter(active=False).order_by('rc_time', direction=DESCENDING).find_all()
282 | # elif active is None:
283 | # return await User.objects.order_by('rc_time', direction=DESCENDING).find_all()
284 | #
285 | #
286 | # class DeleteUser(MyUserBaseHandler):
287 | # """
288 | # 删除用户
289 | # """
290 | #
291 | # @token_required()
292 | # @deco_jsonp
293 | # @perm_check
294 | # async def get(self):
295 | # user_id = self.get_argument("user_id", None)
296 | # if user_id is None:
297 | # return ConstData.msg_args_wrong
298 | # res = await User.objects.filter(user_id=user_id).delete()
299 | # await WebToken.objects.filter(user_id=user_id).delete()
300 | # await MisWeChatUser.objects.filter(user_id=user_id).delete()
301 | # return ConstData.msg_succeed
302 | #
303 | #
304 | # class OnlineUser(MyUserBaseHandler):
305 | # """
306 | # 在线用户
307 | # """
308 | #
309 | # @token_required()
310 | # @deco_jsonp
311 | # @perm_check
312 | # @my_async_paginator
313 | # async def get(self):
314 | # user = await WebToken.objects.find_all()
315 | # user_id = []
316 | # online_user = []
317 | # for i in user:
318 | # result = i.to_dict()
319 | # user_id.append(result['user_id'])
320 | # for x in user_id:
321 | # user_detail = await User.objects.get(user_id=x)
322 | # if user_detail is not None:
323 | # online_user.append(user_detail)
324 | # else:
325 | # dlog.exception('login user is accidental deleted ')
326 | # return online_user
327 | #
328 | #
329 | # class ListApiCallCount(MyUserBaseHandler):
330 | # """
331 | # read api调用次数
332 | # """
333 | #
334 | # @token_required()
335 | # @deco_jsonp
336 | # @perm_check
337 | # @my_async_paginator
338 | # async def get(self):
339 | # return await ApiCallCounts.objects.order_by('counts', direction=DESCENDING).find_all()
340 | #
341 | #
342 | # class GetLogHistory(MyUserBaseHandler):
343 | # """
344 | # read api调用次数
345 | # """
346 | #
347 | # @token_required()
348 | # @deco_jsonp
349 | # @perm_check
350 | # @my_async_paginator
351 | # async def get(self):
352 | # return await LogHistory.objects.order_by('rc_time', direction=DESCENDING).find_all()
353 | #
354 | #
355 | # class GetUserCounts(MyUserBaseHandler):
356 | # @token_required()
357 | # @deco_jsonp
358 | # async def get(self):
359 | # """
360 | # 获取注册用户统计信息:
361 | # 1. 总体注册用户数
362 | # 2. 今日新增用户数
363 | # :return:
364 | # """
365 | # col_name = 'g_users'
366 | # mongo_conn = self.get_async_mongo()
367 | # mycol = mongo_conn[col_name]
368 | #
369 | # total_cnts = await mycol.find().count() # 总的用户数
370 | #
371 | # now_time = get_current_utc_time()
372 | # start_time = datetime(now_time.year, now_time.month, now_time.day) # 今日凌晨
373 | # today_cnts = await mycol.find({'rc_time': {'$gte': start_time}}).count() # 今日用户数
374 | #
375 | # res_dict = dict(
376 | # total_cnt=total_cnts,
377 | # today_cnt=today_cnts
378 | # )
379 | #
380 | # return get_std_json_response(data=jsontool.dumps(res_dict, ensure_ascii=False))
381 | #
382 | #
383 | # class SetUserActiveStatus(MyUserBaseHandler):
384 | # """
385 | # 设置用户激活信息
386 | # 1. 赋予默认组织
387 | # 2. 设置状态值
388 | # """
389 | #
390 | # @token_required()
391 | # @deco_jsonp
392 | # async def get(self):
393 | # user_obj_id = self.get_argument('user_obj_id', None)
394 | # is_set_active = self.get_argument('active', None)
395 | #
396 | # if list_have_none_mem(*[user_obj_id, is_set_active]) is True:
397 | # return ConstData.msg_args_wrong
398 | #
399 | # user_info = await User.objects.get(user_id=user_obj_id)
400 | # if is_set_active == '1':
401 | # is_set_active = True
402 | # elif is_set_active == '0':
403 | # is_set_active = False
404 | # await WebToken.objects.filter(user_id=user_obj_id).delete()
405 | #
406 | # if user_info is None:
407 | # return ConstData.msg_none
408 | #
409 | # user_info.active = is_set_active
410 | # await user_info.save()
411 | # return ConstData.msg_succeed
412 | #
413 | #
414 | # class LoginHistory(MyUserBaseHandler):
415 | # """
416 | # 人工调用接口的日志
417 | # """
418 | #
419 | # @token_required()
420 | # @deco_jsonp
421 | # @my_async_paginator
422 | # async def get(self):
423 | # """
424 | # 查询所有的接口延时测试信息
425 | # :return:
426 | # """
427 | # return get_org_data(self, cls_name=LogHistory)
428 |
--------------------------------------------------------------------------------
/apps/admin/urls.py:
--------------------------------------------------------------------------------
1 | """
2 | 运管和超管后台
3 |
4 | 主要包含的功能:
5 |
6 | 1. 注册用户的查看
7 | 2. 各种活动日志
8 | """
9 | from apps.admin.handlers import *
10 | from dtlib import filetool
11 |
12 | app_path = filetool.get_parent_folder_name(__file__) # set the relative path in one place
13 |
14 | url = [
15 |
16 | # 用户信息
17 | # (r"/%s/get-all-user-info/" % app_path, ListUserInfo), # 获取所有的注册用户的基本信息
18 | # (r"/%s/delete-user/" % app_path, DeleteUser), # 删除用户
19 | # (r"/%s/online-user/" % app_path, OnlineUser), # 在线用户
20 | # (r"/%s/get-user-counts/" % app_path, GetUserCounts), # 获取注册用户的统计信息
21 |
22 | # 组织信息
23 | # (r"/%s/get-all-org-info/" % app_path,), # 获取所有组织信息
24 | # (r"/%s/audit-org/" % app_path,), # 审核组织
25 | # (r"/%s/freeze-org/" % app_path,),#冻结组织
26 |
27 | # (r"/%s/set-user-active-status/" % app_path, SetUserActiveStatus), # 设置用户的激活状态
28 | #
29 | # # 接口调用
30 | # (r"/%s/read-api-call-counts/" % app_path, ListApiCallCount), # api调用次数
31 | #
32 | # # 用户活动日志
33 | # (r"/%s/get-log-history/" % app_path, GetLogHistory),
34 | #
35 | # # 超级管理员全网用于监控的日志
36 | # (r"/%s/all-login-his/" % app_path, LoginHistory), # 用户登录
37 | # (r"/%s/all-api-calls-his/" % app_path, ApiCallsHistory), # 接口调用。非自动化的接口调用
38 |
39 | # 自动化的接口调用是高频的,要用单独的统计系统来部署
40 | (r"/%s/list-organization/" % app_path, ListOrganization), # todo 分页列表,这个是所有的组织,属于管理人员的接口
41 |
42 | # 分三层权限控制
43 | # TODO 管理员用于监控自己企业的日志数据
44 | # todo 用户监控自己的日志数据
45 |
46 | (r"/%s/reset-admin-password/" % app_path, ResetAdminPassword), # 使用 super_token 重置 admin 用户的密码
47 | (r"/%s/get-user-list/" % app_path, GetUserList), # 获取用户列表
48 | (r"/%s/add-user/" % app_path, AddUser), # 添加用户
49 | (r"/%s/delete-user/" % app_path, DeleteUser), # 删除用户
50 | (r"/%s/lock-user/" % app_path, LockUser), # 锁定/解锁用户
51 | ]
52 |
--------------------------------------------------------------------------------
/apps/auth2/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x-utest/xtest-server/4d7c05266dd42ebf4a1ddc416eb615410b40b5fb/apps/auth2/__init__.py
--------------------------------------------------------------------------------
/apps/auth2/decos.py:
--------------------------------------------------------------------------------
1 | import functools
2 |
3 | from dtlib.tornado.decos import api_counts
4 | from dtlib.web.constcls import ConstData
5 | from dtlib.web.tools import get_jsonp_res
6 |
7 | # from xt_base.document.auth2_docs import AuthAccessToken
8 |
9 |
10 | def async_auth_access_token_required(method):
11 | """
12 | - 认证平台的access_token
13 | :param method:
14 | :return:
15 | """
16 |
17 | @api_counts()
18 | @functools.wraps(method)
19 | async def wrapper(self, *args, **kwargs):
20 | """
21 | :type self MyUserBaseHandler
22 | """
23 | callback = self.get_argument('callback', None)
24 | token = self.get_argument('token', None)
25 |
26 | self.set_header('Content-Type', 'application/json')
27 | self.set_header('charset', 'utf-8')
28 |
29 | if token is None:
30 | self.write(get_jsonp_res(callback, ConstData.msg_unauthorized))
31 | return
32 |
33 | self.set_cookie('token', token)
34 |
35 | log_session = await AuthAccessToken.objects.get(token=token)
36 | """:type:AuthAccessToken"""
37 |
38 | if log_session is None:
39 | self.write(get_jsonp_res(callback, ConstData.msg_unauthorized))
40 | return
41 |
42 | # 在此处直接赋值存储session,一些临时变量在此处缓存
43 | self.token = token # 本次请求都会用到token,相当于会话标识
44 | self.cache_log_session = log_session
45 |
46 | await method(self, *args, **kwargs)
47 |
48 | return wrapper
49 |
--------------------------------------------------------------------------------
/apps/auth2/docs.py:
--------------------------------------------------------------------------------
1 | """
2 | 本模块的所有内容都只针对于认证模块的app,
3 | """
4 | # from aiomotorengine import DateTimeField
5 | # from aiomotorengine import FloatField
6 | # from aiomotorengine import IntField
7 | # from aiomotorengine import ReferenceField
8 | # from aiomotorengine import StringField
9 | #
10 | # from xt_base.document.auth2_docs import ProductResource
11 | # from xt_base.document.source_docs import OrgUserDataDocument
12 | # from dtlib.aio.base_mongo import HttpDocument, ClientTypeDocument, MyDocument
13 | # from dtlib.tornado.account_docs import User
14 | # from dtlib.utils import list_have_none_mem
15 |
16 | # TODO: 后面有需要重写相关功能
17 |
18 | class AuthUser(User):
19 | """
20 | 目前先直接继承本用户体系的账号
21 | """
22 | __collection__ = "g_users"
23 |
24 | # class EnterpriseAccount(User):
25 | # """
26 | # 企业用户账号。
27 | # 一个企业可以创建多个app应用
28 | # 这些app应用共享用户池
29 | # """
30 | # __collection__ = 'auth_enterprise'
31 |
32 |
33 | class AuthApp(OrgUserDataDocument, ClientTypeDocument):
34 | """
35 | 开放应用程序,用于做身份认证的
36 | """
37 |
38 | __collection__ = "auth_app"
39 | __lazy__ = False
40 |
41 | appid = StringField(max_length=32, unique=True) # 公钥
42 | secret = StringField(max_length=32, unique=True) # 私钥
43 | callback = StringField() # 可能是web回调的url或者是回调的app应用包名称,回调的域,用于安全性检查
44 |
45 | async def set_default_tag(self, **kwargs):
46 | """
47 | 设置默认的标记
48 | :param kwargs:
49 | :return:
50 | """
51 | client_type = kwargs.get('client_type', None)
52 | http_req = kwargs.get('http_req', None)
53 |
54 | if list_have_none_mem(*[client_type, http_req]):
55 | return None
56 |
57 | await self.set_org_user_tag(http_req=http_req)
58 | self.set_client_type_tag(client_type=client_type)
59 |
60 | # super(AuthApp, self).set_default_rc_tag()
61 | return True
62 |
63 |
64 | class ResourceGroup(MyDocument):
65 | """
66 | 资源套餐组,不同的套餐组有不同的限额
67 | """
68 | __collection__ = 'resource_group'
69 | __lazy__ = False
70 |
71 | name = StringField() # 小组名称
72 |
73 |
74 | class OrgResourceGroupRelation(OrgUserDataDocument):
75 | """
76 | 组织和资源组的关系,不同的资源组有不同的限额
77 | """
78 |
79 | __collection__ = 'org_resource_grp_rel'
80 | __lazy__ = False
81 |
82 | r_grp = ReferenceField(reference_document_type=ResourceGroup)
83 | rg_name = StringField() # 冗余
84 |
85 |
86 | class ResourceGroupLimit(OrgUserDataDocument):
87 | """
88 | 每个账号的相应的资源的限额
89 | """
90 |
91 | __collection__ = 'resource_group_limit'
92 | __lazy__ = False
93 |
94 | resource = ReferenceField(reference_document_type=ProductResource)
95 | r_name = StringField() # 冗余 ,资源名称
96 | limit = IntField() # 目录创建的数目
97 |
98 |
99 | class OrgResourceCnt(OrgUserDataDocument):
100 | """
101 | 当前组织使用资源的情况
102 | """
103 | __collection__ = 'org_resource_cnt'
104 | __lazy__ = False
105 |
106 | resource = ReferenceField(reference_document_type=ProductResource)
107 | r_name = StringField() # 冗余
108 | cnt = IntField()
109 |
110 |
111 | # class UserPlatformApp(AuthApp):
112 | # """
113 | # 用户开放平台应用,需要提供一些东西进行审核的
114 | # """
115 | #
116 | # company_name = StringField(max_length=1024) # 企业名称
117 | # website = StringField(max_length=1024) # 企业网址
118 | # phone = StringField(max_length=32) # 联系电话
119 | # status = IntField() # 审核状态:0表示刚注册
120 |
121 | #
122 | # class WebOpenApp(AuthApp):
123 | # """
124 | # PC端的Web扫码登录App,对应的还有移动端的。
125 | # """
126 | # __collection__ = "auth_app"
127 | #
128 | # cb_url = StringField(max_length=10240) # 回调的url,类似于webhook一样,只要授权后,就调用的url
129 |
130 |
131 | # class MobileOpenApp(AuthApp):
132 | # """
133 | # 移动端开放平台app
134 | # """
135 | # __collection__ = "auth_mobile_app"
136 | # cb_app = StringField(max_length=10240) # 回调的app应用包名称
137 |
138 |
139 | # 正面都是系统数据,当然也有可能是业务数据
140 |
141 | class MobileSafetyData(OrgUserDataDocument, HttpDocument,
142 | # OperationDocument
143 | ):
144 | """
145 | 手机安全数据
146 |
147 | """
148 |
149 | __collection__ = 'mobile_safety_data'
150 |
151 | bs = StringField() # charging battery status
152 | br = FloatField() # 电池状态,"0.36",battery rate
153 |
154 | carrier = StringField() # 运营商,"\U4e2d\U56fd\U8054\U901a";
155 | cellular = StringField() # LTE;
156 | coun = StringField() # CN;
157 | dn = StringField() # "iPhone 6s"
158 |
159 | idf = StringField() # 85BA4C903C16F7631;
160 | imei = StringField() # 000000000000000;
161 | # ip = StringField()
162 | lang = StringField() # "en-CN";
163 | sc_w = IntField() # screen width
164 | sc_h = IntField() # screen height
165 | # mScreen = StringField() # 1242x2208;
166 | dType = StringField() # iPhone;
167 | mac = StringField() # "02:00:00:00";
168 | dModel = StringField() # "iPhone8,2";
169 | osType = StringField() # ios;
170 | # osVerInt =StringField()# "9.3.2";
171 | osVerRelease = StringField() # "9.3.2";
172 | route = StringField() # "24:3:b0";
173 | ssid = StringField() # MyWIFI;
174 | # utc = StringField() # "2016-07-18 06:51:53";
175 | uuid = StringField() # "513DE79D-F20AB2F685A";
176 |
177 | c_time = DateTimeField() # 客户端时间,东8时区
178 |
179 | # 下面是作为SDK时需要的字段
180 | # gsdkVerCode = "2.16.1.25.1.x";
181 | # hAppVerCode = 1;
182 | # hAppVerName = "1.0";
183 |
--------------------------------------------------------------------------------
/apps/auth2/handlers.py:
--------------------------------------------------------------------------------
1 | # 这个要移到第三方
2 | from asyncio import sleep
3 | from dtlib import jsontool
4 | from dtlib.aio.decos import my_async_jsonp
5 | from dtlib.randtool import get_uuid1_key, generate_rand_id
6 | from dtlib.timetool import convert_default_str_to_dt_time
7 | from dtlib.tornado.decos import token_required
8 | from dtlib.tornado.ttl_docs import WebToken
9 | from dtlib.utils import list_have_none_mem
10 | from dtlib.web.constcls import ConstData, QrAuthStatusCode
11 | from dtlib.web.tools import get_std_json_response
12 | from dtlib.web.valuedict import ClientTypeDict
13 |
14 | from apps.auth2.decos import async_auth_access_token_required
15 | from config_api import QR_AUTH_DOMAIN, BASE_DOMAIN, AUTH_CALLBACK_DOMAIN
16 | from dtlib.tornado.base_hanlder import MyUserBaseHandler
17 |
18 | # from xt_base.document.auth2_docs import ThirdAuthToken, MobileAuthPcToken, AuthAccessToken, \
19 | # MobileSafetyData, AuthApp
20 | # from xt_base.document.source_docs import AuthCallbackPage
21 |
22 | try:
23 | from xt_wechat.config import my_auth_app
24 | except:
25 | print("Wechat module didn't installed!")
26 |
27 |
28 | # TODO: 后面有需要重写相关功能
29 |
30 |
31 | class GetMapQrCodeToken(MyUserBaseHandler):
32 | """
33 | PC端将文本内容返回给客户端,由客户端生成二维码
34 |
35 | 客户端定时请求,每次请求都会导致code发生变化
36 | """
37 |
38 | @my_async_jsonp
39 | async def get(self):
40 | appid = self.get_argument('appid', None)
41 |
42 | if list_have_none_mem(*[appid]):
43 | return ConstData.msg_args_wrong
44 |
45 | # todo 后续要加一次数据库IO来查询是否存在此qrcode
46 | unauth_token = await self.create_anonymous_token(
47 | MobileAuthPcToken,
48 | appid=appid,
49 | client_type=ClientTypeDict.browser
50 | )
51 | """:type:MobileAuthPcToken"""
52 |
53 | # 这个链接是给手机打开的,用来给PC签名授权
54 | url = '%s/auth2/app-confirm/' % QR_AUTH_DOMAIN
55 |
56 | res_dict = dict(
57 | appid=appid,
58 | url=url,
59 | qrtoken=unauth_token.token
60 | )
61 | res_data = get_std_json_response(data=jsontool.dumps(res_dict))
62 | return res_data
63 |
64 |
65 | class AppConfirm(MyUserBaseHandler):
66 | """
67 | 手机端扫码后相应的状态提醒。
68 | 如果扫码了,则调出确认的页面,扫码时也改变状态值为410:
69 | 1. 如果确认或者取消后,则使用access_token和uuid去修改状态(注意检查资源从属问题)
70 | #. 如果确认登录后,就进行签名,而且修改状态值为200
71 | """
72 |
73 | @token_required()
74 | @my_async_jsonp
75 | async def get(self):
76 | uuid = self.get_argument('uuid', None)
77 | confirm = self.get_argument('confirm', None)
78 |
79 | if list_have_none_mem(*[uuid, ]):
80 | return ConstData.msg_args_wrong
81 |
82 | current_auth_token = await MobileAuthPcToken.objects.get(token=uuid)
83 | """:type:MobileAuthPcToken"""
84 |
85 | if current_auth_token is None:
86 | return ConstData.msg_forbidden
87 |
88 | user = self.get_current_session_user() # 当前手机登录的用户
89 |
90 | if confirm is None:
91 | current_auth_token.status = QrAuthStatusCode.wait
92 | elif confirm == '1':
93 | # 将状态修改为 确认授权态,授权,用户签名
94 | current_auth_token.user = user
95 | current_auth_token.user_id = user.user_id
96 | current_auth_token.u_name = user.nickname
97 | current_auth_token.status = QrAuthStatusCode.confirm
98 |
99 | # 然后创建auth_token,这个会告诉PC端此用户已经登录,然后把此session传给PC端
100 | # 直接创建webtoken,暂先不客auth_token
101 | await self.create_token_session(user,
102 | WebToken,
103 | client_type=ClientTypeDict.browser
104 | ) # 创建一个PC端的access_token
105 | else:
106 | # 将状态修改为 取消授权态
107 | current_auth_token.status = QrAuthStatusCode.cancel
108 |
109 | current_auth_token = await current_auth_token.save()
110 |
111 | return get_std_json_response(data=jsontool.dumps(current_auth_token.to_dict()))
112 |
113 |
114 | # region 以下就是PC端后续的调用接口
115 |
116 | class GetAuthAccessToken(MyUserBaseHandler):
117 | """
118 | auth2.0里面的获取认证信息,因为二维码信息容易被获取,所以中间再加一个auth_token
119 | :需要私钥(影子)。此时是可信的服务器作为通讯双方
120 | """
121 |
122 | @my_async_jsonp
123 | async def get(self):
124 | appid = self.get_argument('appid', None) # 应用公钥
125 | shadow_secret = self.get_argument('shadow_secret', None) # 影子私钥
126 | auth_token = self.get_argument('auth_token', None) # 用户授权token
127 |
128 | if list_have_none_mem(*[appid, shadow_secret, auth_token]):
129 | return ConstData.msg_args_wrong
130 |
131 | auth_token = await ThirdAuthToken.objects.get(token=auth_token)
132 | """:type:ThirdAuthToken"""
133 | if auth_token is None:
134 | return ConstData.msg_none
135 |
136 | access_token = await AuthAccessToken.objects.get(user=auth_token.user)
137 | """:type:AuthAccessToken"""
138 |
139 | res_data = get_std_json_response(data=jsontool.dumps(access_token.to_dict()))
140 | return res_data
141 |
142 |
143 | class GetAuthUserInfo(MyUserBaseHandler):
144 | """
145 | 另外的一套独立的token体系,后面会独立出来
146 | """
147 |
148 | @async_auth_access_token_required
149 | async def get(self):
150 | """
151 | 返回信息里面有用户针对此应用的unionid信息,方便第三方应用系统A做好账号映射工作
152 | :return:
153 | """
154 | await sleep()
155 |
156 |
157 | class UploadMobileSafetyData(MyUserBaseHandler):
158 | """
159 | 获取手机风控相关数据
160 | """
161 |
162 | @token_required()
163 | @my_async_jsonp
164 | async def post(self):
165 | mobile_safety_data = self.get_post_body_dict()
166 |
167 | if mobile_safety_data is None:
168 | return ConstData.msg_args_wrong
169 |
170 | print(jsontool.dumps(mobile_safety_data))
171 |
172 | # operation_key = mobile_safety_data.get('op', None) # operation 桩,整数:0,1,2,3
173 | mobile_info = mobile_safety_data.get('mi', None) # 收集的手机静态信息,mobile info
174 | apps_info = mobile_safety_data.get('ai', None) # 安装的应用信息, apps info
175 | client_time_str = mobile_safety_data.get('tm', None) # time
176 | # todo 后面存储应用的数据信息
177 |
178 | # operation_dict = OperationDict.get_value_by_attrib_name(operation_key)
179 |
180 | if list_have_none_mem(*[mobile_info, apps_info, client_time_str,
181 | # operation_dict
182 | ]):
183 | return ConstData.msg_args_wrong
184 |
185 | ms_data = MobileSafetyData()
186 | ms_data.bs = mobile_info.get('bs', None)
187 |
188 | # 电池电量
189 | battery_rate = mobile_info.get('br', None)
190 | if battery_rate is not None:
191 | ms_data.br = float(battery_rate)
192 |
193 | ms_data.carrier = mobile_info.get('carrier', None)
194 | ms_data.cellular = mobile_info.get('cellular', None)
195 | ms_data.coun = mobile_info.get('coun', None)
196 | ms_data.dn = mobile_info.get('dn', None)
197 | ms_data.idf = mobile_info.get('idf', None)
198 | ms_data.imei = mobile_info.get('imei', None)
199 | ms_data.lang = mobile_info.get('lang', None)
200 |
201 | # 屏幕分辨率
202 | screen_width = mobile_info.get('dw', None)
203 | screen_height = mobile_info.get('dh', None)
204 | if not list_have_none_mem(*[screen_height, screen_width]):
205 | ms_data.sc_h = int(screen_height)
206 | ms_data.sc_w = int(screen_width)
207 |
208 | ms_data.dType = mobile_info.get('dType', None)
209 | ms_data.mac = mobile_info.get('mac', None)
210 | ms_data.dModel = mobile_info.get('dModel', None)
211 | ms_data.osType = mobile_info.get('osType', None)
212 | ms_data.osVerRelease = mobile_info.get('osVerRelease', None)
213 | ms_data.route = mobile_info.get('route', None)
214 | ms_data.ssid = mobile_info.get('ssid', None)
215 | ms_data.uuid = mobile_info.get('uuid', None)
216 |
217 | client_time = convert_default_str_to_dt_time(client_time_str)
218 | ms_data.c_time = client_time
219 |
220 | # 设置组织和人员归属关系
221 | await ms_data.set_org_user_tag(http_req=self)
222 | ms_data.set_http_tag(http_req=self)
223 | # 设置操作类型
224 | # ms_data.set_operation_tag(operation_dict)
225 |
226 | await ms_data.save()
227 |
228 | return ConstData.msg_succeed
229 |
230 |
231 | class CreateAuthApp(MyUserBaseHandler):
232 | """
233 | 建议auth应用
234 | """
235 |
236 | @token_required()
237 | @my_async_jsonp
238 | async def get(self):
239 | client_name = self.get_argument('client', None)
240 | if client_name is None:
241 | return ConstData.msg_args_wrong
242 |
243 | client_type = ClientTypeDict.get_value_by_attrib_name(client_name)
244 | if client_type is None:
245 | return ConstData.msg_args_wrong
246 |
247 | auth_app = AuthApp()
248 |
249 | uuid_key = get_uuid1_key()
250 |
251 | auth_app.appid = uuid_key
252 | auth_app.secret = generate_rand_id(sstr=uuid_key)
253 | auth_app.callback = BASE_DOMAIN
254 |
255 | await auth_app.set_default_tag(client_type=client_type, http_req=self)
256 | await auth_app.save()
257 |
258 | return ConstData.msg_succeed
259 |
260 |
261 | # endregion
262 |
263 |
264 | class CreateAuthCallbackPage(MyUserBaseHandler):
265 | """
266 | 创建page
267 | """
268 |
269 | @token_required()
270 | @my_async_jsonp
271 | async def get(self):
272 | auth_cb_page = AuthCallbackPage()
273 | auth_cb_page.url = AUTH_CALLBACK_DOMAIN
274 | await auth_cb_page.set_org_user_tag(http_req=self)
275 | auth_cb_page.set_template()
276 | auth_cb_page.set_default_rc_tag()
277 | await auth_cb_page.save()
278 | return ConstData.msg_succeed
279 |
--------------------------------------------------------------------------------
/apps/auth2/urls.py:
--------------------------------------------------------------------------------
1 | """
2 | 这里面有类似于微信客户端扫码登录的全套解决方案。但是也有区别:
3 |
4 | 1. 本方法不是使用重定向,而是使用回调函数。所以不定型是登录,还可以用作为签名等场合。
5 |
6 | """
7 | # from apps.auth2.handlers import *
8 | # from dtlib import filetool
9 | #
10 | # app_path = filetool.get_parent_folder_name(__file__) # set the relative path in one place
11 | #
12 | # url = [
13 | #
14 | # # PC端的授权页面
15 | # (r"/%s/get-map-qrcode-token/" % app_path, GetMapQrCodeToken), # PC上生成串,然后由客户端js生成qrcode,客户端生成二维码js只有10K
16 | #
17 | # # 根据二维码授权结果,以字符形式的auth_token再换取第三方的access_token,
18 | # (r"/%s/get-auth-access-token/" % app_path, GetAuthAccessToken),
19 | #
20 | # # 移动授权app客户端,手机端在扫码后,打开二维码所在页面
21 | # (r"/%s/app-confirm/" % app_path, AppConfirm), # 这是二维码中的具体文字内容,手机端的确认
22 | #
23 | # # 网站主服务器
24 | # (r"/%s/get-auth-user-info" % app_path, GetAuthUserInfo),
25 | #
26 | # # todo 手机端注册
27 | # # . 输入邮箱,再轮询查看认证状态。
28 | # # . 输入手机号码,再发送验证码
29 | # # . 优先:直接绑定微信
30 | #
31 | # # 创建开发平台auth应用
32 | # (r"/%s/create-auth-app/" % app_path, CreateAuthApp),
33 | #
34 | # # 移动端数据安全
35 | # (r"/%s/up-ms-data/" % app_path, UploadMobileSafetyData), # 获取手机风控数据,在登录的时候和手机放一起
36 | #
37 | # # pc上面生成二维码,给手机扫描,由已经认证了的PC给手机进行授权--(这里面的功能给废弃掉)
38 | # # (r"/%s/get-pam-token-qrcode/" % app_path, GetPamTokenQrCode), # PC上生成的二维码图片(1分钟刷新一次),让手机扫描
39 | # # (r"/%s/get-mobile-token/" % app_path, GetMobileToken), # 由手机发起的请求,获取和刷新access_token
40 | #
41 | # # 由未授权PC上生成二维码,然后授权之后的手机扫码
42 | # # (r"/%s/get-map-token-qrcode/" % app_path, GetMapTokenQrCode), # PC上生成的二维码图片(1分钟刷新一次),让手机扫描(此种在服务端生成二维码的方案废弃掉)
43 | # # (r"/%s/pc-get-auth-result/" % app_path, PcGetAuthResult), # PC端轮询获取授权的结果
44 | #
45 | # # todo auth callback page的 CRUD
46 | # (r"/%s/create-auth-cb-page/" % app_path, CreateAuthCallbackPage),
47 | # # (r"/%s/get-auth-cb-page/" % app_path, GetAuthCallbackPage),
48 | # ]
49 |
50 | url = []
51 | # 暂时注释掉, 似乎没用到 (2018-04-23)
52 |
--------------------------------------------------------------------------------
/apps/base/__init__.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 |
3 | """
4 | the common base handlers
5 | """
6 | __author__ = 'zheng'
7 |
--------------------------------------------------------------------------------
/apps/base/handlers.py:
--------------------------------------------------------------------------------
1 | import dtlib
2 | from dtlib import jsontool
3 | from dtlib import timetool
4 | from dtlib.mongo.utils import get_mongodb_version, get_pip_list, get_python_version
5 | from dtlib.tornado.decos import test_token_required, api_counts
6 | from dtlib.web.decos import deco_jsonp
7 | from dtlib.web.tools import get_std_json_response
8 | from tornado.web import RequestHandler
9 |
10 | from config import app_version, test_token
11 | from dtlib.tornado.base_hanlder import MyUserBaseHandler
12 |
13 |
14 | class MainHandler(RequestHandler):
15 | """
16 | 本接口的首页信息
17 | """
18 |
19 | def get(self):
20 | self.write('This the Home Page')
21 |
22 |
23 | class AppInfo(MyUserBaseHandler):
24 | """
25 | 获取本web应用的基本信息
26 | """
27 |
28 | @api_counts()
29 | @deco_jsonp(is_async=False)
30 | def get(self):
31 | print('run app-info:', app_version)
32 | res = dict(
33 | server='tornado', # web服务器
34 | app_version=app_version, # 应用版本
35 | dtlib_version=dtlib.VERSION, # 第三方库dt-lib的版本
36 | req_time=timetool.get_current_time_string(), # 当前查询时间点
37 | timezone=timetool.get_time_zone(), # 当前服务器的时区
38 | )
39 | return get_std_json_response(data=jsontool.dumps(res, ensure_ascii=False))
40 |
41 |
42 | class PrivateAppInfo(MyUserBaseHandler):
43 | """
44 | 获取本web应用的基本信息,这些需要有管理员的test_key才能访问
45 | """
46 |
47 | @test_token_required(test_token=test_token)
48 | @deco_jsonp(is_async=False)
49 | def get(self):
50 | print('run app-info:', app_version)
51 |
52 | mongo_version = get_mongodb_version()
53 | pip_list = get_pip_list()
54 | python = get_python_version()
55 |
56 | res = dict(
57 | server='tornado', # web服务器
58 | app_version=app_version, # 应用版本
59 | dtlib_version=dtlib.VERSION, # 第三方库dt-lib的版本
60 | req_time=timetool.get_current_time_string(), # 当前查询时间点
61 | timezone=timetool.get_time_zone(), # 当前服务器的时区
62 |
63 | mongo_server=mongo_version, # mongo的服务器版本号
64 | python=python, # python版本号
65 | pip_list=pip_list, # 安装的pip_list
66 | )
67 | return get_std_json_response(data=jsontool.dumps(res, ensure_ascii=False))
68 |
69 |
70 | class GetClientInfo(MyUserBaseHandler):
71 | @deco_jsonp(is_async=False)
72 | def get(self):
73 | """
74 | 返回客户端的公网IP地址及端口
75 | :return:
76 | """
77 |
78 | client_req = self.request
79 |
80 | res_dict = dict(
81 | remote_ip=client_req.remote_ip,
82 | method=client_req.method,
83 | path=client_req.path,
84 | headers=client_req.headers._dict
85 |
86 | )
87 |
88 | return get_std_json_response(data=jsontool.dumps(res_dict))
89 |
90 |
91 | class CreateServerAsset(MyUserBaseHandler):
92 | """
93 | 创建服务器资产
94 | """
95 |
96 | async def post(self):
97 | return 'hello'
98 |
--------------------------------------------------------------------------------
/apps/base/urls.py:
--------------------------------------------------------------------------------
1 | """
2 | 任何一个Web系统里面都需要的一些提供系统信息的工具集
3 | """
4 | from apps.base.handlers import *
5 | from dtlib import filetool
6 |
7 | app_path = filetool.get_parent_folder_name(__file__) # set the relative path in one place
8 |
9 | url = [
10 | # 公用
11 | (r"/", MainHandler),
12 | (r"/app-info/", AppInfo),
13 | (r"/private-app-info/", PrivateAppInfo), # 一些隐私一些的应用信息
14 |
15 | (r"/get-client-info/", GetClientInfo), # 获取客户端请求信息,包括IP地址
16 | (r"/create-server-asset/", CreateServerAsset), # 创建信息服务器资产
17 | ]
18 |
--------------------------------------------------------------------------------
/apps/dashboard/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x-utest/xtest-server/4d7c05266dd42ebf4a1ddc416eb615410b40b5fb/apps/dashboard/__init__.py
--------------------------------------------------------------------------------
/apps/dashboard/handlers.py:
--------------------------------------------------------------------------------
1 | # from dtlib.web.tools import get_std_json_response
2 | # from dtlib import jsontool
3 | # from bson import ObjectId
4 | # from pymongo import DESCENDING
5 | # from functools import reduce
6 | import time
7 | from pymongo import DESCENDING
8 | from bson import ObjectId
9 | from dtlib import jsontool
10 | from dtlib.web.decos import deco_jsonp
11 | from dtlib.web.constcls import ConstData, ResCode
12 | from dtlib.web.tools import get_std_json_response
13 | from dtlib.utils import list_have_none_mem
14 | from dtlib.aio.base_mongo import wrap_default_rc_tag
15 | from dtlib.tornado.decos import token_required
16 | from dtlib.tornado.base_hanlder import MyUserBaseHandler
17 |
18 | import json
19 |
20 |
21 | class UpdateContent(MyUserBaseHandler):
22 | """
23 | 更新文本内容
24 | """
25 |
26 | def __init__(self, *args, **kwargs):
27 | super(UpdateContent, self).__init__(*args, **kwargs)
28 |
29 | @token_required()
30 | @deco_jsonp()
31 | async def post(self, *args, **kwargs):
32 | post_json = self.get_post_body_dict()
33 | content_id = post_json.get('content_id', None)
34 | pro_id = post_json.get('pro_id', "5a7fb0cd47de9d5cf3d13a44")
35 | group = post_json.get('group', "test")
36 | time_stamp = post_json.get('date_time', None)
37 | content = post_json.get('content', None)
38 |
39 | if list_have_none_mem(*[pro_id, group, time_stamp, content]):
40 | return ConstData.msg_args_wrong
41 | mongo_conn = self.get_async_mongo()
42 | mycol = mongo_conn['dashboard_content']
43 | timeArray = time.localtime(int(time_stamp) / 1000.0)
44 | date_time = time.strftime("%Y-%m-%d %H:%M:%S", timeArray)
45 | data = dict(
46 | pro_id=ObjectId(pro_id),
47 | group=group,
48 | date_time=date_time,
49 | content=content,
50 | )
51 | data = wrap_default_rc_tag(data) # 加上默认的标签
52 | if content_id:
53 | await mycol.update({'_id': ObjectId(content_id)}, {'$set': data}, upsert=True)
54 | return ConstData.msg_succeed
55 | else:
56 | _id = await mycol.insert(data)
57 | msg_succeed = '{"code":%s,"msg":"%s","data":{"_id": "%s"}}' % (ResCode.ok, "success", _id)
58 | return msg_succeed
59 |
60 |
61 | class DeleteContent(MyUserBaseHandler):
62 | """
63 | 删除文本内容
64 | """
65 |
66 | def __init__(self, *args, **kwargs):
67 | super(DeleteContent, self).__init__(*args, **kwargs)
68 |
69 | @token_required()
70 | @deco_jsonp()
71 | async def post(self, *args, **kwargs):
72 | post_json = self.get_post_body_dict()
73 | content_id = post_json.get('content_id', None)
74 | # pro_id = self.get_argument('pro_id', "5a7fb0cd47de9d5cf3d13a44")
75 | if list_have_none_mem(*[content_id]):
76 | return ConstData.msg_args_wrong
77 | mongo_conn = self.get_async_mongo()
78 | mycol = mongo_conn['dashboard_content']
79 | mycol.update({'_id': ObjectId(content_id)}, {'$set': {'is_del': True}}, upsert=False)
80 | return ConstData.msg_succeed
81 |
82 |
83 | class GetContent(MyUserBaseHandler):
84 | def __init__(self, *args, **kwargs):
85 | super(GetContent, self).__init__(*args, **kwargs)
86 |
87 | @token_required()
88 | @deco_jsonp()
89 | async def get(self, *args, **kwargs):
90 | hide_fields = {
91 | 'rc_time': 0,
92 | 'del_time': 0,
93 | 'is_del': 0,
94 | 'pro_id': 0
95 | }
96 | return await self.get_content_list(col_name='dashboard_content', hide_fields=hide_fields)
97 |
98 | async def get_content_list(self, *args, **kwargs):
99 | """
100 | 直接传入项目名称和表的名称,返回分页的信息
101 | :param self:
102 | :param args:
103 | :param kwargs:col_name
104 | :return:
105 | """
106 | page_cap = self.get_argument('page_cap', '10') # 一页的记录容量-var
107 | pro_id = self.get_argument('pro_id', None)
108 |
109 | col_name = kwargs.get('col_name', None)
110 | hide_fields = kwargs.get('hide_fields', None) # 需要隐藏的字段,一个dict
111 | """:type:dict"""
112 |
113 | if list_have_none_mem(*[col_name, pro_id, page_cap]):
114 | return ConstData.msg_args_wrong
115 | page_cap = int(page_cap)
116 | mongo_conn = self.get_async_mongo()
117 | mycol = mongo_conn[col_name]
118 |
119 | if pro_id is None:
120 | msg_details = mycol.find({
121 | "is_del": False
122 | }, hide_fields).sort([('date_time', DESCENDING)]) # 升序排列
123 | else:
124 | msg_details = mycol.find({
125 | 'pro_id': ObjectId(pro_id),
126 | "is_del": False
127 | }, hide_fields).sort([('date_time', DESCENDING)]) # 升序排列
128 |
129 | msg_details_cnt = await msg_details.count()
130 | page_size = page_cap if page_cap < msg_details_cnt else msg_details_cnt
131 | msg_content_list = await msg_details.to_list(page_size)
132 | return get_std_json_response(data=jsontool.dumps(msg_content_list))
133 |
134 |
135 | class UpdateProjectShow(MyUserBaseHandler):
136 | """
137 | 更新项目是否展示的状态
138 | """
139 |
140 | def __init__(self, *args, **kwargs):
141 | super(UpdateProjectShow, self).__init__(*args, **kwargs)
142 |
143 | @deco_jsonp()
144 | async def post(self, *args, **kwargs):
145 | post_json = self.get_post_body_dict()
146 | tv_tags = post_json.get('tv_tags', None)
147 | pro_id = post_json.get('pro_id', None)
148 |
149 | if list_have_none_mem(*[tv_tags, pro_id]):
150 | return ConstData.msg_args_wrong
151 | if not isinstance(tv_tags, list):
152 | return ConstData.msg_args_wrong
153 | mongo_conn = self.get_async_mongo()
154 | mycol = mongo_conn['test_project']
155 | project = await mycol.find_one({'_id': ObjectId(pro_id)}, {'tags': 1})
156 | if 'tags' in project:
157 | tags = project['tags']
158 | if set(tv_tags).issubset(set(tags)) is False:
159 | return ConstData.msg_args_wrong
160 | await mycol.update({'_id': ObjectId(pro_id)}, {'$set': {'tv_tags': tv_tags}}, upsert=False)
161 | return ConstData.msg_succeed
162 |
--------------------------------------------------------------------------------
/apps/dashboard/urls.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 | """
3 | websocket相关接口
4 | """
5 |
6 | from apps.dashboard.handlers import UpdateContent, GetContent, DeleteContent, UpdateProjectShow
7 | from dtlib import filetool
8 |
9 | app_path = filetool.get_parent_folder_name(__file__)
10 |
11 | url = [
12 | (r"/%s/update_content/" % app_path, UpdateContent), # 更新项目信息
13 | (r"/%s/delete_content/" % app_path, DeleteContent), # 删除项目信息
14 | (r"/%s/get_content/" % app_path, GetContent), # 获取项目信息
15 | (r"/%s/update_project_show/" % app_path, UpdateProjectShow), # 更新展示状态
16 | ]
17 |
--------------------------------------------------------------------------------
/apps/project/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x-utest/xtest-server/4d7c05266dd42ebf4a1ddc416eb615410b40b5fb/apps/project/__init__.py
--------------------------------------------------------------------------------
/apps/project/handlers.py:
--------------------------------------------------------------------------------
1 | from dtlib.tornado.base_hanlder import MyUserBaseHandler
2 | from bson import ObjectId
3 | from dtlib import jsontool
4 | from dtlib.aio.decos import my_async_jsonp, my_async_paginator
5 | from dtlib.tornado.decos import token_required
6 | from dtlib.utils import list_have_none_mem
7 | from dtlib.web.constcls import ConstData, ResCode
8 | from dtlib.web.tools import get_std_json_response
9 |
10 | from dtlib.tornado.utils import get_org_data
11 | from dtlib.tornado.utils import get_org_data_paginator
12 |
13 | from dtlib.web.decos import deco_jsonp
14 | from dtlib.tornado.utils import set_default_rc_tag
15 | from apps.admin.decos import admin_required
16 |
17 |
18 | class ListProjectsNote(MyUserBaseHandler):
19 | """
20 | 根据当前组织得到appid,key
21 | """
22 |
23 | @token_required()
24 | @my_async_jsonp
25 | async def get(self):
26 | project_id = self.get_argument('project_id', None)
27 | if project_id is None:
28 | return ConstData.msg_args_wrong
29 | db = self.get_async_mongo()
30 | proj_col = db.test_project
31 | project_obj = await proj_col.find_one({'_id': ObjectId(str(project_id))})
32 | organization = project_obj['organization']
33 | app_col = db.test_data_app
34 | test_data_app = await app_col.find_one({'organization': organization})
35 | data = dict(
36 | project_name=project_obj['project_name'],
37 | project_id=project_obj['_id'],
38 | app_id=test_data_app['app_id'],
39 | app_key=test_data_app['app_key']
40 | )
41 | res_str = get_std_json_response(code=200, data=jsontool.dumps(data))
42 |
43 | return res_str
44 |
45 |
46 | class CreateTestProject(MyUserBaseHandler):
47 | """
48 | 项目名称
49 | """
50 |
51 | @token_required()
52 | @my_async_jsonp
53 | async def post(self):
54 | post_json = self.get_post_body_dict()
55 |
56 | mark = post_json.get('mark', None)
57 | project_name = post_json.get('name', None)
58 |
59 | organization = await self.get_organization()
60 | user = self.get_current_session_user()
61 |
62 | db = self.get_async_mongo()
63 | proj_col = db.test_project
64 | org_col = db.organization
65 | org_res = await org_col.find_one({'_id': ObjectId(organization)})
66 | org_name = org_res['name']
67 | user_col = db.g_users
68 | user_res = await user_col.find_one({'_id': ObjectId(user)})
69 | user_nickname = user_res['nickname']
70 | new_data = dict(
71 | project_name=project_name,
72 | mark=mark,
73 | organization=organization,
74 | org_name=org_name,
75 | owner=user,
76 | owner_name=user_nickname,
77 | )
78 | new_data = set_default_rc_tag(new_data)
79 |
80 | result = await proj_col.insert(new_data)
81 |
82 | res_str = get_std_json_response(code=200, data=jsontool.dumps(result))
83 |
84 | return res_str
85 |
86 |
87 | class UpdateTestProject(MyUserBaseHandler):
88 | """
89 | 更新project的name
90 | """
91 |
92 | @admin_required()
93 | @my_async_jsonp
94 | async def post(self):
95 | post_json = self.get_post_body_dict()
96 |
97 | id = post_json.get('id', None)
98 | project_name = post_json.get('name', None)
99 | mark = post_json.get('mark', None)
100 |
101 | if list_have_none_mem(*[id, project_name, mark]):
102 | return ConstData.msg_args_wrong
103 |
104 | # todo 做资源归属和权限的判断
105 | db = self.get_async_mongo()
106 | proj_col = db.test_project
107 | project_obj = await proj_col.find_one({'_id': ObjectId(str(id))})
108 | user_org = await self.get_organization()
109 |
110 | organization = project_obj['organization']
111 | if (project_obj is None) or (organization is None):
112 | return ConstData.msg_forbidden
113 |
114 | pro_org_id = organization
115 | if pro_org_id != user_org:
116 | return ConstData.msg_forbidden
117 |
118 | data = dict(
119 | project_name=project_name,
120 | mark=mark
121 | )
122 | result = await proj_col.update({'_id': ObjectId(str(id))}, {'$set': data}, upsert=False)
123 | res_str = get_std_json_response(code=200, data=jsontool.dumps(result))
124 |
125 | return res_str
126 |
127 |
128 | class DeleteTestProject(MyUserBaseHandler):
129 | """
130 | 删除project(实际是将is_del设置为true)
131 | """
132 |
133 | @admin_required()
134 | @my_async_jsonp
135 | async def get(self):
136 | id = self.get_argument('id', None)
137 | if id is None:
138 | return ConstData.msg_args_wrong
139 |
140 | # todo 做资源归属和权限的判断
141 | db = self.get_async_mongo()
142 | proj_col = db.test_project
143 | test_data_col = db.unit_test_data
144 | project_obj = await proj_col.find_one({'_id': ObjectId(str(id))})
145 | user_org = await self.get_organization()
146 |
147 | if project_obj is None:
148 | return ConstData.msg_forbidden
149 |
150 | pro_org_id = project_obj['organization']
151 | if pro_org_id != user_org:
152 | return ConstData.msg_forbidden
153 |
154 | await proj_col.update({'_id': ObjectId(str(id))}, {'$set': {'is_del': True}}, upsert=False)
155 | # 删除附属于项目的测试结果
156 | await test_data_col.update({'pro_id': ObjectId(str(id))}, {'$set': {'is_del': True}}, multi=True)
157 | return ConstData.msg_succeed
158 |
159 |
160 | class ReadProjectsRecord(MyUserBaseHandler):
161 | """
162 | 根据project—id查找30次的构建情况
163 | """
164 |
165 | @token_required()
166 | @my_async_jsonp
167 | async def get(self):
168 | id = self.get_argument('id', None)
169 | tag = self.get_argument('tag')
170 | if id is None:
171 | return ConstData.msg_args_wrong
172 |
173 | # todo 做资源归属和权限的判断
174 | db = self.get_async_mongo()
175 | proj_col = db.test_project
176 | project_obj = await proj_col.find_one({'_id': ObjectId(id)})
177 | user_org = await self.get_organization()
178 |
179 | if project_obj is None:
180 | return ConstData.msg_forbidden
181 |
182 | pro_org_id = project_obj['organization']
183 | if pro_org_id != user_org:
184 | return ConstData.msg_forbidden
185 |
186 | return await get_org_data_paginator(self, col_name='unit_test_data', pro_id=id, tag=[tag],
187 | hide_fields={'details': 0})
188 |
189 |
190 | class ListProjects(MyUserBaseHandler):
191 | """
192 | 本组织的所有的项目列出来
193 | """
194 |
195 | @token_required()
196 | @my_async_jsonp
197 | @my_async_paginator
198 | async def get(self):
199 | return await get_org_data(self, collection='test_project')
200 |
201 |
202 | class GetProjectTags(MyUserBaseHandler):
203 | """
204 | 获取某个项目的所有tag
205 | """
206 |
207 | # @my_async_jsonp
208 | @my_async_jsonp
209 | async def get(self):
210 | pro_id = self.get_argument('pro_id', None)
211 | if pro_id is None:
212 | return ConstData.msg_args_wrong
213 | db = self.get_async_mongo()
214 | pro_col = db.test_project
215 | tags = await pro_col.find_one({'_id': ObjectId(pro_id)}, {'tags': 1})
216 | # test_data_col = db.unit_test_data
217 | # tags = await test_data_col.distinct("tag",
218 | # {"pro_id": ObjectId(pro_id), 'is_del': False, 'tag': {'$exists': True}})
219 | tag_list = list()
220 | if 'tags' in tags.keys():
221 | tag_list = tags['tags']
222 | msg_succeed = '{"code":%s,"msg":"success","data":%s}' % (ResCode.ok, jsontool.dumps(tag_list)) # 操作成功
223 | return msg_succeed
224 |
--------------------------------------------------------------------------------
/apps/project/urls.py:
--------------------------------------------------------------------------------
1 | from apps.project.handlers import *
2 | from dtlib import filetool
3 |
4 | app_path = filetool.get_parent_folder_name(__file__) # set the relative path in one place
5 |
6 | url = [
7 |
8 | (r"/%s/create-test-project/" % app_path, CreateTestProject), # 创建项目
9 | (r"/%s/update-test-project/" % app_path, UpdateTestProject), # 更新项目
10 | (r"/%s/delete-test-project/" % app_path, DeleteTestProject), # 删除项目
11 | (r"/%s/list-projects/" % app_path, ListProjects), # 本组织的所有项目
12 |
13 | (r"/%s/xtest-client-config/" % app_path, ListProjectsNote), # 根据project——id查找app-id
14 |
15 | (r"/%s/read-projects-record/" % app_path, ReadProjectsRecord), # 根据project—id查找30次的构建情况
16 | (r"/%s/get-tags/" % app_path, GetProjectTags), # 根据project—id查找30次的构建情况
17 |
18 | ]
19 |
--------------------------------------------------------------------------------
/apps/share/__init__.py:
--------------------------------------------------------------------------------
1 | # 一些带传播性的功能放这里面
2 |
--------------------------------------------------------------------------------
/apps/share/docs.py:
--------------------------------------------------------------------------------
1 | """
2 | 分享link
3 | """
4 | # from aiomotorengine import StringField, IntField
5 | # from dtlib.tornado.base_docs import OrgUserDataDocument
6 | #
7 | # from xt_base.document.base_docs import ProjectBaseDocument
8 |
9 |
10 | # todo 后面都要加上项目的名称的冗余
11 | # TODO: 后面有需要重写相关功能
12 |
13 | class ShareTestReport(OrgUserDataDocument, ProjectBaseDocument):
14 | """
15 | 分享测试报告,只有拥有此share的ID流水号的,就可以访问此报告信息,
16 | 永久性的
17 | """
18 |
19 | __collection__ = 'share_test_report'
20 | rep_id = StringField() # 报告的流水号
21 | stoken = StringField() # 访问此报告时所需要的token串号,share token
22 | cnt = IntField() # 被访问的次数
23 | mark = StringField() # 关于此分享的备注信息
24 | # project = ReferenceField(reference_document_type=Project) # 测试数据所属的项目
25 | # project_name = StringField() # 项目名称,冗余
26 |
27 |
28 |
29 | class ShareProjectReport(OrgUserDataDocument, ProjectBaseDocument):
30 | """
31 | 分享测试项目报告,只有拥有此share的ID流水号的,就可以访问此报告信息,
32 | 永久性的
33 | """
34 | __collection__ = 'share_project_report'
35 | pro_id = StringField() # 测试项目id
36 | stoken = StringField() # 访问此报告时所需要的token串号,share token
37 | cnt = IntField() # 被访问的次数
38 | mark = StringField() # 关于此分享的备注信息
39 | # project = ReferenceField(reference_document_type=Project) # 测试数据所属的项目
40 | # project_name = StringField() # 项目名称,冗余
--------------------------------------------------------------------------------
/apps/share/handlers.py:
--------------------------------------------------------------------------------
1 | import math
2 |
3 | from pymongo import DESCENDING
4 | from dtlib.tornado.base_hanlder import MyUserBaseHandler
5 | from bson import ObjectId
6 | from dtlib import jsontool
7 | from dtlib.randtool import generate_uuid_token
8 | from dtlib.tornado.decos import token_required
9 | from dtlib.utils import list_have_none_mem
10 | from dtlib.web.constcls import ConstData
11 | from dtlib.web.decos import deco_jsonp
12 | from dtlib.web.tools import get_std_json_response
13 |
14 | from dtlib.tornado.utils import get_org_data_paginator
15 |
16 | from dtlib.tornado.utils import set_default_rc_tag
17 |
18 | share_page = '/utest-report-share.html?stoken=' # 单独部署的一套前端内容
19 |
20 | pro_share_page = '/pro-report-share.html?stoken=' # 单独部署的一套前端内容
21 |
22 |
23 | class GetUtestShareLink(MyUserBaseHandler):
24 | """
25 | 获取分享链接
26 | """
27 |
28 | @token_required()
29 | @deco_jsonp()
30 | async def get(self):
31 | """
32 | 1. 自己当前的组织下的资源
33 | 2. 生成share的内容
34 |
35 | 支持手机端的
36 | :return:
37 | """
38 |
39 | rep_id = self.get_argument('rep_id', None) # 测试报告的ID
40 |
41 | if list_have_none_mem(*[rep_id]):
42 | return ConstData.msg_args_wrong
43 |
44 | db = self.get_async_mongo()
45 | share_col = db.share_test_report
46 | utest_col = db.unit_test_data
47 | proj_col = db.test_project
48 |
49 | # todo 做资源归属和权限的判断
50 | test_data = await utest_col.find_one({'_id': ObjectId(rep_id)})
51 | project = await proj_col.find_one({'_id': test_data['pro_id']})
52 |
53 | # if (project is None) or (project.organization is None):
54 | if project is None:
55 | return ConstData.msg_forbidden
56 |
57 | user_org = await self.get_organization()
58 | pro_org_id = project['organization']
59 | if pro_org_id != user_org:
60 | return ConstData.msg_forbidden
61 |
62 | share_obj = await share_col.find_one({'rep_id': rep_id})
63 |
64 | if share_obj is None: # 如果不存在分享Link则创建一组
65 | stoken = generate_uuid_token() # 生成一组随机串,分享的时候会看到
66 |
67 | share_data = dict(
68 | rep_id=rep_id,
69 | stoken=stoken,
70 | cnt=0,
71 | is_del=False,
72 | project=project['_id'],
73 | p_name=project['project_name'],
74 | owner=project['owner'],
75 | owner_name=project['owner_name'],
76 | organization=project['organization'],
77 | org_name=project['org_name']
78 | )
79 | # await share_obj.set_project_tag(project)
80 | # await share_obj.set_org_user_tag(http_req=self)
81 | share_data = set_default_rc_tag(share_data)
82 | await share_col.insert(share_data)
83 | share_url = share_page + stoken
84 | else:
85 | # 如果有,则直接使用
86 | share_url = share_page + share_obj['stoken']
87 |
88 | res_dict = dict(
89 | share_url=share_url
90 | )
91 |
92 | return get_std_json_response(data=jsontool.dumps(res_dict))
93 |
94 |
95 | class GetUtestShareData(MyUserBaseHandler):
96 | """
97 | 获取分享的单元测试数据接口
98 | """
99 |
100 | @deco_jsonp()
101 | async def get(self):
102 | stoken = self.get_argument('stoken', None)
103 |
104 | if list_have_none_mem(*[stoken]):
105 | return ConstData.msg_args_wrong
106 |
107 | db = self.get_async_mongo()
108 | share_col = db.share_test_report
109 | utest_col = db['unit_test_data']
110 | share_obj = await share_col.find_one({'stoken': stoken})
111 |
112 | if share_obj is None:
113 | return ConstData.msg_forbidden
114 |
115 | # todo 把完整的Unittest的报告内容获取出来并返回
116 |
117 | msg_details = utest_col.find({"_id": ObjectId(str(share_obj['rep_id']))})
118 | msg_content_list = await msg_details.to_list(1)
119 |
120 | msg_content = msg_content_list[0]
121 |
122 | # 做一个阅读访问次数的计数
123 | cnt = share_obj['cnt']
124 | if cnt is None:
125 | cnt = 0
126 | cnt += 1
127 | await share_col.update({'stoken': stoken}, {'$set':{'cnt': cnt}})
128 |
129 | return get_std_json_response(data=jsontool.dumps(msg_content, ensure_ascii=False))
130 |
131 |
132 | class GetProjectShareLink(MyUserBaseHandler):
133 | """
134 | 获取项目成长分享链接
135 | """
136 |
137 | @token_required()
138 | @deco_jsonp()
139 | async def get(self):
140 | """
141 | 1. 自己当前的组织下的资源
142 | 2. 生成share的内容
143 |
144 | 支持手机端的
145 | :return:
146 | """
147 |
148 | pro_id = self.get_argument('project_id', None) # 测试报项目的ID
149 | tag = self.get_argument('tag', 'default') # 测试报项目的ID
150 |
151 | if list_have_none_mem(*[pro_id]):
152 | return ConstData.msg_args_wrong
153 |
154 | # todo 做资源归属和权限的判断
155 |
156 | user_org = await self.get_organization()
157 |
158 | db = self.get_async_mongo()
159 | proj_col = db.test_project
160 | share_col = db.share_project_report
161 |
162 | project = await proj_col.find_one({'_id': ObjectId(str(pro_id))})
163 |
164 | if tag != 'default' and tag not in project['tags']:
165 | return ConstData.msg_args_wrong
166 |
167 | pro_org_id = project['organization']
168 |
169 | if (project is None) or (pro_org_id is None):
170 | return ConstData.msg_forbidden
171 |
172 | if pro_org_id != user_org:
173 | return ConstData.msg_forbidden
174 |
175 | #todo: delete default next release
176 | condition = {
177 | 'pro_id': pro_id
178 | }
179 | if tag == 'default':
180 | condition['$or'] = [
181 | {'tag': 'default'},
182 | {'tag': {'$exists': False}}
183 | ]
184 | else:
185 | condition['tag'] = tag
186 |
187 | share_obj = await share_col.find_one(condition)
188 |
189 | if share_obj is None: # 如果不存在分享Link则创建一组
190 | stoken = generate_uuid_token() # 生成一组随机串,分享的时候会看到
191 |
192 | share_data = dict(
193 | pro_id=pro_id,
194 | stoken=stoken,
195 | cnt=0,
196 | project=project['_id'],
197 | p_name = project['project_name'],
198 | owner = project['owner'],
199 | owner_name = project['owner_name'],
200 | organization = project['organization'],
201 | org_name = project['org_name'],
202 | tag=tag
203 | )
204 | share_data = set_default_rc_tag(share_data)
205 | await share_col.insert(share_data)
206 | share_url = pro_share_page + stoken
207 | else:
208 | # 如果有,则直接使用
209 | share_url = pro_share_page + share_obj['stoken']
210 |
211 | res_dict = dict(
212 | share_url=share_url
213 | )
214 |
215 | return get_std_json_response(data=jsontool.dumps(res_dict))
216 |
217 |
218 | class GetProjectShareData(MyUserBaseHandler):
219 | """
220 | 获取分享的单元测试数据接口
221 | """
222 |
223 | @deco_jsonp()
224 | async def get(self):
225 | stoken = self.get_argument('stoken', None)
226 | if list_have_none_mem(*[stoken]):
227 | return ConstData.msg_args_wrong
228 |
229 | page_size = self.get_argument('page_size', 30)
230 | page_idx = self.get_argument('page_idx', 1)
231 | page_size = int(page_size)
232 | page_idx = int(page_idx)
233 |
234 | db = self.get_async_mongo()
235 | share_col = db.share_project_report
236 | utest_col = db['unit_test_data']
237 |
238 | share_obj = await share_col.find_one({'stoken': stoken})
239 |
240 | if share_obj is None:
241 | return ConstData.msg_forbidden
242 |
243 | # 做一个阅读访问次数的计数
244 | await share_col.update({'stoken': stoken}, {'$inc':{'cnt': 1}})
245 |
246 | condition = {
247 | "pro_id": share_obj['project'],
248 | "is_del": False
249 | }
250 | #todo: delete default next release
251 | if 'tag' in share_obj.keys():
252 | tag = share_obj['tag']
253 | if tag != 'default':
254 | condition['tag'] = tag
255 | else:
256 | condition['$or'] = [
257 | {'tag': tag}, {'tag': {'$exists': False}}
258 | ]
259 | else:
260 | condition['$or'] = [
261 | {'tag': 'default'}, {'tag': {'$exists': False}}
262 | ]
263 |
264 | res = utest_col.find(
265 | condition, {"details": 0},
266 | sort=[('rc_time', DESCENDING)])
267 |
268 | page_count = await res.count()
269 | msg_details = res.skip(page_size * (page_idx - 1)).limit(page_size) # 进行分页
270 |
271 | total_page = math.ceil(page_count / page_size) # 总的页面数
272 |
273 | msg_content_list = await msg_details.to_list(page_size)
274 |
275 | page_res = dict(
276 | page_idx=page_idx,
277 | page_total_cnts=total_page,
278 | page_cap=page_size,
279 | page_data=msg_content_list
280 | )
281 |
282 | return get_std_json_response(data=jsontool.dumps(page_res, ensure_ascii=False))
283 |
284 |
285 | class MyUtestShare(MyUserBaseHandler):
286 | @token_required()
287 | @deco_jsonp()
288 | async def get(self):
289 | """
290 | 获取当前用户下的所有的utest分享链接
291 | :return:
292 | """
293 | return await get_org_data_paginator(self, col_name='share_test_report')
294 |
295 |
296 | class MyProjectShare(MyUserBaseHandler):
297 | @token_required()
298 | @deco_jsonp()
299 | async def get(self):
300 | """
301 | 获取当前用户下的所有的utest分享链接
302 | :return:
303 | """
304 | return await get_org_data_paginator(self, col_name='share_project_report')
305 |
306 |
307 | class UpdateUtestShare(MyUserBaseHandler):
308 | @token_required()
309 | @deco_jsonp()
310 | async def post(self):
311 | """
312 | 删除分享链接,直接完全删除,不需要留备份
313 | :return:
314 | """
315 | post_dict = self.get_post_body_dict()
316 | share_id = post_dict.get('share_id', None)
317 | mark = post_dict.get('mark', None)
318 | if list_have_none_mem(*[share_id, mark]):
319 | return ConstData.msg_args_wrong
320 |
321 | user = self.get_current_session_user()
322 | db = self.get_async_mongo()
323 | user_col = db.g_users
324 | share_col = db.share_test_report
325 | user_result = await user_col.find_one({'_id': ObjectId(user)})
326 | res = await share_col.find_one({'_id': ObjectId(str(share_id)), 'owner': user_result['_id']})
327 |
328 | if res is None:
329 | return ConstData.msg_forbidden
330 |
331 | await share_col.update({'_id': ObjectId(str(share_id)), 'owner': user_result['_id']},
332 | {'$set': {'mark': mark}})
333 | return ConstData.msg_succeed
334 |
335 |
336 | class DeleteUtestShare(MyUserBaseHandler):
337 | @token_required()
338 | @deco_jsonp()
339 | async def get(self):
340 | """
341 | 删除分享链接,直接完全删除,不需要留备份
342 | :return:
343 | """
344 | share_id = self.get_argument('share_id', None)
345 | if list_have_none_mem(*[share_id]):
346 | return ConstData.msg_args_wrong
347 | user = self.get_current_session_user()
348 |
349 | db = self.get_async_mongo()
350 | user_col = db.g_users
351 | share_col = db.share_test_report
352 |
353 | user_result = await user_col.find_one({'_id': ObjectId(user)})
354 |
355 | res = await share_col.find_one({'_id': ObjectId(str(share_id)), 'owner': user_result['_id']})
356 | """:type:ShareTestReport"""
357 | if res is None:
358 | return ConstData.msg_forbidden
359 | await share_col.remove({'_id': ObjectId(str(share_id)), 'owner': user_result['_id']})
360 | return ConstData.msg_succeed
361 |
362 |
363 | class UpdateProjectShare(MyUserBaseHandler):
364 | @token_required()
365 | @deco_jsonp()
366 | async def post(self):
367 | """
368 | 删除分享链接
369 | :return:
370 | """
371 | post_dict = self.get_post_body_dict()
372 | share_id = post_dict.get('share_id', None)
373 | mark = post_dict.get('mark', None)
374 | if list_have_none_mem(*[share_id, mark]):
375 | return ConstData.msg_args_wrong
376 |
377 | user = self.get_current_session_user()
378 | db = self.get_async_mongo()
379 | user_col = db.g_users
380 | share_col = db.share_project_report
381 | user_result = await user_col.find_one({'_id': ObjectId(user)})
382 | res = await share_col.find_one({'_id': ObjectId(str(share_id)), 'owner': user_result['_id']})
383 |
384 | if res is None:
385 | return ConstData.msg_forbidden
386 | await share_col.update({'_id': ObjectId(str(share_id)), 'owner': user_result['_id']},
387 | {'$set': {'mark': mark}})
388 | return ConstData.msg_succeed
389 |
390 |
391 | class DeleteProjectShare(MyUserBaseHandler):
392 | @token_required()
393 | @deco_jsonp()
394 | async def get(self):
395 | """
396 | 删除分享链接
397 | :return:
398 | """
399 | share_id = self.get_argument('share_id', None)
400 | if list_have_none_mem(*[share_id]):
401 | return ConstData.msg_args_wrong
402 | user = self.get_current_session_user()
403 |
404 | db = self.get_async_mongo()
405 | share_proj_col = db.share_project_report
406 |
407 | res = await share_proj_col.find_one({'_id': ObjectId(str(share_id)), 'owner': ObjectId(user)})
408 |
409 | if res is None:
410 | return ConstData.msg_forbidden
411 |
412 | await share_proj_col.remove({'_id': ObjectId(str(share_id)), 'owner': ObjectId(user)})
413 | return ConstData.msg_succeed
414 |
--------------------------------------------------------------------------------
/apps/share/urls.py:
--------------------------------------------------------------------------------
1 | from dtlib import filetool
2 |
3 | from apps.share.handlers import *
4 |
5 | app_path = filetool.get_parent_folder_name(__file__) # 'apis' # set the relative path in one place
6 |
7 | url = [
8 | # 分享测试报告
9 | (r"/%s/get-utest-share-link/" % app_path, GetUtestShareLink), # 获取Utest的分享链接
10 | (r"/%s/get-pro-share-link/" % app_path, GetProjectShareLink), # 获取project的分享链接
11 |
12 | # 对分享的外链进行管理
13 | (r"/%s/my-utest-share/" % app_path, MyUtestShare), # 我的Utest的分享链接
14 | (r"/%s/delete-utest-share/" % app_path, DeleteUtestShare),
15 | (r"/%s/update-utest-share/" % app_path, UpdateUtestShare),
16 |
17 | (r"/%s/my-project-share/" % app_path, MyProjectShare), # 我的试项目报告的数据
18 | (r"/%s/update-project-share/" % app_path, UpdateProjectShare),
19 | (r"/%s/delete-project-share/" % app_path, DeleteProjectShare), #
20 |
21 | # 通过stoken来获取数据的接口,不同的认证方式
22 | # todo 这块可以分离部署
23 | (r"/%s/get-utest-share-data/" % app_path, GetUtestShareData), # 以分享途径获得测试报告的数据
24 | (r"/%s/get-pro-share-data/" % app_path, GetProjectShareData), # 以分享途径获得测试项目报告的数据
25 |
26 | ]
27 |
--------------------------------------------------------------------------------
/apps/testdata/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x-utest/xtest-server/4d7c05266dd42ebf4a1ddc416eb615410b40b5fb/apps/testdata/__init__.py
--------------------------------------------------------------------------------
/apps/testdata/docs.py:
--------------------------------------------------------------------------------
1 | # from aiomotorengine import StringField, IntField, BooleanField, FloatField, DateTimeField, ReferenceField, ListField
2 | #
3 | # from xt_base.document.source_docs import InfoAsset
4 | # from xt_base.document.base_docs import Project
5 | # from dtlib.aio.base_mongo import MyDocument
6 | # from dtlib.tornado.account_docs import Organization
7 | # from dtlib.tornado.base_docs import OrgDataBaseDocument
8 |
9 | # TODO: 后面有需要重写相关功能
10 |
11 | class ProjectDataDocument(OrgDataBaseDocument, MyDocument):
12 | """
13 | 项目数据
14 | """
15 | # 数据归属
16 | project = ReferenceField(reference_document_type=Project) # 测试数据所属的项目
17 | project_name = StringField() # 项目名称,冗余
18 |
19 | async def set_project_tag(self, project):
20 | """
21 | 打上项目标记
22 | :type project:Project
23 | :return:
24 | """
25 |
26 | organization = project.organization
27 | self.project = project
28 | self.project_name = project.project_name
29 | self.organization = organization
30 | self.org_name = organization.name
31 |
32 |
33 | class ProjectFeedBack(ProjectDataDocument):
34 | """
35 | 项目相关的问题反馈
36 | """
37 |
38 | msg = StringField()
39 | # images = ListField() # 图片列表
40 | # todo 语音和视频
41 | label = StringField() # 关于问题的标记
42 | status = IntField() # 处理的状态码
43 |
44 |
45 | class ServiceStatus(MyDocument):
46 | """
47 | 服务器开放的服务状态
48 | """
49 | __collection__ = "service_status"
50 |
51 | info_asset = ReferenceField(reference_document_type=InfoAsset) # 所属资产,信息资产
52 | port = IntField() # 端口号
53 | protocol = StringField() # 协议名称
54 | status = StringField() # 状态
55 | version = StringField # 版本
56 |
57 |
58 | class ApiTestData(ProjectDataDocument):
59 | """
60 | 测试数据统计表---因为性能问题,取消使用 2016-09-30
61 | """
62 | __collection__ = "api_test_data"
63 |
64 | was_successful = BooleanField() # 是否是成功的
65 | total = IntField()
66 | failures = IntField()
67 | errors = IntField()
68 | skipped = IntField()
69 | run_time = StringField(max_length=1024)
70 |
71 |
72 | class ApiTestDataNote(MyDocument):
73 | """
74 | api测试详细数据,只记录失败和错误---因为性能问题,取消使用 2016-09-30
75 | """
76 | __collection__ = "api_test_data_note"
77 | apitestdata_id = StringField(max_length=1024)
78 | test_case = StringField(max_length=1024) # 出错测试用例
79 | explain = StringField(max_length=1024) # 目的
80 | status = StringField(max_length=1024) # 测试状态,失败或者错误
81 | note = StringField() # 详细记录
82 |
83 | organization = ReferenceField(reference_document_type=Organization) # 数据所属的组织
84 |
85 |
86 | class UnitTestDataDetail(MyDocument):
87 | """
88 | api测试详细数据,只记录失败和错误--插入时没有使用
89 | """
90 | test_case = StringField(max_length=1024) # 出错测试用例
91 | explain = StringField(max_length=1024) # 目的
92 | status = StringField(max_length=1024) # 测试状态,失败或者错误
93 | note = StringField() # 详细记录
94 |
95 |
96 | class UnitTestData(MyDocument):
97 | """
98 | 测试数据统计表--插入时没有使用,没有使用ORM
99 | """
100 | __collection__ = "unit_test_data"
101 |
102 | pro_version = StringField(max_length=1024) # 项目版本号:1.2.3.4
103 | was_successful = BooleanField() # 是否是成功的
104 | total = IntField()
105 | failures = IntField()
106 | errors = IntField()
107 | skipped = IntField()
108 | run_time = FloatField()
109 | details = ListField(ReferenceField(UnitTestDataDetail))
110 |
111 | # 数据归属
112 | project = ReferenceField(reference_document_type=Project) # 测试数据所属的项目
113 | project_name = StringField() # 项目名称,冗余
114 | organization = ReferenceField(reference_document_type=Organization) # 数据所属的组织
115 |
116 |
117 | class PerformReport(MyDocument):
118 | """
119 | 性能测试报告,已经作废,在bench中有专门的处理的了:2016-09-10
120 | waste
121 | """
122 | __collection__ = "perform_report"
123 |
124 | server_soft_ware = StringField(max_length=2048)
125 | server_host_name = StringField(max_length=2048)
126 | server_port = StringField(max_length=64)
127 | doc_path = StringField(max_length=2048)
128 | doc_length = IntField() # 文档长度,bytes
129 | con_level = IntField() # 并发量
130 | test_time_taken = FloatField() # 消耗时间 seconds
131 | complete_req = IntField() # 完成请求数
132 | failed_req = IntField() # 失败请求数
133 | non_2xx_res = IntField() # 非2xx请求数
134 | total_trans = IntField() # 总传输数据量bytes
135 | html_trans = IntField() # 总html传输数据量bytes
136 | req_per_sec = FloatField() # 每秒请求量
137 | time_per_req = FloatField() # 平均http请求响应时间:ms
138 | time_per_req_across = FloatField() # 所有并发请求量耗时(平均事务响应时间):ms
139 | trans_rate = FloatField() # 每秒传输数据量,Kbytes/sec
140 |
141 | organization = ReferenceField(reference_document_type=Organization) # 数据所属的组织
142 |
143 |
144 | class SafetyTestReport(ProjectDataDocument):
145 | """
146 | 安全测试报告
147 | """
148 | __collection__ = "safety_test_report"
149 | # project_name = StringField() # 项目名,里面加上版本号,这个并不是具体的内部项目,和其它几种测试数据不同
150 | hack_tool = StringField() # 用于hack的软件名称
151 | total_cnts = IntField() # 总计次数
152 | success_cnts = IntField() # 成功次数
153 | success_rate = FloatField() # 成功率,冗余
154 | time_cycle = FloatField() # 花费时间:s
155 | crack_rate = FloatField() # 破解效率,冗余
156 | mark = StringField() # 描述备注
157 | # organization = ReferenceField(reference_document_type=Organization) # 组织
158 |
159 |
160 | class ApiReqDelay(MyDocument):
161 | """测试接口的延时
162 | """
163 | __collection__ = "api_req_delay"
164 |
165 | domain = StringField(max_length=2048) # ms,基准域
166 | path = StringField(max_length=2048) # ms,接口路径及参数
167 | delay = FloatField() # ms,请求时间
168 | http_status = IntField() # http状态值
169 |
170 | # 数据归属
171 | project = ReferenceField(reference_document_type=Project)
172 | project_name = StringField() # 项目名称,冗余
173 | organization = ReferenceField(reference_document_type=Organization)
174 |
175 |
176 | class ExDataRecord(MyDocument):
177 | """
178 | 实验数据的记录表
179 | """
180 |
181 | __collection__ = "ex_data_record"
182 |
183 | # --------------数据状态值记录-----------
184 | record_status = StringField(max_length=64) # 数据的状态:reported,filted,experiment
185 | # --------------数据发现和登记期-----------
186 | custom_name = StringField(max_length=256) # 用户名称
187 | captcha_id = StringField(max_length=64)
188 | event_start_time = DateTimeField() # weblog产生开始时间
189 | event_end_time = DateTimeField() # weblog产生结束时间
190 | event_reporter = StringField(max_length=256) # 事件汇报人
191 | event_report_time = DateTimeField() # 事件汇报时间
192 | track_class = StringField(max_length=64)
193 | js_version = StringField(max_length=16)
194 | js_tag_page = StringField(max_length=2048) # 在bitbucket或者tower中的当前版本的修改标记
195 | css_version = StringField(max_length=16)
196 | css_tag_page = StringField(max_length=2048) # 在bitbucket或者tower中的当前版本的修改标记
197 | # --------------数据过滤的采集备案期-----------
198 | data_collection_name = StringField(max_length=256)
199 | producers = StringField(max_length=256)
200 | ex_user = StringField(max_length=256) # 数据收集人
201 | action_time = DateTimeField() # 数据备案时间
202 | # event_time = DateTimeField()
203 | file_name = StringField(max_length=64)
204 | file_path = StringField(max_length=2048) # 实验数据文件所在路径,以FTP的方式来共享
205 | file_size = IntField()
206 | record_cnt = IntField() # 记录的数目
207 | # --------------数据实验期-----------
208 | researcher = StringField(max_length=256) # 数据实验人
209 | researche_time = DateTimeField() # 数据实验时间
210 | research_result = StringField(max_length=10240) # 验证处理结果
211 | # experiment_id= Refer
212 |
213 |
214 | class CurtainExData(MyDocument):
215 | """
216 | Curtain项目的测试数据
217 | """
218 | # answer = ListField()
219 | # track_data = ListField()
220 | action_user = StringField(max_length=256)
221 |
222 |
223 | class LimitTestData(MyDocument):
224 | """
225 | 测试数据统计表
226 | """
227 | __collection__ = "limit_test_data"
228 |
229 | # id = ObjectId()
230 | was_successful = BooleanField() # 是否是成功的
231 | total = IntField()
232 | failures = IntField()
233 | errors = IntField()
234 | skipped = IntField()
235 | run_time = StringField(max_length=1024)
236 |
237 | organization = ReferenceField(reference_document_type=Organization) # 数据所属的组织
238 |
239 |
240 | class LimitTestDataNote(MyDocument):
241 | """
242 | api测试详细数据,只记录失败和错误(作废-2016-11-23)
243 | """
244 | __collection__ = "limit_test_data_note"
245 | limittestdata_id = StringField(max_length=1024)
246 | test_case = StringField(max_length=1024) # 出错测试用例
247 | explain = StringField(max_length=1024) # 目的
248 | status = StringField(max_length=1024) # 测试状态,失败或者错误
249 | note = StringField() # 详细记录
250 |
251 | organization = ReferenceField(reference_document_type=Organization) # 数据所属的组织
252 |
253 |
254 | class UnitPenetrationTest(MyDocument):
255 | """
256 | 渗透测试详细信息
257 | """
258 | test_case = StringField(max_length=1024) # 测试目的
259 | result = StringField(max_length=1024) # 结果
260 |
261 |
262 | class PenetrationTestData(ProjectDataDocument):
263 | """
264 | 渗透测试详情
265 | """
266 | __collection__ = "penetration_test_data"
267 | start_time = StringField()
268 | use_time = FloatField()
269 | note = StringField()
270 | # project = ReferenceField(reference_document_type=Project) # 测试数据所属的项目
271 | # project_name = StringField() # 项目名称,冗余
272 | # organization = ReferenceField(reference_document_type=Organization) # 数据所属的组织
273 |
274 |
275 | class PenetrationTestDataNote(MyDocument):
276 | """
277 | 渗透测试详情
278 | """
279 | __collection__ = "penetration_test_data_note"
280 | penetration_id = StringField(max_length=1024)
281 | ip = StringField(max_length=1024)
282 | details = ListField(ReferenceField(UnitPenetrationTest))
283 | # SSHRootEmptyPassword = StringField(max_length=1024)
284 | # RedisEmptyPassword = StringField(max_length=1024)
285 | # MongoEmptyPassword = StringField(max_length=1024)
286 | organization = ReferenceField(reference_document_type=Organization) # 数据所属的组织
287 |
288 |
289 | class ProxyipTestData(MyDocument):
290 | """
291 | 爆破测试
292 | """
293 | __collection__ = "proxyip_test_data"
294 | remoteip = StringField(max_length=1024)
295 | originalip = StringField(max_length=1024)
296 | proxyip = StringField(max_length=1024)
297 |
298 | organization = ReferenceField(reference_document_type=Organization) # 数据所属的组织
299 |
300 |
301 | class FeedbackMsg(MyDocument):
302 | """
303 | feedback
304 | """
305 |
306 | __collection__ = 'feedback_msg'
307 | file_path = StringField() # 文件路径
308 | msg = StringField() # msg
309 |
--------------------------------------------------------------------------------
/apps/testdata/handlers.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from dtlib.tornado.base_hanlder import MyUserBaseHandler
4 | from bson import ObjectId
5 | from dtlib import jsontool
6 | from dtlib.aio.base_mongo import wrap_default_rc_tag
7 | from dtlib.aio.decos import my_async_paginator, my_async_jsonp
8 | from dtlib.dtlog import dlog
9 | from dtlib.tornado.base_hanlder import MyAppBaseHandler
10 | from dtlib.tornado.decos import token_required, app_token_required
11 | from dtlib.utils import list_have_none_mem
12 | from dtlib.web.constcls import ConstData, ResCode
13 | from dtlib.web.decos import deco_jsonp
14 | from dtlib.web.tools import get_std_json_response
15 | from dtlib.web.valuedict import ClientTypeDict
16 | from dtlib.tornado.utils import get_org_data
17 | from dtlib.tornado.utils import wrap_org_tag, wrap_project_tag, get_org_data_paginator
18 |
19 |
20 | class ListApiTestData(MyUserBaseHandler):
21 | """
22 | 前段页面显示出数据
23 | 如果没有任何参数,则默认返回首页的n条数据
24 | 如果并非首次回返
25 | --因为性能问题,取消使用 2016-09-30
26 | """
27 |
28 | @token_required()
29 | @my_async_jsonp
30 | @my_async_paginator
31 | async def get(self):
32 | pro_id = self.get_argument('pro_id', None)
33 | if pro_id is None:
34 | return await get_org_data(self, collection='api_test_data')
35 | else:
36 | return await get_org_data(self, collection='api_test_data', pro_id=pro_id)
37 |
38 |
39 | class ListSafetyTestData(MyUserBaseHandler):
40 | """
41 | """
42 |
43 | @token_required()
44 | @my_async_jsonp
45 | @my_async_paginator
46 | async def get(self):
47 | return await get_org_data(self, collection='safety_test_report')
48 |
49 |
50 | class ListApiReqDelay(MyUserBaseHandler):
51 | @token_required()
52 | @my_async_jsonp
53 | @my_async_paginator
54 | async def get(self):
55 | """
56 | 查询所有的接口延时测试信息
57 | :return:
58 | """
59 | pro_id = self.get_argument('pro_id', None)
60 | if pro_id is None:
61 | return await get_org_data(self, collection='api_req_delay')
62 | else:
63 | return await get_org_data(self, collection='api_req_delay', pro_id=pro_id)
64 | # return await ApiReqDelay.objects.filter(project_name=project_name).order_by('rc_time',
65 | # direction=DESCENDING).find_all()
66 |
67 | # return ApiReqDelay.objects.order_by('rc_time', direction=DESCENDING).find_all()
68 |
69 |
70 | class ListPerformanceTestData(MyUserBaseHandler):
71 | """
72 | 废弃掉的功能
73 | """
74 |
75 | @token_required()
76 | @my_async_jsonp
77 | @my_async_paginator
78 | async def get(self):
79 | return await get_org_data(self, collection='perform_report')
80 |
81 |
82 | class ListUnitTestData(MyUserBaseHandler):
83 | """
84 | 带分页的内容,不使用ORM了,这样返回会快速很多
85 | """
86 |
87 | @token_required()
88 | @my_async_jsonp
89 | async def get(self):
90 | """
91 | 客户端将整个报告上传到服务器
92 | :return:返回插入的数据的id,方便后续做关联插入
93 | """
94 | pro_id = self.get_argument('pro_id', None)
95 | tag = self.get_arguments('tag')
96 | return await get_org_data_paginator(self, col_name='unit_test_data', pro_id=pro_id, hide_fields={'details': 0}, tag=tag)
97 |
98 |
99 | class DeleteTestData(MyUserBaseHandler):
100 | """
101 | 删除testdata(实际是将is_del设置为true)
102 | """
103 |
104 | @token_required()
105 | @my_async_jsonp
106 | async def get(self):
107 | id = self.get_argument('id', None)
108 | if id is None:
109 | return ConstData.msg_args_wrong
110 |
111 | # todo 做资源归属和权限的判断
112 | # project_obj = await .objects.get(id=ObjectId(str(id)))
113 | mongo_coon = self.get_async_mongo()
114 | mycol = mongo_coon['unit_test_data']
115 | pro_col = mongo_coon['test_project']
116 | testdata = await mycol.find_one({'_id': ObjectId(str(id))})
117 | testdata_organization = testdata['organization']
118 | user_org = await self.get_organization()
119 |
120 | if (testdata is None) or (testdata_organization is None):
121 | return ConstData.msg_forbidden
122 |
123 | pro_org_id = testdata_organization
124 | if pro_org_id != user_org:
125 | return ConstData.msg_forbidden
126 |
127 | await mycol.update({"_id": ObjectId(str(id))}, {"$set": {"is_del": True}})
128 |
129 | if 'tag' in testdata.keys():
130 | pro_id = testdata['pro_id']
131 | tag = testdata['tag']
132 | # todo: count records of this tag, if < 1, delete tag in project
133 | tags = await mycol.find({"pro_id": ObjectId(pro_id), 'is_del': False, 'tag': tag}, {'tag': 1}).count()
134 | if tags < 1:
135 | project = await pro_col.find_one({"_id": ObjectId(pro_id)})
136 | proj_tags = project['tags']
137 | proj_tags.remove(tag)
138 | await pro_col.update({"_id": ObjectId(pro_id)}, {"$set": {"tags": proj_tags}})
139 | return ConstData.msg_succeed
140 |
141 |
142 | class CreateUnitTestData(MyAppBaseHandler):
143 | """
144 | 插入测试数据,包括总值和明细值,高性能的一次性插入,没有用ORM
145 | """
146 |
147 | @app_token_required()
148 | # @my_async_jsonp
149 | async def post(self):
150 | """
151 | 客户端将整个报告上传到服务器
152 | :return:
153 | """
154 | token = self.get_argument('token', None)
155 | req_dict = self.get_post_body_dict()
156 |
157 | # 做好字段的检查工作
158 | pro_id = req_dict.get('pro_id', None)
159 | failures = req_dict.get('failures', None)
160 | errors = req_dict.get('errors', None)
161 | details = req_dict.get('details', None)
162 | skipped = req_dict.get('skipped', None)
163 | pro_version = req_dict.get('pro_version', None)
164 | run_time = req_dict.get('run_time', None)
165 | total = req_dict.get('total', None)
166 | was_successful = req_dict.get('was_successful', None)
167 | tag = req_dict.get('tag', 'default')
168 |
169 | if list_have_none_mem(
170 | *[pro_id, failures, errors,
171 | details, skipped,
172 | pro_version, run_time,
173 | total, was_successful, ]):
174 | return ConstData.msg_args_wrong
175 |
176 | if len(pro_version) > 32: # 如果版本号长度大于32,比如出现了没有标定版本号的情况
177 | pro_version = '0.0.0.0.0'
178 | req_dict['pro_version'] = pro_version
179 |
180 | db = self.get_async_mongo()
181 | proj_col = db.test_project
182 | test_data_col = db.unit_test_data
183 |
184 | # project = await Project.objects.get(id=ObjectId(pro_id))
185 | project = await proj_col.find_one({'_id': ObjectId(pro_id)})
186 | """:type:Project"""
187 |
188 | app_org = await self.get_organization()
189 | """:type:Organization"""
190 |
191 | # if (project is None) or (project.organization is None):
192 | if project is None:
193 | return ConstData.msg_forbidden
194 |
195 | # 权限鉴定,不允许越权访问别人的组织的项目
196 | pro_org_id = project['organization']
197 | if pro_org_id != app_org:
198 | return ConstData.msg_forbidden
199 |
200 | # todo 后续的一些更细节的字段内在的约束检查
201 |
202 | req_dict = wrap_project_tag(req_dict, project) # 加上项目标签
203 | req_dict = wrap_default_rc_tag(req_dict) # 加上默认的标签
204 | req_dict = wrap_org_tag(req_dict, str(pro_org_id)) # 加上组织的标签
205 | insert_res = await test_data_col.insert(req_dict)
206 | if 'tags' in project.keys():
207 | pro_tags = project['tags']
208 | # if tag in pro_tags:
209 | # return ConstData.res_tpl % (ResCode.ok, 'success', '"' + str(insert_res) + '"')
210 | else:
211 | pro_tags = []
212 | if tag not in pro_tags:
213 | pro_tags.append(tag)
214 | await proj_col.update({'_id': ObjectId(pro_id)}, {'$set': {'tags': pro_tags}})
215 | # return ConstData.res_tpl % (ResCode.ok, 'success', '"' + str(insert_res) + '"')
216 | self.redirect('/share/get-utest-share-link/?token={}&rep_id={}'.format(token, insert_res))
217 |
218 |
219 | class GetOneUnitTestData(MyUserBaseHandler):
220 | """
221 | 获取一条记录
222 | """
223 |
224 | @token_required()
225 | @my_async_jsonp
226 | async def get(self):
227 | """
228 | 要做鉴权和区分
229 | :return:
230 | """
231 | data_id = self.get_argument('id', None)
232 | if list_have_none_mem(*[data_id]):
233 | return ConstData.msg_args_wrong
234 |
235 | mongo_coon = self.get_async_mongo()
236 | mycol = mongo_coon['unit_test_data']
237 |
238 | user_org = await self.get_organization()
239 | """:type:Organization"""
240 |
241 | msg_details = await mycol.find_one({
242 | "organization": ObjectId(user_org),
243 | "_id": ObjectId(data_id)
244 | })
245 |
246 | return get_std_json_response(data=jsontool.dumps(msg_details, ensure_ascii=False))
247 |
248 | class ApiAuth(MyAppBaseHandler):
249 | """
250 | 验证API是否被授权了,通过ID和KEY来换取token
251 | """
252 |
253 | @my_async_jsonp
254 | async def post(self):
255 | appid = self.get_argument('appid_form', None)
256 | appkey = self.get_argument('appkey_form', None)
257 |
258 | if list_have_none_mem(*[appid, appkey]):
259 | return ConstData.msg_args_wrong
260 |
261 | db = self.get_async_mongo()
262 | app_col = db.test_data_app
263 |
264 | test_data_app = await app_col.find_one({'app_id': str(appid)}) # 后面要为app_id建立index
265 | if test_data_app is None:
266 | return ConstData.msg_none
267 |
268 | # 可以和数据库连接形成动态的验证
269 | if str(appkey) == str(test_data_app['app_key']):
270 | # todo:后面对于自动化的工具应用,要隔离部署,单独做一套体系,先默认使用某个人的信息了
271 |
272 | app_session = await self.create_app_session(app_id=appid, client_type=ClientTypeDict.api)
273 |
274 | if app_session is None:
275 | return ConstData.msg_forbidden
276 |
277 | # todo 后面要做高频的api接口的统计系统
278 |
279 | res_data = jsontool.dumps(app_session)
280 | dlog.debug(res_data)
281 | return get_std_json_response(data=res_data)
282 | else:
283 | return ConstData.msg_fail
284 |
285 |
286 | class ApiAuthout(MyAppBaseHandler):
287 | """
288 | 取消API和认证
289 | """
290 |
291 | @token_required()
292 | @deco_jsonp(is_async=False)
293 | def get(self):
294 | self.log_out()
295 | return get_std_json_response(code=200, msg='logout', data='')
296 |
297 |
298 | # code trash (2018-04-23 yx)
299 | # class CreatePerformReport(MyUserBaseHandler):
300 | # # TODO: change to motor
301 | # """
302 | # 保存性能测试的数据指标
303 | # """
304 | #
305 | # @token_required()
306 | # @my_async_jsonp
307 | # async def post(self):
308 | # # TODO 读取动态的内容
309 | # perform_report = PerformReport()
310 | # # data = json_decode(self.request.body)
311 | #
312 | # # perform_report.server_soft_ware='squid/3.4.2'
313 | # perform_report.server_host_name = self.get_argument('doc_path', None)
314 | # perform_report.server_port = '80'
315 | # # perform_report.doc_path = '/'
316 | # # perform_report.doc_length = 131
317 | # perform_report.con_level = self.get_argument('con_level', None) # 并发量
318 | # # perform_report.test_time_taken = 52.732 # 消耗时间 seconds
319 | # # perform_report.complete_req = data['con_level'] * 20 # 完成请求数
320 | # perform_report.failed_req = self.get_argument('failed_req', None) # 失败请求数
321 | # # perform_report.non_2xx_res = IntField()#非2xx请求数
322 | # # perform_report.total_trans = 62505778#总传输数据量bytes
323 | # # perform_report.html_trans = 62472673#总html传输数据量bytes
324 | #
325 | #
326 | # perform_report.req_per_sec = self.get_argument('req_per_sec', None) # 每秒请求量
327 | # perform_report.time_per_req = self.get_argument('time_per_req', None) # 平均http请求响应时间
328 | # perform_report.time_per_req_across = self.get_argument('time_per_req_across', None) # 平均事务响应时间
329 | # # perform_report.trans_rate = 1157.56#每秒传输数据量
330 | # # perform_report.time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
331 | # perform_report.set_default_rc_tag()
332 | #
333 | # perform_report.organization = await self.get_organization()
334 | #
335 | # perform_report.save()
336 | #
337 | # return ConstData.msg_succeed
338 | #
339 | #
340 | # class CreateSafetyTestReport(MyUserBaseHandler):
341 | # # TODO: change to motor
342 | # """
343 | # 保存安全测试数据
344 | # """
345 | #
346 | # @app_token_required()
347 | # @my_async_jsonp
348 | # async def get(self):
349 | # project_id = self.get_argument('project_id', None)
350 | # total_cnts = self.get_argument('total_cnts', None)
351 | # success_cnts = self.get_argument('success_cnts', None)
352 | # time_cycle = self.get_argument('time_cycle', None)
353 | # # project_name = self.get_argument('project_name', None)
354 | # hack_tool = self.get_argument('hack_tool', None)
355 | # notes = self.get_argument('notes', None)
356 | #
357 | # if list_have_none_mem(*[project_id, total_cnts, success_cnts, time_cycle]) is True:
358 | # return ConstData.msg_args_wrong
359 | #
360 | # # todo 判定项目从属的问题,后面要做成专门的公共函数
361 | # app_org = await self.get_org_by_app()
362 | # """:type:Organization"""
363 | # project = await Project.objects.get(id=ObjectId(project_id))
364 | # """:type:Project"""
365 | # if project is None or app_org is None:
366 | # return ConstData.msg_none
367 | # if app_org.get_id() != project.organization.get_id():
368 | # return ConstData.msg_forbidden
369 | #
370 | # safety_report = SafetyTestReport()
371 | # safety_report.hack_tool = hack_tool
372 | # safety_report.total_cnts = int(total_cnts)
373 | # safety_report.success_cnts = int(success_cnts)
374 | # safety_report.success_rate = int(success_cnts) / int(total_cnts)
375 | # safety_report.time_cycle = float(time_cycle)
376 | # safety_report.crack_rate = float(time_cycle) / int(success_cnts)
377 | #
378 | # safety_report.mark = notes # 加上备注
379 | # safety_report.set_default_rc_tag()
380 | # await safety_report.set_project_tag(project)
381 | # await safety_report.save()
382 | #
383 | # return ConstData.msg_succeed
384 | #
385 | #
386 | # class CreateApiReqDelay(MyUserBaseHandler):
387 | # # TODO: change to motor
388 | # @app_token_required()
389 | # @my_async_jsonp
390 | # async def post(self):
391 | # """创建api访问的时间接口
392 | # """
393 | # pro_id = self.get_argument('pro_id', None)
394 | # domain = self.get_argument('domain', None)
395 | # path = self.get_argument('path', None)
396 | # delay = self.get_argument('delay', None)
397 | # http_status = self.get_argument('http_status', None)
398 | #
399 | # if list_have_none_mem(*[domain, path, delay, pro_id, http_status]):
400 | # return ConstData.msg_args_wrong
401 | #
402 | # project = await Project.objects.get(id=ObjectId(pro_id))
403 | # """:type:Project"""
404 | # if project is None:
405 | # return ConstData.msg_forbidden
406 | #
407 | # api_req_delay = ApiReqDelay()
408 | # api_req_delay.project = project
409 | # api_req_delay.project_name = project.project_name
410 | # api_req_delay.domain = domain
411 | # api_req_delay.delay = delay
412 | # api_req_delay.path = path
413 | # api_req_delay.http_status = http_status
414 | # api_req_delay.organization = await self.get_org_by_app()
415 | # api_req_delay.set_default_rc_tag()
416 | #
417 | # await api_req_delay.save()
418 | #
419 | # return ConstData.msg_succeed
420 | #
421 | #
422 | # class CreatePenetrationReport(MyUserBaseHandler):
423 | # # TODO: change to motor
424 | # """
425 | # 渗透测试数据
426 | # """
427 | #
428 | # @app_token_required()
429 | # @my_async_jsonp
430 | # async def post(self):
431 | # start_time = self.get_argument('start_time', None)
432 | # use_time = self.get_argument('use_time', None)
433 | # note = self.get_argument('note', None)
434 | # pro_id = self.get_argument('pro_id', None)
435 | # if list_have_none_mem(*[start_time, use_time]) is True:
436 | # return ConstData.msg_args_wrong
437 | #
438 | # project = await Project.objects.get(id=ObjectId(pro_id))
439 | # """:type:Project"""
440 | #
441 | # app_org = await self.get_org_by_app()
442 | # """:type:Organization"""
443 | #
444 | # if (project is None) or (project.organization is None):
445 | # return ConstData.msg_forbidden
446 | #
447 | # # 权限鉴定,不允许越权访问别人的组织
448 | # if project.organization.get_id() != app_org.get_id():
449 | # return ConstData.msg_forbidden
450 | #
451 | # penetration_report = PenetrationTestData()
452 | # penetration_report.start_time = start_time
453 | # penetration_report.use_time = use_time
454 | # penetration_report.note = note
455 | # penetration_report.project = project
456 | # penetration_report.project_name = project.project_name
457 | # penetration_report.organization = project.organization
458 | # penetration_report.set_default_rc_tag()
459 | #
460 | # result = await penetration_report.save()
461 | #
462 | # res_str = get_std_json_response(code=200, data=jsontool.dumps(result.to_dict()))
463 | # return res_str
464 | #
465 | #
466 | # class CreatePenetrationReportNote(MyUserBaseHandler):
467 | # # TODO: change to motor
468 | # """
469 | # 渗透测试数据详情
470 | # """
471 | #
472 | # @app_token_required()
473 | # @my_async_jsonp
474 | # async def post(self):
475 | # penetration_report = PenetrationTestDataNote()
476 | # penetration_report.penetration_id = self.get_argument('penetration_id', None)
477 | # penetration_report.ip = self.get_argument('ip', None)
478 | # penetration_report.details = self.get_argument('details', None)
479 | # # penetration_report.MongoEmptyPassword = self.get_argument('MongoEmptyPassword', None)
480 | # # penetration_report.RedisEmptyPassword = self.get_argument('RedisEmptyPassword', None)
481 | # # penetration_report.SSHRootEmptyPassword = self.get_argument('SSHRootEmptyPassword', None)
482 | # penetration_report.set_default_rc_tag()
483 | # penetration_report.organization = await self.get_org_by_app()
484 | # await penetration_report.save()
485 | #
486 | # return ConstData.msg_succeed
487 | #
488 | #
489 | # class CreateProxyipReport(MyUserBaseHandler):
490 | # # TODO: change to motor
491 | # """
492 | # 代理测试数据
493 | # """
494 | #
495 | # @app_token_required()
496 | # @my_async_jsonp
497 | # async def post(self):
498 | # proxyip_report = ProxyipTestData()
499 | # proxyip_report.remoteip = self.get_argument('remoteip', None)
500 | # proxyip_report.originalip = self.get_argument('originalip', None)
501 | # proxyip_report.proxyip = self.get_argument('proxyip', None)
502 | # proxyip_report.set_default_rc_tag()
503 | # proxyip_report.organization = await self.get_org_by_app()
504 | # await proxyip_report.save()
505 | #
506 | # return ConstData.msg_succeed
507 | #
508 | #
509 | # class ReadProxyipReport(MyUserBaseHandler):
510 | # """
511 | # 读取代理测试数据
512 | # """
513 | #
514 | # @token_required()
515 | # @my_async_jsonp
516 | # @my_async_paginator
517 | # async def get(self):
518 | # pro_id = self.get_argument('pro_id', None)
519 | # if pro_id is None:
520 | # return await get_org_data(self, collection='proxyip_test_data')
521 | # else:
522 | # return await get_org_data(self, collection='proxyip_test_data', pro_id=pro_id)
523 | #
524 | #
525 | # class ReadPenetrationReport(MyUserBaseHandler):
526 | # """
527 | # 读取渗透测试数据
528 | # """
529 | #
530 | # @token_required()
531 | # @my_async_jsonp
532 | # @my_async_paginator
533 | # async def get(self):
534 | # pro_id = self.get_argument('pro_id', None)
535 | # if pro_id is None:
536 | # return await get_org_data(self, collection='penetration_test_data')
537 | # else:
538 | # return await get_org_data(self, collection='penetration_test_data', pro_id=pro_id)
539 | #
540 | #
541 | # class ReadPenetrationReportNote(MyUserBaseHandler):
542 | # # TODO: change to motor
543 | # """
544 | # 读取渗透测试数据详情
545 | # """
546 | #
547 | # @token_required()
548 | # @my_async_jsonp
549 | # # @my_async_paginator
550 | # async def get(self):
551 | # penetration_id = self.get_argument('penetration_id', None)
552 | # res = await PenetrationTestDataNote.objects.filter(penetration_id=penetration_id).find_all()
553 | # result = []
554 | # for item in res:
555 | # result.append(item.to_dict())
556 | # data = get_std_json_response(code=200, data=jsontool.dumps(result))
557 | # return data
558 | #
559 | #
560 | # class ReadDetailTestData(MyUserBaseHandler):
561 | # # TODO: change to motor
562 | # """
563 | #
564 | # """
565 | #
566 | # @token_required()
567 | # @my_async_jsonp
568 | # async def get(self):
569 | # id = self.get_argument('id', None)
570 | # callback = self.get_argument('callback', None)
571 | # res = await ApiTestDataNote.objects.filter(apitestdata_id=id).find_all()
572 | # result = []
573 | # for item in res:
574 | # result.append(item.to_dict())
575 | # data = get_std_json_response(code=200, data=jsontool.dumps(result))
576 | # return data
577 | #
578 | #
579 | # class ReadTestDataTag(MyUserBaseHandler):
580 | # """
581 | # --因为性能问题,取消使用 2016-09-30
582 | # """
583 | #
584 | # @token_required()
585 | # @my_async_jsonp
586 | # async def get(self):
587 | # id = self.get_argument('id', None)
588 | # res = await ApiTestData.objects.get(id=ObjectId(id))
589 | # return get_std_json_response(code=200, data=jsontool.dumps(res.to_dict()))
590 | #
591 | #
592 | # class CreateFeedback(MyUserBaseHandler):
593 | # # TODO: change to motor
594 | # """
595 | # 上传文件
596 | # """
597 | #
598 | # @token_required()
599 | # @my_async_jsonp
600 | # async def post(self):
601 | # upload_path = os.path.join(os.path.dirname(__file__), 'files') # 文件的暂存路径
602 | # file_metas = self.request.files['uploadfile'] # 提取表单中‘name’为‘file’的文件元数据
603 | # msg = self.get_argument('msg', None)
604 | # if list_have_none_mem(*[msg]):
605 | # return ConstData.msg_args_wrong
606 | #
607 | # file_list = []
608 | # for meta in file_metas:
609 | # filename = meta['filename']
610 | # # filepath = os.path.join(upload_path, filename)
611 | # filepath = '/var/static/' + filename
612 | # with open(filepath, 'wb') as up: # 有些文件需要已二进制的形式存储,实际中可以更改
613 | # up.write(meta['body'])
614 | # file_list.append(filename)
615 | #
616 | # feedback_msg = FeedbackMsg()
617 | # feedback_msg.msg = msg
618 | # feedback_msg.file_path = filepath
619 | # await feedback_msg.save()
620 | # return ConstData.msg_succeed
621 | #
622 | #
623 | # class FeedbackList(MyUserBaseHandler):
624 | # """
625 | # 查看列表
626 | # """
627 | #
628 | # @token_required()
629 | # @my_async_jsonp
630 | # @my_async_paginator_list
631 | # async def get(self):
632 | # mongo_coon = self.get_async_mongo()
633 | #
634 | # res = await mongo_coon['feedback_msg'].find({})
635 | # list = []
636 | # for i in res:
637 | # list.append(i.to_list())
638 | # return list
639 |
--------------------------------------------------------------------------------
/apps/testdata/urls.py:
--------------------------------------------------------------------------------
1 | from dtlib import filetool
2 |
3 | from apps.testdata.handlers import *
4 |
5 | app_path = filetool.get_parent_folder_name(__file__) # 'apis' # set the relative path in one place
6 |
7 | url = [
8 | # 测试结果插入数据
9 |
10 |
11 | (r"/%s/api-auth/" % app_path, ApiAuth),
12 | (r"/%s/api-auth-out/" % app_path, ApiAuthout),
13 |
14 | # api_auth纯接口区jsonp,不涉及UI,
15 | # (r"/%s/create-perform-report/" % app_path, CreatePerformReport), # 服务端性能测试报告
16 | # (r"/%s/create-safety-test-report/" % app_path, CreateSafetyTestReport), # 安全数据
17 | # (r"/%s/create-api-req-delay/" % app_path, CreateApiReqDelay), # 模拟浏览器器请求的响应时间
18 | # (r"/%s/create-penetration-test-data/" % app_path, CreatePenetrationReport), # 插入渗透测试数据
19 | # (r"/%s/create-penetration-test-data-note/" % app_path, CreatePenetrationReportNote), # 插入渗透测试数据详情
20 | # (r"/%s/create-proxyip-test-data/" % app_path, CreateProxyipReport), # 插入代理ip测试数据
21 |
22 | (r"/%s/create-test-data/" % app_path, CreateUnitTestData), # 创建完整的测试数据--自动化高频接口
23 | (r"/%s/list-test-data/" % app_path, ListUnitTestData), # 显示分页信息,不使用ORM
24 | (r"/%s/get-one-test-data/" % app_path, GetOneUnitTestData), # 显示一个数据信息
25 |
26 | # login_auth分页信息显示-jsonp
27 | (r"/%s/read-server-api-testdata/" % app_path, ListApiTestData), # 接口测试
28 | (r"/%s/read-safety-testdata/" % app_path, ListSafetyTestData), # 安全测试
29 | (r"/%s/read-performance-testdata/" % app_path, ListPerformanceTestData), # 压力测试
30 | (r"/%s/read-api-req-delay-testedata/" % app_path, ListApiReqDelay), # 响应时间的显示
31 | # (r"/%s/read-penetration-test-data/" % app_path, ReadPenetrationReport), # 读取爆破测试数据
32 | # (r"/%s/read-penetration-test-data-note/" % app_path, ReadPenetrationReportNote), # 读取爆破测试数据
33 | # (r"/%s/read-proxyip-test-data/" % app_path, ReadProxyipReport), # 读取代理ip测试数据
34 |
35 | (r"/%s/delete-test-data/" % app_path, DeleteTestData), # 删除接口测试数据
36 |
37 | # (r"/%s/feedback/" % app_path, CreateFeedback), #
38 | # (r"/%s/feedback-list/" % app_path, FeedbackList), #
39 |
40 | ]
41 |
--------------------------------------------------------------------------------
/apps/webapp/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x-utest/xtest-server/4d7c05266dd42ebf4a1ddc416eb615410b40b5fb/apps/webapp/__init__.py
--------------------------------------------------------------------------------
/apps/webapp/handlers.py:
--------------------------------------------------------------------------------
1 | from bson import ObjectId
2 | from dtlib import jsontool
3 | from dtlib.aio.decos import my_async_jsonp
4 | from dtlib.tornado.decos import token_required
5 | # from dtlib.tornado.docs import TestDataApp
6 | from dtlib.utils import list_have_none_mem
7 | from dtlib.web.constcls import ConstData
8 | from dtlib.web.tools import get_std_json_response
9 |
10 | from dtlib.tornado.base_hanlder import MyUserBaseHandler
11 |
12 |
13 | class GetOrgTestApps(MyUserBaseHandler):
14 | """
15 | 获取某个组织的自动化APP
16 | """
17 |
18 | @token_required()
19 | @my_async_jsonp
20 | async def get(self):
21 | org_id = self.get_argument('org_id', None)
22 |
23 | if list_have_none_mem(*[org_id, ]):
24 | return ConstData.msg_args_wrong
25 |
26 | # org = await Organization.objects.get(id=ObjectId(str(org_id)))
27 | # """:type Organization"""
28 | # if org is None:
29 | # return ConstData.msg_none
30 |
31 | db = self.get_async_mongo()
32 | app_col = db.test_data_app
33 |
34 | test_app = await app_col.find_one({
35 | 'organization': ObjectId(str(org_id))
36 | })
37 |
38 | # test_app = await TestDataApp.objects.get(organization=ObjectId(str(org_id))) # 如果后面是1对多了,则查询多条
39 |
40 | if test_app is None:
41 | return ConstData.msg_none
42 | else:
43 | # result = []
44 | # result.append(test_app.to_dict())
45 | return get_std_json_response(code=200, data=jsontool.dumps(test_app, ensure_ascii=False))
46 |
47 | # code trash (2018-04-23 yx)
48 | # class GetCurrentOrgTestApp(MyUserBaseHandler):
49 | # """
50 | # 获取当前组织的自动化APP
51 | # """
52 | #
53 | # @token_required()
54 | # @my_async_jsonp
55 | # async def get(self):
56 | # organization = await self.get_organization()
57 | # """:type Organization"""
58 | # app = await TestDataApp.objects.get(organization=ObjectId(organization.get_id()))
59 | #
60 | # if app is None:
61 | # return ConstData.msg_none
62 | # else:
63 | # result = []
64 | #
65 | # result.append(app.to_dict())
66 | # return get_std_json_response(code=200, data=jsontool.dumps(result))
67 |
--------------------------------------------------------------------------------
/apps/webapp/urls.py:
--------------------------------------------------------------------------------
1 | from dtlib import filetool
2 | from apps.webapp.handlers import *
3 |
4 | app_path = filetool.get_parent_folder_name(__file__) # set the relative path in one place
5 |
6 | url = [
7 |
8 |
9 | # todo: 这个后面不再用了,因为后面应用是和组织绑定了,谁拥有组织,谁就有应用的读写权限
10 | # (r"/%s/get-auth-user-test-app/" % app_path, GetCurrentOrgTestApp), # 获取当前用户的应用ID/KEY
11 | #
12 | (r"/%s/get-org-test-apps/" % app_path, GetOrgTestApps), # 获取当前用户的应用ID/KEY
13 | ]
14 |
--------------------------------------------------------------------------------
/apps/ws/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x-utest/xtest-server/4d7c05266dd42ebf4a1ddc416eb615410b40b5fb/apps/ws/__init__.py
--------------------------------------------------------------------------------
/apps/ws/handlers.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 |
3 | """
4 | 建立长连接的websocket
5 | """
6 | from tornado import websocket
7 | from tornado.gen import coroutine
8 | from dtlib.tornado.tools import call_subprocess
9 | from config import AUTOTEST_CMD
10 |
11 |
12 | class ApiTestsSocket(websocket.WebSocketHandler):
13 | """
14 | 执行API脚本的WS请求
15 | """
16 |
17 | ws_client = None # 请求的客户端
18 |
19 | def check_origin(self, origin):
20 | return True
21 |
22 | def open(self):
23 | """
24 | 连接websocket服务器时进行的event
25 | """
26 |
27 | ApiTestsSocket.ws_client = self # 添加websocket
28 |
29 | print("WebSocket opened")
30 |
31 | @coroutine
32 | def on_message(self, message):
33 | """
34 | 收到信息的时候进行的动作
35 | """
36 | # write_message用于主动写信息,这里将收到的信息原样返回
37 | # yield sleep(5)
38 | print('Websocket receive message')
39 | # self.write_message(ConstData.msg_fail)
40 | if message == "exec_api_tests":
41 | cmd = AUTOTEST_CMD # 执行自动化脚本任务
42 | # cmd = 'sleep 5'
43 | result, error = yield call_subprocess(cmd)
44 | else:
45 | pass
46 | # self.write_message(ConstData.msg_fail)
47 | # self.write_message(u"You said: " + message)
48 |
49 | def on_close(self):
50 | """
51 | 关闭连接时的动作
52 | """
53 | ApiTestsSocket.ws_client = None
54 |
--------------------------------------------------------------------------------
/apps/ws/urls.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 | """
3 | websocket相关接口
4 | """
5 |
6 |
7 | from apps.ws.handlers import ApiTestsSocket
8 | from dtlib import filetool
9 |
10 | app_path = filetool.get_parent_folder_name(__file__)
11 |
12 | url = [
13 | (r"/%s/exec-api-tests/" % app_path, ApiTestsSocket), # 执行服务
14 | ]
15 |
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import sys
4 |
5 | from dtlib.dbtool import DbSetting
6 |
7 | app_version = '4.18.5.21.1' # 项目版本号
8 | test_token = '1234' # 用于超级管理员调用url的简单的token
9 |
10 | # if you use tornado project,this returns the root of the project
11 | BASE_DIR = os.path.abspath(os.path.curdir)
12 | debug_mode = False # 是否是调试模式
13 |
14 | settings = {
15 | "cookie_secret": "42233434",
16 | # 'xsrf_cookies':True,
17 | "debug": False, # 非调试模式
18 | "static_path": os.path.join(BASE_DIR, "static"), # 静态文件目录
19 | # "login_url":"/",
20 | "template_path": os.path.join(BASE_DIR, "static/templates"), # 模板目录
21 | }
22 |
23 | HTTP_PORT = 8011 # 启动本app的端口
24 | SERVER_PROCESS = 0 # 设置进程数,0表示利用所有的CPU核心
25 |
26 | # 根据环境变量,切换开发态和生产态
27 | try:
28 | docker_flag = os.environ.get('DOCKER', "")
29 | # TODO: add online config.
30 | if docker_flag == '1':
31 | mongo_host = 'mongo'
32 | print('Run in docker!')
33 | else:
34 | mongo_host = '127.0.0.1'
35 | except:
36 | print("Unexpected error:", sys.exc_info()[0])
37 | raise
38 |
39 | # ======Mongodb===
40 | mongodb_cfg = DbSetting(
41 | alias='default',
42 | host=mongo_host,
43 | port=27017,
44 | db_name='xtest',
45 | user_name='xtest',
46 | user_pwd='xtest@2018',
47 | max_connections=10
48 | )
49 |
50 | LOGGING_LEVEL = logging.ERROR # 日志输出的级别
51 | LOGGING_STREAM = '/dev/stdout' # 重定向到控制台
52 |
53 | AUTOTEST_CMD = 'python /api-test-template/run.py' # 自动化测试脚本
54 |
55 | if __name__ == '__main__':
56 | # BASE_DIR = os.path.abspath('.')
57 | # print BASE_DIR
58 | pass
59 |
--------------------------------------------------------------------------------
/config_api.py:
--------------------------------------------------------------------------------
1 | """
2 | 本服务特有的config,和标准的模板config独立出来
3 | """
4 | BASE_DOMAIN = 'http://api.apiapp.cc' # 根域名,一些全局变量里面会用到
5 | QR_AUTH_DOMAIN = 'qrauth://api.apiapp.cc' # 二维码用于扫码认证的
6 | AUTH_CALLBACK_DOMAIN = 'http://xtest.apiapp.cc/'
7 | capt_image_domain = 'http://192.168.1.200:8079' # 本地打码图片服务器
8 |
--------------------------------------------------------------------------------
/init_app.py:
--------------------------------------------------------------------------------
1 | """
2 | 初始化app的运行环境
3 | - 多核心环境设计有竞争的资源时,会有线程冲突
4 | """
5 | import asyncio
6 | import motor
7 |
8 | from config import mongodb_cfg
9 | from dtlib.mongo.utils import set_mongo_ttl_index
10 |
11 |
12 | async def init_mongo():
13 | """
14 | 不能在多核心状态进行设置
15 | :return:
16 | """
17 | mongo_conn = motor.MotorClient(
18 | host=mongodb_cfg.host,
19 | port=mongodb_cfg.port,
20 | )
21 | db = mongo_conn[mongodb_cfg.db_name]
22 |
23 | res = await db.authenticate(
24 | mongodb_cfg.user_name,
25 | mongodb_cfg.user_pwd,
26 | )
27 | if res is True:
28 | log_session = mongo_conn.log_session
29 | index_filed_name = 'last_use_time'
30 | ttl_seconds = 60 * 60 * 48 # 2days
31 | await set_mongo_ttl_index(log_session, index_filed_name, ttl_seconds)
32 | print('set session ttl succeed...')
33 |
34 |
35 | if __name__ == '__main__':
36 | init_loop = asyncio.get_event_loop()
37 | init_loop.run_until_complete(init_mongo())
38 | # init_loop.stop()
39 | print('init app succeed')
40 |
--------------------------------------------------------------------------------
/myapplication.py:
--------------------------------------------------------------------------------
1 | from tornado.web import Application
2 |
3 | from dtlib.tornado.const_data import FieldDict
4 | from dtlib.web.valuedict import ValueDict
5 |
6 | commentKeep_ValueDict = ValueDict(0, '')
7 |
8 |
9 | class MyApplication(Application):
10 | """
11 | 加入一些自定义的应用
12 | """
13 |
14 | def set_async_redis(self, async_rc_pool):
15 | """
16 | 设置连接池
17 | :param async_rc_pool:
18 | :return:
19 | """
20 | self.settings[FieldDict.key_async_redis_pool] = async_rc_pool
21 | pass
22 |
23 | def set_sync_redis(self, sync_rc_pool):
24 | """
25 | 获取同步类型的redis的连接池
26 | :return:
27 | """
28 | self.settings[FieldDict.key_sync_redis_pool] = sync_rc_pool
29 | pass
30 |
31 | def set_async_mongo(self, async_mongo_pool):
32 | """
33 | 异步的mongo连接池
34 | :param async_mongo_pool:
35 | :return:
36 | """
37 | self.settings[FieldDict.key_async_mongo_pool] = async_mongo_pool
38 |
--------------------------------------------------------------------------------
/nginx_config/test-api.conf:
--------------------------------------------------------------------------------
1 | #demo of nginx config
2 | # the upstream component nginx needs to connect to
3 |
4 | upstream gtt-webapi{
5 | server 127.0.0.1:8011;
6 | }
7 |
8 |
9 | # configuration of the server
10 | server {
11 | # the port your site will be served on
12 | listen 8009;
13 | # the domain name it will serve for
14 | server_name 127.0.0.1; # substitute your machine's IP address or FQDN
15 | charset utf-8;
16 | # max upload size
17 | client_max_body_size 75M; # adjust to taste
18 |
19 | sendfile on;
20 | keepalive_timeout 0;
21 |
22 |
23 | location / {
24 |
25 | if ($request_method = 'OPTIONS') {
26 | add_header 'Access-Control-Allow-Origin' '*';
27 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
28 | #
29 | # Custom headers and headers various browsers *should* be OK with but aren't
30 | #
31 | add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
32 | #
33 | # Tell client that this pre-flight info is valid for 20 days
34 | #
35 | add_header 'Access-Control-Max-Age' 1728000;
36 | add_header 'Content-Type' 'text/plain charset=UTF-8';
37 | add_header 'Content-Length' 0;
38 | return 204;
39 | }
40 |
41 | proxy_pass_header Server;
42 | proxy_set_header Host $http_host;
43 | proxy_redirect off;
44 | proxy_set_header X-Real-IP $remote_addr;
45 | proxy_set_header X-Scheme $scheme;
46 | proxy_pass http://gtt-webapi;
47 | add_header Access-Control-Allow-Origin *;
48 | add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
49 | #add_header Access-Control-Allow-Credentials true;
50 | }
51 |
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/nginx_config/test.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 8099;
3 | server_name 192.168.1.200;
4 | root /www/xtest-web/dist/;
5 |
6 | location ~ .*\.(jpg|jpeg|png|webp)$ {
7 | add_header "Access-Control-Allow-Origin" "*";
8 | add_header "Access-Control-Allow-Headers" "X-Requested-With";
9 | add_header "Access-Control-Allow-Methods" "GET,POST,OPTIONS";
10 | expires 7d;
11 | }
12 | location ~ .*\.(js|css)?$
13 | {
14 | expires 1h;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # python package requirements
2 |
3 | requests==2.18.4
4 |
5 | #mongodb
6 | motor==1.2.1
7 |
8 | tornado==4.3
9 | #torndsession==1.1.4
10 |
11 | tabulate==0.8.2
12 |
13 | # xtest base
14 | https://github.com/ityoung/dtlib/archive/master.zip
15 |
--------------------------------------------------------------------------------
/route.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from tornado.web import StaticFileHandler
4 |
5 | # from config import *
6 | from config import settings, BASE_DIR
7 | from dtlib.tornado.tools import get_apps_url
8 |
9 | urls = get_apps_url(BASE_DIR)
10 |
11 | try:
12 | from xt_wechat.urls import url
13 | # TODO: set online flag.
14 | urls += url
15 | except:
16 | pass
17 |
18 | urls += [
19 | (r"/favicon.ico", StaticFileHandler, {"path": "/static/favicon.ico"}),
20 | (r"/js/(.*)", StaticFileHandler, {"path": os.path.join(settings['static_path'], 'js')}),
21 | (r"/css/(.*)", StaticFileHandler, {"path": os.path.join(settings['static_path'], 'css')}),
22 | (r"/img/(.*)", StaticFileHandler, {"path": os.path.join(settings['static_path'], 'img')}),
23 | (r"/content/(.*)", StaticFileHandler, {"path": os.path.join(settings['static_path'], 'content')}),
24 |
25 | ]
26 |
27 | # todo 后续写成 include的方式,更直观
28 |
--------------------------------------------------------------------------------
/service.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | exe_project_name="xt-server-api"
3 | python_file_name="start.py"
4 |
5 | #shell code
6 | exe_file_folder="/www/$exe_project_name"
7 | exe_file_tag="$exe_project_name/$python_file_name"
8 | exe_file_path="$exe_file_folder/$python_file_name"
9 | log_path="/var/log/$exe_project_name/$exe_project_name.log"
10 |
11 | case "$1" in
12 | start)
13 | echo "start $exe_project_name"
14 | cd $exe_file_folder
15 | nohup python $exe_file_path 1>$log_path 2>$log_path.error &
16 | echo $exe_file_path
17 | echo $log_path
18 | echo $log_path.error
19 | ;;
20 | restart)
21 | echo "kill $exe_project_name"
22 | ps -ef|grep python |grep $exe_file_tag|awk '{print $2}'|xargs kill -9
23 |
24 | echo "start $exe_project_name"
25 | cd $exe_file_folder
26 | nohup python $exe_file_path 1>$log_path 2>$log_path.error &
27 | echo $exe_file_path
28 | echo $log_path
29 | echo $log_path.error
30 | ;;
31 | stop)
32 | echo "kill $exe_project_name"
33 | ps -ef|grep python |grep $exe_file_tag|awk '{print $2}'|xargs kill -9
34 | ;;
35 | esac
--------------------------------------------------------------------------------
/start.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 | """
3 |
4 | - 可以使用tornado server
5 | - 修改ioloop绑定后,可多核启动
6 |
7 | """
8 |
9 | import asyncio
10 | import logging
11 |
12 | import motor.motor_asyncio
13 | from tabulate import tabulate
14 |
15 | import config
16 | from config import HTTP_PORT, settings, mongodb_cfg, LOGGING_LEVEL, debug_mode, SERVER_PROCESS
17 | from dtlib.dtlog import dlog
18 | from dtlib.tornado.async_server import MyAsyncHttpServer
19 | from route import urls
20 | from myapplication import MyApplication
21 |
22 |
23 | def output_server_info():
24 | """
25 | 输出服务器相关配置信息
26 | :param kwargs:
27 | :return:
28 | """
29 |
30 | tbl_out = tabulate([
31 | ['app_version', config.app_version],
32 | ['http_port', config.HTTP_PORT],
33 | ['process_num ', config.SERVER_PROCESS],
34 | ['db_server', config.mongodb_cfg.host]
35 | ],
36 | tablefmt='grid')
37 | print(tbl_out)
38 | print('http://127.0.0.1:%s' % config.HTTP_PORT)
39 |
40 |
41 | async def auth_mongodb(**kwargs):
42 | """
43 | 异步身份认证mongodb
44 | :type future:asyncio.Future
45 | :param kwargs:
46 | :return:
47 | """
48 | mongo_conn = kwargs.get('mongo_conn', None)
49 |
50 | if mongo_conn is None:
51 | raise ValueError
52 |
53 | res = await mongo_conn.authenticate(
54 | mongodb_cfg.user_name,
55 | mongodb_cfg.user_pwd,
56 | )
57 | if res is True:
58 | print('mongodb authenticate succeed...')
59 |
60 | async def auth_mongo(**kwargs):
61 |
62 | db = kwargs.get('db', None)
63 |
64 | if mongo_conn is None:
65 | raise ValueError
66 | res = await db.authenticate(
67 | mongodb_cfg.user_name,
68 | mongodb_cfg.user_pwd,
69 | )
70 |
71 | if res is True:
72 | print('mongodb authenticate succeed...')
73 |
74 | if __name__ == '__main__':
75 | # 设置日志输出级别
76 | dlog.setLevel(LOGGING_LEVEL)
77 | logging.getLogger('asyncio').setLevel(logging.WARNING)
78 |
79 | # 输出运行服务的相关信息
80 | output_server_info()
81 |
82 | app = MyApplication(
83 | urls,
84 | **settings
85 | )
86 |
87 | server = MyAsyncHttpServer(
88 | app,
89 | xheaders=True # when running behind a load balancer like nginx
90 | )
91 | server.bind(HTTP_PORT) # 设置端口
92 | server.start(num_processes=SERVER_PROCESS) # if 0,forks one process per cpu,#TORNADO_SERVER_PROCESS
93 |
94 | # asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) # 使用uvloop来进行loop替换
95 | io_loop = asyncio.get_event_loop()
96 | io_loop.set_debug(debug_mode)
97 |
98 | # 设置Mongodb
99 | # you only need to keep track of the DB instance if you connect to multiple databases.
100 | mongo_conn = motor.motor_asyncio.AsyncIOMotorClient(mongodb_cfg.host,mongodb_cfg.port)
101 | # mongo_conn.max_pool_size = mongodb_cfg.max_connections # mongodb连接池最大连接数
102 | db=mongo_conn.xtest
103 | io_loop.run_until_complete(auth_mongo(db=db)) # 数据库认证
104 |
105 | # 设置异步mongo连接,方便后续直接非ORM操作,比如请求大量的数据
106 | app.set_async_mongo(db)
107 |
108 | io_loop.run_forever()
109 |
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/x-utest/xtest-server/4d7c05266dd42ebf4a1ddc416eb615410b40b5fb/static/favicon.ico
--------------------------------------------------------------------------------
/static/templates/code/argument.codetpl:
--------------------------------------------------------------------------------
1 |
2 | #路由值
3 | (r"/%s/create-{{low_cls_name}}/" % app_path, Create{{cls_name}}),
4 | (r"/%s/read-{{low_cls_name}}/" % app_path, Read{{cls_name}}),
5 | (r"/%s/update-{{low_cls_name}}/" % app_path, Update{{cls_name}}),
6 | (r"/%s/delete-{{low_cls_name}}/" % app_path, Delete{{cls_name}}),
7 | (r"/%s/list-{{low_cls_name}}/" % app_path, List{{cls_name}}),#分页列表
8 |
9 | #region {{cls_name}} CRUBL
10 |
11 | class Create{{cls_name}}(BaseHandler):
12 | """
13 | 创建{{cls_name}}
14 | """
15 | def get(self):
16 | self.write('get')
17 |
18 | @cor_login_required
19 | @coroutine
20 | def post(self):
21 | #表单提取
22 | {%for x in line%}
23 | {{x}} = self.get_argument("{{x}}",None){%end%}
24 |
25 | #空值检查
26 | if list_have_none_mem(*[{%for x in line%}{{x}},{%end%}]) is True:
27 | raise Return(ConstData.msg_args_wrong)
28 |
29 | #数据表赋值
30 | doc_obj = {{cls_name}}()
31 | {%for x in line%}
32 | doc_obj.{{x}} = {{x}}{%end%}
33 | doc_obj.set_default_rc_tag()
34 | yield doc_obj.save()
35 |
36 | raise Return(ConstData.msg_succeed)
37 |
38 |
39 | class Read{{cls_name}}(BaseHandler):
40 | """
41 | 创建{{cls_name}}
42 | """
43 | def get(self):
44 | self.write('get')
45 |
46 | def post(self):
47 | self.write('post')
48 |
49 |
50 | class Update{{cls_name}}(BaseHandler):
51 | """
52 | 更新{{cls_name}}
53 | """
54 | def get(self):
55 | self.write('get')
56 |
57 | def post(self):
58 | self.write('post')
59 |
60 |
61 | class Delete{{cls_name}}(BaseHandler):
62 | """
63 | 删除{{cls_name}}
64 | """
65 | def get(self):
66 | self.write('get')
67 |
68 | def post(self):
69 | self.write('post')
70 |
71 | class List{{cls_name}}(BaseHandler):
72 | """
73 | 分页列表{{cls_name}}
74 | """
75 | def get(self):
76 | self.write('get')
77 |
78 | def post(self):
79 | self.write('post')
80 |
81 |
82 | #endregion
83 |
84 |
85 | #region 接口测试脚本
86 |
87 | class {{cls_name}}ApiTest(BaseTest):
88 | """
89 | 本项目的重要的接口测试脚本,需要登录的
90 | """
91 |
92 | def test_create_{{underline_cls_name}}(self):
93 | """
94 | """
95 | url = "%s/%s/create-{{low_cls_name}}/" % (self.base_url, app_path)
96 |
97 | params = dict({%for x in line%}
98 | {{x}} = 'xxx',{%end%}
99 | )
100 |
101 | res = self.se_req.post(url, data=params)
102 | dlog.debug(res.text)
103 | res_dict = json.loads(res.text)
104 | self.assertEqual(res_dict['code'], 200)
105 |
106 | def test_read_{{underline_cls_name}}(self):
107 | """
108 | """
109 | url = "%s/%s/read-{{low_cls_name}}/" % (self.base_url, app_path)
110 | self.assertTrue(True)
111 |
112 | def test_update_{{underline_cls_name}}(self):
113 | """
114 | """
115 | url = "%s/%s/update-{{low_cls_name}}/" % (self.base_url, app_path)
116 |
117 | params = dict({%for x in line%}
118 | {{x}} = 'xxx',{%end%}
119 | )
120 |
121 | res = self.se_req.post(url, data=params)
122 | dlog.debug(res.text)
123 | res_dict = json.loads(res.text)
124 | self.assertEqual(res_dict['code'], 200)
125 |
126 |
127 | def test_delete_{{underline_cls_name}}(self):
128 | """
129 | """
130 | url = "%s/%s/delete-{{low_cls_name}}/" % (self.base_url, app_path)
131 |
132 | params = dict({%for x in line%}
133 | {{x}} = 'xxx',{%end%}
134 | )
135 |
136 | res = self.se_req.post(url, data=params)
137 | dlog.debug(res.text)
138 | res_dict = json.loads(res.text)
139 | self.assertEqual(res_dict['code'], 200)
140 |
141 | def test_list_{{underline_cls_name}}(self):
142 | """
143 | """
144 | url = "%s/%s/list-{{low_cls_name}}/" % (self.base_url, app_path)
145 | self.assertTrue(True)
146 |
147 |
148 | #endregion
149 |
150 |
151 | #前端分页代码
152 |
153 |
154 | {%for x in line%}
155 |
157 |
158 | {{x}} {%end%}
156 |