Skip to content

Commit

Permalink
添加记录访客的功能
Browse files Browse the repository at this point in the history
  • Loading branch information
githublitao committed Jun 30, 2018
1 parent 30a68d8 commit 7c62bee
Show file tree
Hide file tree
Showing 24 changed files with 395 additions and 144 deletions.
3 changes: 2 additions & 1 deletion .gitattributes
@@ -1,3 +1,4 @@
*.js linguist-language=Python
*.css linguist-language=Python
*.html linguist-language=Python
*.html linguist-language=Python
*.vue linguist-language=Python
26 changes: 13 additions & 13 deletions api_automation_test/settings.py
Expand Up @@ -160,28 +160,28 @@
# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'api_test',
'USER': 'root',
'PASSWORD': '123456',
'HOST': '192.168.193.129',
'PORT': '3306',
}
}

# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.mysql',
# 'NAME': 'api_test',
# 'USER': 'root',
# 'PASSWORD': 'lt19910301',
# 'HOST': '172.18.162.26',
# 'PASSWORD': '123456',
# 'HOST': '192.168.193.129',
# 'PORT': '3306',
# }
# }

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'api_test',
'USER': 'root',
'PASSWORD': 'lt19910301',
'HOST': '172.18.162.26',
'PORT': '3306',
}
}

# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.mysql',
Expand Down
29 changes: 26 additions & 3 deletions api_test/admin.py
Expand Up @@ -93,6 +93,28 @@ def has_delete_permission(self, request, obj=None):
return False


class ReadAndDeleteModelAdmin(admin.ModelAdmin):
"""ModelAdmin class that prevents modifications through the admin.
The changelist and the detail view work, but a 403 is returned
if one actually tries to edit an object.
"""

actions = None

def get_readonly_fields(self, request, obj=None):
return self.fields or [f.name for f in self.model._meta.fields]

def has_add_permission(self, request):
return False

# Allow viewing objects but not actually changing them
def has_change_permission(self, request, obj=None):
if request.method not in ('GET', 'HEAD'):
return True
return super(ReadAndDeleteModelAdmin, self).has_change_permission(request, obj)


class MemberInProject(admin.TabularInline):
model = ProjectMember

Expand Down Expand Up @@ -392,9 +414,10 @@ class AutomationReportSendConfigForm(ReadOnlyModelAdmin):
admin.site.register(AutomationReportSendConfig, AutomationReportSendConfigForm)


class VisitorsRecordForm(admin.ModelAdmin):
list_display = ('id', 'host', 'callTime')
list_display_links = ('id', 'host', 'callTime')
class VisitorsRecordForm(ReadAndDeleteModelAdmin):
search_fields = ('province', 'city', 'district')
list_display = ('id', 'formattedAddress', "country", "province", "city", "district", 'callTime')
list_display_links = ('id', 'formattedAddress', "country", "province", "city", "district", 'callTime')
list_per_page = 20
ordering = ('-id',)

Expand Down
112 changes: 112 additions & 0 deletions api_test/api/VisitorRecord.py
@@ -0,0 +1,112 @@
import logging

import requests
from rest_framework.parsers import JSONParser
from rest_framework.views import APIView

from api_test.common.api_response import JsonResponse
from api_test.models import VisitorsRecord

logger = logging.getLogger(__name__) # 这里使用 __name__ 动态搜索定义的 logger 配置,这里有一个层次关系的知识点。


class Record(APIView):
# authentication_classes = (TokenAuthentication,)
permission_classes = ()

def parameter_check(self, data):
"""
校验参数
:param data:
:return:
"""
try:
# 必传参数 success
if data["success"] not in [0, 1]:
return JsonResponse(code="999996", msg="参数有误!")
except KeyError:
return JsonResponse(code="999996", msg="参数有误!")

def ip_get_city(self, ip):
"""
通过ip获取城市信息
:param ip:
:return:
"""
params = {
"output": "json",
# "location": "104.06151"+","+"30.54852",
"key": "2200d7985fd43582411687abaa5b01eb",
"ip": ip
}
headers = {"Content-Type": "application/json;charset=utf-8"}
response = requests.get(url="http://restapi.amap.com/v3/ip",
params=params, headers=headers, allow_redirects=False,
timeout=8)
if response.status_code == 301:
response = requests.get(url=response.headers["location"])
return response.json()

def post(self, request):
"""
记录访客
:param request:
:return:
"""
if request.environ["REMOTE_ADDR"] == "127.0.0.1":
return JsonResponse(code="999999", msg="成功!")
data = JSONParser().parse(request)
result = self.parameter_check(data)
if result:
return result
if data["success"] == 0:
ip = self.ip_get_city(request.environ["REMOTE_ADDR"])
VisitorsRecord(formattedAddress=request.environ["REMOTE_ADDR"], province=ip["province"],
city=ip["city"],
success="失败", reason="获取用户经纬度失败!").save()
else:
try:
longitude = data["longitude"]
latitude = data["latitude"]
except KeyError:
return JsonResponse(code="999996", msg="参数有误")
params = {
"output": "json",
# "location": "104.06151"+","+"30.54852",
"key": "2200d7985fd43582411687abaa5b01eb",
"location": str(longitude)+","+str(latitude)
}
headers = {"Content-Type": "application/json;charset=utf-8"}
response = requests.get(url="http://restapi.amap.com/v3/geocode/regeo",
params=params, headers=headers, allow_redirects=False,
timeout=8)
if response.status_code == 301:
response = requests.get(url=response.headers["location"])
try:
visitor_addr = response.json()
if visitor_addr["status"] == "1":
VisitorsRecord(
formattedAddress=visitor_addr["regeocode"]["formatted_address"],
country=visitor_addr["regeocode"]["addressComponent"]["country"],
province=visitor_addr["regeocode"]["addressComponent"]["province"],
city=visitor_addr["regeocode"]["addressComponent"]["city"],
district=visitor_addr["regeocode"]["addressComponent"]["district"],
township=visitor_addr["regeocode"]["addressComponent"]["township"],
street=visitor_addr["regeocode"]["addressComponent"]["streetNumber"]["street"],
number=visitor_addr["regeocode"]["addressComponent"]["streetNumber"]["number"],
success="成功",
reason=visitor_addr["info"]
).save()
else:
ip = self.ip_get_city(request.environ["REMOTE_ADDR"])
VisitorsRecord(formattedAddress=request.environ["REMOTE_ADDR"], province=ip["province"],
city=ip["city"],
success="失败", reason=ip["info"]).save()
except Exception as e:
ip = self.ip_get_city(request.environ["REMOTE_ADDR"])
VisitorsRecord(formattedAddress=request.environ["REMOTE_ADDR"], province=ip["province"],
city=ip["city"],
success="失败", reason=e).save()

return JsonResponse(code="999999", msg="成功!")

2 changes: 0 additions & 2 deletions api_test/api/user.py
Expand Up @@ -3,7 +3,6 @@
from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework.views import APIView

from api_test.models import VisitorsRecord
from api_test.serializers import TokenSerializer
from api_test.common.api_response import JsonResponse

Expand All @@ -21,7 +20,6 @@ def post(self, request, *args, **kwargs):
serializer.is_valid(raise_exception=True)
user = serializer.validated_data["user"]
# token, created = Token.objects.get_or_create(user=user)
VisitorsRecord(host=request.environ["REMOTE_ADDR"]).save()
data = TokenSerializer(Token.objects.get(user=user)).data
data["userphoto"] = '/file/userphoto.jpg'
return JsonResponse(data=data, code="999999", msg="成功")
Expand Down
4 changes: 3 additions & 1 deletion api_test/common/confighttp.py
Expand Up @@ -272,7 +272,9 @@ def get(header, address, request_parameter_type, data):
"""
if request_parameter_type == 'raw':
data = json.dumps(data)
response = requests.get(url=address, params=data, headers=header, timeout=8)
response = requests.get(url=address, params=data, headers=header, timeout=8, allow_redirects=False)
if response.status_code == 301:
response = requests.get(url=response.headers["location"])
try:
return response.status_code, response.json()
except json.decoder.JSONDecodeError:
Expand Down
13 changes: 11 additions & 2 deletions api_test/models.py
Expand Up @@ -655,11 +655,20 @@ class VisitorsRecord(models.Model):
访客记录
"""
id = models.AutoField(primary_key=True)
host = models.CharField(max_length=50, blank=True, null=True, verbose_name="访客地址")
formattedAddress = models.CharField(max_length=1024, blank=True, null=True, verbose_name="访客地址")
country = models.CharField(max_length=50, blank=True, null=True, verbose_name="国家")
province = models.CharField(max_length=50, blank=True, null=True, verbose_name="省份")
city = models.CharField(max_length=50, blank=True, null=True, verbose_name="城市")
district = models.CharField(max_length=50, blank=True, null=True, verbose_name="县级")
township = models.CharField(max_length=50, blank=True, null=True, verbose_name="镇")
street = models.CharField(max_length=50, blank=True, null=True, verbose_name="街道")
number = models.CharField(max_length=50, blank=True, null=True, verbose_name="门牌号")
success = models.CharField(max_length=50, blank=True, null=True, verbose_name="成功")
reason = models.CharField(max_length=1024, blank=True, null=True, verbose_name="原因")
callTime = models.DateTimeField(auto_now_add=True, verbose_name="访问时间")

def __unicode__(self):
return self.host
return self.formattedAddress

class Meta:
verbose_name = "访客"
Expand Down
3 changes: 2 additions & 1 deletion api_test/urls.py
@@ -1,6 +1,6 @@
from django.conf.urls import url

from api_test.api import ApiDoc, automationCase as Case, member, dynamic, user
from api_test.api import ApiDoc, automationCase as Case, member, dynamic, user, VisitorRecord
from api_test.api import automationReport as Report
from api_test.api.global_parameter import HostTotal, AddHost, UpdateHost, DelHost, DisableHost, EnableHost
from api_test.api.projectList import ProjectList, AddProject, DelProject, \
Expand Down Expand Up @@ -70,4 +70,5 @@
url(r'member/get_email', member.GetEmail.as_view()),
url(r'dynamic/dynamic', dynamic.Dynamic.as_view()),
url(r'user/login', user.obtain_auth_token),
url(r'user/VisitorRecord', VisitorRecord.Record.as_view()),
]
90 changes: 47 additions & 43 deletions frontend/build/webpack.base.conf.js
Expand Up @@ -22,50 +22,54 @@ module.exports = {
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'scss_vars': '@/styles/vars.scss'
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
},
{
test: /element-ui.src.*?js$/,
loader: 'babel-loader'
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'scss_vars': '@/styles/vars.scss'
}
]
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
},
{
test: /element-ui.src.*?js$/,
loader: 'babel-loader'
}
]
},
// externals: {
// 'AMap': 'AMap',
// 'AMapUI': 'AMapUI'
// },
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
]
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
]
}
2 changes: 1 addition & 1 deletion frontend/dist/index.html
@@ -1 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><title>自动化测试平台</title><link href=/static/css/app.3a1563fcc9524db67e54f91a07474cdc.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/static/js/manifest.e9fecef20dfb3f85963f.js></script><script type=text/javascript src=/static/js/vendor.00ebef1d9e9c19674cd1.js></script><script type=text/javascript src=/static/js/app.e113befbeb3f47b05d96.js></script></body></html>
<!DOCTYPE html><html><head><meta charset=utf-8><title>自动化测试平台</title><script type=text/javascript src="http://webapi.amap.com/maps?v=1.4.6&key=2200d7985fd43582411687abaa5b01eb"></script><link href=/static/css/app.3a1563fcc9524db67e54f91a07474cdc.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/static/js/manifest.81b59ff2778a95208898.js></script><script type=text/javascript src=/static/js/vendor.00ebef1d9e9c19674cd1.js></script><script type=text/javascript src=/static/js/app.d5f5bc29aa454af6c0d4.js></script></body></html>
2 changes: 2 additions & 0 deletions frontend/dist/static/js/app.d5f5bc29aa454af6c0d4.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions frontend/dist/static/js/app.d5f5bc29aa454af6c0d4.js.map

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions frontend/dist/static/js/app.e113befbeb3f47b05d96.js

This file was deleted.

1 change: 0 additions & 1 deletion frontend/dist/static/js/app.e113befbeb3f47b05d96.js.map

This file was deleted.

2 changes: 2 additions & 0 deletions frontend/dist/static/js/manifest.81b59ff2778a95208898.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7c62bee

Please sign in to comment.