优化微信消息推送模板,管理后台增加微信绑定功能

publicsignpoll
youngS 3 years ago
parent 820e1df84e
commit 0bac3e55f4
  1. 25
      fir_admin/src/api/wxbind.js
  2. 19
      fir_admin/src/router/index.js
  3. 2
      fir_admin/src/views/domain/list.vue
  4. 152
      fir_admin/src/views/wxbind/Detail.vue
  5. 201
      fir_admin/src/views/wxbind/list.vue
  6. 3
      fir_ser/admin/urls.py
  7. 3
      fir_ser/admin/views/login.py
  8. 60
      fir_ser/admin/views/user.py
  9. 12
      fir_ser/api/utils/auth.py
  10. 7
      fir_ser/api/utils/baseutils.py
  11. 12
      fir_ser/api/utils/geetest/geetest_utils.py
  12. 228
      fir_ser/api/utils/mp/wechat.py
  13. 9
      fir_ser/api/utils/serializer.py
  14. 3
      fir_ser/api/views/domain.py
  15. 7
      fir_ser/api/views/download.py
  16. 16
      fir_ser/api/views/login.py
  17. 96
      fir_ser/api/views/thirdlogin.py

@ -0,0 +1,25 @@
import request from '@/utils/request'
export function getWxBindInfos(query) {
return request({
url: '/wxbind/info',
method: 'get',
params: query
})
}
export function updateWxBindInfo(data) {
return request({
url: '/wxbind/info',
method: 'put',
data
})
}
export function deleteWxBind(data) {
return request({
url: '/wxbind/info',
method: 'delete',
data
})
}

@ -194,6 +194,25 @@ export const constantRoutes = [
} }
] ]
}, },
{
path: '/wxbind',
component: Layout,
children: [
{
path: 'list',
name: 'wxbind_info_list',
component: () => import('@/views/wxbind/list'),
meta: { title: '微信绑定管理', icon: 'form' }
},
{
path: 'edit/:id(\\d+)',
component: () => import('@/views/wxbind/Detail'),
name: 'wxbind_info_edit',
meta: { title: '编辑信息', noCache: true, activeMenu: '/wxbind/list' },
hidden: true
}
]
},
{ {
path: '/supersign', path: '/supersign',
component: Layout, component: Layout,

@ -38,7 +38,7 @@
</router-link> </router-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="下载域名" min-width="280px"> <el-table-column label="下载域名" min-width="280px" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
{{ scope.row.domain_name }} {{ scope.row.domain_name }}
</template> </template>

@ -0,0 +1,152 @@
<template>
<div class="app-container">
<el-form ref="postForm" :model="postForm" label-width="100px" :disabled="!is_edit">
<el-row>
<el-col :span="12">
<el-form-item label="用户ID">
<el-row :gutter="12">
<el-col :span="16">
<el-input :value="postForm.user_id" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="微信openid">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.openid" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="微信昵称">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.nickname" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="地址">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.address" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="创建时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.created_time" type="datetime" disabled />
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="用户头像" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-image :src="postForm.head_img_url" :preview-src-list="[postForm.head_img_url]" fit="contain" style="width: 100px; height: 100px" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="订阅状态" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.subscribe" class="filter-item">
<el-option v-for="item in wxbind_state_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="性别" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.sex" class="filter-item">
<el-option v-for="item in wxbind_sex_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-col :span="9" style="float: right">
<el-button v-if="!is_edit" type="primary" :disabled="postForm.id === postForm.used_id" @click="is_edit=true">修改</el-button>
<div v-else>
<el-button type="primary" @click="is_edit=false">取消</el-button>
<el-button type="primary" @click="updateData">保存修改</el-button>
</div>
</el-col>
</div>
</template>
<script>
import { getWxBindInfos, updateWxBindInfo } from '@/api/wxbind'
const wxbind_state_choices = [
{ id: false, name: '未订阅' },
{ id: true, name: '已订阅' }
]
const wxbind_sex_choices = [
{ id: 0, name: '未知' },
{ id: 1, name: '男' },
{ id: 2, name: '女' }
]
const defaultForm = {
user_id: undefined,
address: undefined,
created_time: undefined,
head_img_url: undefined,
id: undefined,
nickname: undefined,
openid: undefined,
sex: undefined,
subscribe: undefined,
subscribe_time: undefined
}
export default {
name: 'OrderDetail',
components: { }, filters: {},
data() {
return {
postForm: Object.assign({}, defaultForm),
loading: false,
is_edit: false,
wxbind_state_choices,
wxbind_sex_choices
}
},
computed: {},
created() {
const id = this.$route.params && this.$route.params.id
this.fetchData(id)
},
methods: {
fetchData(id) {
getWxBindInfos({ id: id }).then(response => {
if (response.data.length === 1) {
this.postForm = response.data[0]
}
}).catch(err => {
console.log(err)
})
},
updateData() {
updateWxBindInfo(this.postForm).then(response => {
this.$message.success('更新成功')
this.postForm = response.data
}).catch(err => {
console.log(err)
})
}
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,201 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.user_id" placeholder="用户ID" style="width: 140px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.openid" placeholder="微信openid" style="width: 300px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.nickname" placeholder="微信昵称" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-select v-model="listQuery.subscribe" placeholder="玩家是否订阅" clearable class="filter-item" style="width: 140px" @change="handleFilter">
<el-option v-for="item in wxbind_state_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.sort" style="width: 140px" class="filter-item" @change="handleFilter">
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
Search
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
stripe
>
<el-table-column align="center" label="ID" width="90">
<template slot-scope="scope">
{{ scope.row.id }}
</template>
</el-table-column>
<el-table-column label="用户ID" width="100" align="center">
<template slot-scope="scope">
<router-link :to="{name: 'user_info_edit',params:{id:scope.row.user_id}}">
<el-link type="primary"> {{ scope.row.user_id }}</el-link>
</router-link>
</template>
</el-table-column>
<el-table-column label="头像" align="center">
<template slot-scope="scope">
<el-image :src="scope.row.head_img_url" :preview-src-list="[scope.row.head_img_url]" fit="contain" style="width: 80px; height: 80px" />
</template>
</el-table-column>
<el-table-column label="微信昵称" min-width="180px" align="center">
<template slot-scope="scope">
{{ scope.row.nickname }}
</template>
</el-table-column>
<el-table-column label="微信openid" min-width="280px" align="center">
<template slot-scope="scope">
{{ scope.row.openid }}
</template>
</el-table-column>
<el-table-column label="地址" min-width="200px" align="center">
<template slot-scope="scope">
{{ scope.row.address }}
</template>
</el-table-column>
<el-table-column label="性别" min-width="60px" align="center">
<template slot-scope="scope">
{{ scope.row |sexLableFilter }}
</template>
</el-table-column>
<el-table-column class-name="status-col" label="订阅状态" width="95" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.subscribe | statusFilter">{{ scope.row| statusLableFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="created_time" label="创建时间" width="120">
<template slot-scope="scope">
<i class="el-icon-time" />
<el-tooltip :content="scope.row.created_time">
<span>{{ scope.row.created_time|formatTime }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template slot-scope="scope">
<router-link :to="{name: 'wxbind_info_edit',params:{id:scope.row.id}}">
<el-button type="primary" size="mini">
查看编辑
</el-button>
</router-link>
<el-button type="danger" size="mini" @click="remove_wxbind(scope.row.id)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="fetchData" />
</div>
</template>
<script>
import { getWxBindInfos, deleteWxBind } from '@/api/wxbind'
import { baseFilter } from '@/utils'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive
const sortOptions = [
{ label: '创建时间 Ascending', key: 'created_time' },
{ label: '创建时间 Descending', key: '-created_time' }
]
const wxbind_state_choices = [
{ id: false, name: '未订阅' },
{ id: true, name: '已订阅' }
]
const wxbind_sex_choices = [
{ id: 0, name: '未知' },
{ id: 1, name: '男' },
{ id: 2, name: '女' }
]
export default {
name: 'WxBindInfo',
components: { Pagination },
directives: { waves },
filters: {
formatTime(time) {
if (time) {
return time.split('T')[0]
}
},
wxbindStatusFilter(status) {
const statusMap = {
'1': 'success',
'0': 'gray',
'2': 'info' }
return statusMap[status]
},
wxbindLableFilter(row) {
if (row.wxbind_type === 2) {
return '应用' + row.app_info.name + '域名'
}
return baseFilter(row.wxbind_type, row.wxbind_type_choices)
},
statusLableFilter(row) {
return baseFilter(row.subscribe, wxbind_state_choices)
},
sexLableFilter(row) {
return baseFilter(row.sex, wxbind_sex_choices)
},
statusFilter(status) {
const statusMap = {
true: 'success',
false: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
page: 1,
limit: 10,
user_id: undefined,
sort: '-created_time',
openid: undefined,
nickname: undefined,
subscribe: undefined
},
sortOptions,
wxbind_state_choices
}
},
created() {
this.fetchData()
}, mounted() {
if (this.$route.params.user_id) {
this.listQuery.user_id = this.$route.params.user_id
}
console.log(this.$router)
},
methods: {
remove_wxbind(wxbind_id) {
deleteWxBind({ id: wxbind_id }).then(response => {
this.list = response.data
this.total = response.total
this.listLoading = false
})
},
handleFilter() {
this.listQuery.page = 1
this.fetchData()
},
fetchData() {
this.listLoading = true
getWxBindInfos(this.listQuery).then(response => {
this.list = response.data
this.total = response.total
this.listLoading = false
})
}
}
}
</script>

@ -15,7 +15,7 @@ Including another URLconf
""" """
from django.urls import re_path from django.urls import re_path
from admin.views.login import LoginView, LoginUserView from admin.views.login import LoginView, LoginUserView
from admin.views.user import UserInfoView, UserCertificationInfoView from admin.views.user import UserInfoView, UserCertificationInfoView, ThirdWxAccountView
from admin.views.app import AppInfoView, AppReleaseInfoView from admin.views.app import AppInfoView, AppReleaseInfoView
from admin.views.storage import StorageInfoView, StorageChangeView from admin.views.storage import StorageInfoView, StorageChangeView
from admin.views.order import OrderInfoView from admin.views.order import OrderInfoView
@ -38,5 +38,6 @@ urlpatterns = [
re_path("^developer/info", DeveloperInfoView.as_view()), re_path("^developer/info", DeveloperInfoView.as_view()),
re_path("^devices/info", DevicesInfoView.as_view()), re_path("^devices/info", DevicesInfoView.as_view()),
re_path("^domain/info", DomainNameInfoView.as_view()), re_path("^domain/info", DomainNameInfoView.as_view()),
re_path("^wxbind/info", ThirdWxAccountView.as_view()),
] ]

@ -5,13 +5,10 @@
# date: 2021/4/11 # date: 2021/4/11
from django.contrib import auth from django.contrib import auth
from api.models import Token, UserInfo
from rest_framework.response import Response from rest_framework.response import Response
from api.utils.auth import ExpiringTokenAuthentication from api.utils.auth import ExpiringTokenAuthentication
from api.utils.serializer import UserInfoSerializer from api.utils.serializer import UserInfoSerializer
from django.core.cache import cache
from rest_framework.views import APIView from rest_framework.views import APIView
import os, datetime
from api.utils.utils import get_captcha, valid_captcha, set_user_token from api.utils.utils import get_captcha, valid_captcha, set_user_token
from api.utils.response import BaseResponse from api.utils.response import BaseResponse
from fir_ser.settings import CACHE_KEY_TEMPLATE, LOGIN from fir_ser.settings import CACHE_KEY_TEMPLATE, LOGIN

@ -5,11 +5,11 @@
# date: 2021/4/11 # date: 2021/4/11
from django.contrib import auth from django.contrib import auth
from api.models import Token, UserInfo, UserCertificationInfo from api.models import Token, UserInfo, UserCertificationInfo, ThirdWeChatUserInfo
from rest_framework.response import Response from rest_framework.response import Response
from api.utils.auth import AdminTokenAuthentication from api.utils.auth import AdminTokenAuthentication
from api.utils.baseutils import get_dict_from_filter_fields from api.utils.baseutils import get_dict_from_filter_fields
from api.utils.serializer import AdminUserInfoSerializer, AdminUserCertificationSerializer from api.utils.serializer import AdminUserInfoSerializer, AdminUserCertificationSerializer, AdminThirdWxSerializer
from django.core.cache import cache from django.core.cache import cache
from rest_framework.views import APIView from rest_framework.views import APIView
import binascii import binascii
@ -25,7 +25,7 @@ from rest_framework.pagination import PageNumberPagination
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class AppsPageNumber(PageNumberPagination): class PageNumber(PageNumberPagination):
page_size = 20 # 每页显示多少条 page_size = 20 # 每页显示多少条
page_size_query_param = 'limit' # URL中每页显示条数的参数 page_size_query_param = 'limit' # URL中每页显示条数的参数
page_query_param = 'page' # URL中页码的参数 page_query_param = 'page' # URL中页码的参数
@ -46,7 +46,7 @@ class UserInfoView(APIView):
filter_data["certification__status__isnull"] = True filter_data["certification__status__isnull"] = True
else: else:
filter_data["certification__status"] = certification filter_data["certification__status"] = certification
page_obj = AppsPageNumber() page_obj = PageNumber()
obj_list = UserInfo.objects.filter(**filter_data).order_by(sort) obj_list = UserInfo.objects.filter(**filter_data).order_by(sort)
page_serializer = page_obj.paginate_queryset(queryset=obj_list, request=request, page_serializer = page_obj.paginate_queryset(queryset=obj_list, request=request,
view=self) view=self)
@ -87,7 +87,7 @@ class UserCertificationInfoView(APIView):
filter_fields = ["id", "card", "name", "status"] filter_fields = ["id", "card", "name", "status"]
filter_data = get_dict_from_filter_fields(filter_fields, request.query_params) filter_data = get_dict_from_filter_fields(filter_fields, request.query_params)
sort = request.query_params.get("sort", "-created_time") sort = request.query_params.get("sort", "-created_time")
page_obj = AppsPageNumber() page_obj = PageNumber()
obj_list = UserCertificationInfo.objects.filter(**filter_data).order_by(sort) obj_list = UserCertificationInfo.objects.filter(**filter_data).order_by(sort)
page_serializer = page_obj.paginate_queryset(queryset=obj_list, request=request, page_serializer = page_obj.paginate_queryset(queryset=obj_list, request=request,
view=self) view=self)
@ -115,3 +115,53 @@ class UserCertificationInfoView(APIView):
res.code = 1004 res.code = 1004
res.msg = "数据校验失败" res.msg = "数据校验失败"
return Response(res.dict) return Response(res.dict)
class ThirdWxAccountView(APIView):
authentication_classes = [AdminTokenAuthentication, ]
def get(self, request):
res = BaseResponse()
filter_fields = ["id", "openid", "nickname", "subscribe", "user_id"]
filter_data = get_dict_from_filter_fields(filter_fields, request.query_params)
sort = request.query_params.get("sort", "-created_time")
page_obj = PageNumber()
obj_list = ThirdWeChatUserInfo.objects.filter(**filter_data).order_by(sort)
page_serializer = page_obj.paginate_queryset(queryset=obj_list, request=request,
view=self)
serializer = AdminThirdWxSerializer(page_serializer, many=True)
res.data = serializer.data
res.total = obj_list.count()
return Response(res.dict)
def put(self, request):
res = BaseResponse()
data = request.data
pk = data.get("id", None)
if not pk:
res.code = 1003
res.msg = "参数错误"
return Response(res.dict)
obj = ThirdWeChatUserInfo.objects.filter(pk=pk).first()
if obj:
data['pk'] = pk
serializer_obj = AdminThirdWxSerializer(obj, data=data, partial=True)
if serializer_obj.is_valid():
serializer_obj.save()
res.data = serializer_obj.data
return Response(res.dict)
res.code = 1004
res.msg = "数据校验失败"
return Response(res.dict)
def delete(self, request):
res = BaseResponse()
data = request.data
pk = data.get("id", None)
if not pk:
res.code = 1003
res.msg = "参数错误"
else:
ThirdWeChatUserInfo.objects.filter(pk=pk).delete()
return self.get(request)
return Response(res.dict)

@ -105,3 +105,15 @@ class SuperSignPermission(BasePermission):
if results and results[0]: if results and results[0]:
return results[0].supersign_active return results[0].supersign_active
return True return True
class DownloadQrPermission(BasePermission):
message = "权限不足"
def has_permission(self, request, view):
domain_type = request.query_params.get('domain_type', -1)
results = get_user_from_request_auth(request)
if results and results[0]:
if domain_type == '0' and results[0].role < 2:
return False
return True

@ -246,3 +246,10 @@ def check_app_domain_name_access(app_obj, access_domain_name, extra_domain=None)
if user_domain_name: domain_list.append(user_domain_name) if user_domain_name: domain_list.append(user_domain_name)
if access_domain_name in domain_list: if access_domain_name in domain_list:
return True return True
def get_real_ip_address(request):
if request.META.get('HTTP_X_FORWARDED_FOR', None):
return request.META.get('HTTP_X_FORWARDED_FOR')
else:
return request.META.get('REMOTE_ADDR')

@ -35,11 +35,17 @@ def check_bypass_status():
# 从缓存中取出当前缓存的bypass状态(success/fail) # 从缓存中取出当前缓存的bypass状态(success/fail)
def get_bypass_cache(): def get_bypass_cache(count=3):
bypass_status_cache = redis_connect.get(GEETEST_BYPASS_STATUS_KEY) bypass_status_cache = redis_connect.get(GEETEST_BYPASS_STATUS_KEY)
if not bypass_status_cache: if bypass_status_cache:
bypass_status_cache = 'fail'
return bypass_status_cache return bypass_status_cache
else:
if count > 0:
check_bypass_status()
count -= 1
return get_bypass_cache(count)
else:
return 'fail'
# 验证初始化接口,GET请求 # 验证初始化接口,GET请求

@ -10,6 +10,7 @@ import json
from django.core.cache import cache from django.core.cache import cache
from api.utils.baseutils import get_format_time
from fir_ser.settings import THIRDLOGINCONF, CACHE_KEY_TEMPLATE from fir_ser.settings import THIRDLOGINCONF, CACHE_KEY_TEMPLATE
from api.utils.mp.utils import WxMsgCryptBase from api.utils.mp.utils import WxMsgCryptBase
@ -65,15 +66,25 @@ def create_menu():
"name": "分发平台", "name": "分发平台",
"sub_button": [ "sub_button": [
{ {
"type": "view", "type": "click",
"name": "官方地址", "name": "官方地址",
"url": "https://flyapps.cn" "key": "flyapps"
}, },
{ {
"type": "view", "type": "view",
"name": "留言反馈", "name": "留言反馈",
"url": "https://flyapps.cn/gbook/" "url": "https://flyapps.cn/gbook/"
}, },
{
"type": "click",
"name": "查询登录绑定",
"key": "query_bind"
},
{
"type": "click",
"name": "解除登录绑定",
"key": "unbind"
},
] ]
}, },
{ {
@ -156,3 +167,216 @@ def check_signature(params):
class WxMsgCrypt(WxMsgCryptBase): class WxMsgCrypt(WxMsgCryptBase):
def __init__(self): def __init__(self):
super().__init__(**wx_login_info.get('auth')) super().__init__(**wx_login_info.get('auth'))
class WxTemplateMsg(object):
def send_msg(self, to_user, template_id, content):
msg_uri = f'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={get_wx_access_token_cache()}'
data = {
"touser": to_user,
"template_id": template_id,
# "url": "http://weixin.qq.com/download",
"topcolor": "#FF0000",
"data": content
}
req = requests.post(msg_uri, json=data)
if req.status_code == 200:
return True, format_req_json(req.json(), get_userinfo_from_openid, to_user)
logger.error(f"send msg from openid failed {req.status_code} {req.text}")
return False, req.text
def login_success_msg(self, to_user, wx_nick_name, username):
"""
您进行了微信扫一扫登录操作
系统帐号yin.xiaogang
登录系统OA系统
登录时间2014-11-28 10:06:32
如有疑问请致电IT服务台400-888-8888 关注公众号在线反馈
:param to_user:
:param wx_nick_name: 微信昵称
:param username: flyapps 用户昵称
:return:
"""
now_time = get_format_time()
msg_id = 'EJhBbxJvHdWnwwexaqb0lCC2sM7D7WMex5-yJvTL5sU'
content_data = {
"first": {
"value": f"您的微信账户“{wx_nick_name}”进行了网站登录操作",
"color": "#173177"
},
"keyword1": {
"value": username,
"color": "#173177"
},
"keyword2": {
"value": "fly应用分发平台",
"color": "#173177"
},
"keyword3": {
"value": now_time.replace('_', ' '),
"color": "#173177"
},
"remark": {
"value": "感谢您的关注",
"color": "#173177"
},
}
return self.send_msg(to_user, msg_id, content_data)
def login_failed_msg(self, to_user, wx_nick_name):
now_time = get_format_time()
msg_id = '9rChJFw6nR0Wbp7SXsImh99qm6Dj1hrRWJo1NpEJ_3g'
content_data = {
"first": {
"value": f"您的微信账户“{wx_nick_name}”登录失败",
"color": "#173177"
},
"keyword1": {
"value": "还未绑定用户,请通过手机或者邮箱登录账户之后进行绑定",
"color": "#173177"
},
"keyword2": {
"value": now_time.replace('_', ' '),
"color": "#173177"
},
"remark": {
"value": "感谢您的关注",
"color": "#173177"
},
}
return self.send_msg(to_user, msg_id, content_data)
def bind_success_msg(self, to_user, wx_nick_name, username):
now_time = get_format_time()
msg_id = 'twMMQn9AKZKevbZBYh8EcFMk7BnC5Y09FmDkZQEH43w'
content_data = {
"first": {
"value": f"您的微信账户“{wx_nick_name}”绑定成功",
"color": "#173177"
},
"keyword1": {
"value": username,
"color": "#173177"
},
"keyword2": {
"value": now_time.replace('_', ' '),
"color": "#173177"
},
"remark": {
"value": "感谢您的关注",
"color": "#173177"
},
}
return self.send_msg(to_user, msg_id, content_data)
def bind_failed_msg(self, to_user, wx_nick_name, msg):
now_time = get_format_time()
msg_id = 'WIrRuHiDG0f976seBAmY-rjSil0AiT9E5l0PHrPnsfs'
content_data = {
"first": {
"value": f"您的微信账户“{wx_nick_name}”绑定失败",
"color": "#173177"
},
"keyword1": {
"value": msg,
"color": "#173177"
},
"keyword2": {
"value": now_time.replace('_', ' '),
"color": "#173177"
},
"remark": {
"value": "每个微信只可以绑定一个登录账户,请先解绑,然后重新扫描绑定。感谢您的关注",
"color": "#173177"
},
}
return self.send_msg(to_user, msg_id, content_data)
def unbind_success_msg(self, to_user, wx_nick_name, username):
now_time = get_format_time()
msg_id = 'RabYMg8-jPGhonk957asbW17iLHSLp8BfEXnyesRZ60'
content_data = {
"first": {
"value": f"您的微信账户“{wx_nick_name}”已经解除绑定",
"color": "#173177"
},
"keyword1": {
"value": username,
"color": "#173177"
},
"keyword2": {
"value": now_time.replace('_', ' '),
"color": "#173177"
},
"keyword3": {
"value": "解除绑定成功,您将无法使用微信扫描登录平台",
"color": "#173177"
},
"remark": {
"value": "如需重新绑定,请登陆平台,在个人资料进行绑定。感谢您的关注",
"color": "#173177"
},
}
return self.send_msg(to_user, msg_id, content_data)
def bind_query_success_msg(self, to_user, wx_nick_name, username, name, mobile, email):
msg_id = 'yU15jLNSULagJTff01X67mDtDytBSs3iBpOBi8c7dvs'
content_data = {
"first": {
"value": f"您的微信账户“{wx_nick_name}”绑定信息结果",
"color": "#173177"
},
"keyword1": {
"value": username,
"color": "#173177"
},
"keyword2": {
"value": name,
"color": "#173177"
},
"keyword3": {
"value": mobile,
"color": "#173177"
},
"keyword4": {
"value": email,
"color": "#173177"
},
"remark": {
"value": "感谢您的关注",
"color": "#173177"
},
}
return self.send_msg(to_user, msg_id, content_data)
def query_bind_info_failed_msg(self, to_user, wx_nick_name, action_msg, failed_msg):
now_time = get_format_time()
msg_id = 'uCxjYt216zRAv_sPZihKk4xp7-6pLmRW1oNLLW7L3oI'
content_data = {
"first": {
"value": f"您的微信账户“{wx_nick_name}{action_msg}失败了",
"color": "#173177"
},
"keyword1": {
"value": wx_nick_name,
"color": "#173177"
},
"keyword2": {
"value": action_msg,
"color": "#173177"
},
"keyword3": {
"value": failed_msg,
"color": "#173177"
},
"keyword4": {
"value": now_time.replace('_', ' '),
"color": "#173177"
},
"remark": {
"value": "暂无登录绑定信息,如需绑定,请登陆平台,在个人资料进行绑定。感谢您的关注",
"color": "#173177"
},
}
return self.send_msg(to_user, msg_id, content_data)

@ -614,7 +614,14 @@ class AdminUserCertificationSerializer(serializers.ModelSerializer):
class ThirdWxSerializer(serializers.ModelSerializer): class ThirdWxSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = models.ThirdWeChatUserInfo model = models.ThirdWeChatUserInfo
exclude = ["id"] exclude = ["id", "sex", "address", "user_id"]
class AdminThirdWxSerializer(ThirdWxSerializer):
class Meta:
model = models.ThirdWeChatUserInfo
fields = "__all__"
read_only_fields = ["id", "openid", "user_id", "head_img_url"]
class DomainNameSerializer(serializers.ModelSerializer): class DomainNameSerializer(serializers.ModelSerializer):

@ -9,7 +9,7 @@ from rest_framework.views import APIView
from api.utils.baseutils import is_valid_domain, get_cname_from_domain, get_user_domain_name, \ from api.utils.baseutils import is_valid_domain, get_cname_from_domain, get_user_domain_name, \
get_min_default_domain_cname_obj get_min_default_domain_cname_obj
from api.utils.response import BaseResponse from api.utils.response import BaseResponse
from api.utils.auth import ExpiringTokenAuthentication from api.utils.auth import ExpiringTokenAuthentication, DownloadQrPermission
from rest_framework.response import Response from rest_framework.response import Response
from api.models import UserDomainInfo, Apps from api.models import UserDomainInfo, Apps
import logging import logging
@ -62,6 +62,7 @@ def add_new_domain_info(res, request, domain_name, domain_type):
class DomainCnameView(APIView): class DomainCnameView(APIView):
authentication_classes = [ExpiringTokenAuthentication, ] authentication_classes = [ExpiringTokenAuthentication, ]
permission_classes = [DownloadQrPermission, ]
def get(self, request): def get(self, request):
res = BaseResponse() res = BaseResponse()

@ -24,7 +24,7 @@ from api.models import Apps, AppReleaseInfo, APPToDeveloper, APPSuperSignUsedInf
from django.http import FileResponse from django.http import FileResponse
import logging import logging
from api.utils.baseutils import get_profile_full_path, get_app_domain_name, get_filename_form_file, \ from api.utils.baseutils import get_profile_full_path, get_app_domain_name, get_filename_form_file, \
check_app_domain_name_access check_app_domain_name_access, get_real_ip_address
from api.utils.throttle import VisitShortThrottle, InstallShortThrottle, InstallThrottle1, InstallThrottle2 from api.utils.throttle import VisitShortThrottle, InstallShortThrottle, InstallThrottle1, InstallThrottle2
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -228,10 +228,7 @@ class InstallView(APIView):
res.data = {"download_url": download_url, "extra_url": extra_url} res.data = {"download_url": download_url, "extra_url": extra_url}
if download_url != "" and "mobileconifg" not in download_url: if download_url != "" and "mobileconifg" not in download_url:
if request.META.get('HTTP_X_FORWARDED_FOR', None): ip = get_real_ip_address(request)
ip = request.META['HTTP_X_FORWARDED_FOR']
else:
ip = request.META['REMOTE_ADDR']
logger.info(f"remote ip {ip} short {short} download_url {download_url} app_obj {app_obj}") logger.info(f"remote ip {ip} short {short} download_url {download_url} app_obj {app_obj}")
set_app_download_by_cache(app_id) set_app_download_by_cache(app_id)
amount = app_obj.get("d_count") amount = app_obj.get("d_count")

@ -10,7 +10,7 @@ from rest_framework.views import APIView
from api.utils.utils import get_captcha, valid_captcha, \ from api.utils.utils import get_captcha, valid_captcha, \
get_sender_sms_token, is_valid_sender_code, get_sender_email_token, get_random_username, \ get_sender_sms_token, is_valid_sender_code, get_sender_email_token, get_random_username, \
check_username_exists, set_user_token check_username_exists, set_user_token
from api.utils.baseutils import is_valid_phone, is_valid_email, get_min_default_domain_cname_obj from api.utils.baseutils import is_valid_phone, is_valid_email, get_min_default_domain_cname_obj, get_real_ip_address
from api.utils.auth import ExpiringTokenAuthentication from api.utils.auth import ExpiringTokenAuthentication
from api.utils.response import BaseResponse from api.utils.response import BaseResponse
from fir_ser.settings import REGISTER, LOGIN, CHANGER from fir_ser.settings import REGISTER, LOGIN, CHANGER
@ -400,7 +400,7 @@ class RegistView(APIView):
return Response(response.dict) return Response(response.dict)
def wx_qr_code_response(ret, code, qr_info): def wx_qr_code_response(ret, code, qr_info, ip_addr):
if code: if code:
logger.info(f"微信登录码获取成功, {qr_info}") logger.info(f"微信登录码获取成功, {qr_info}")
errmsg = qr_info.get('errmsg') errmsg = qr_info.get('errmsg')
@ -410,7 +410,7 @@ def wx_qr_code_response(ret, code, qr_info):
else: else:
ticket = qr_info.get('ticket') ticket = qr_info.get('ticket')
if ticket: if ticket:
set_wx_ticket_login_info_cache(ticket) set_wx_ticket_login_info_cache(ticket, {'ip_addr': ip_addr})
ret.data = {'qr': show_qrcode_url(ticket), 'ticket': ticket} ret.data = {'qr': show_qrcode_url(ticket), 'ticket': ticket}
else: else:
ret.code = 1003 ret.code = 1003
@ -533,7 +533,7 @@ class UserInfoView(APIView):
ret = BaseResponse() ret = BaseResponse()
uid = request.user.uid uid = request.user.uid
code, qr_info = make_wx_login_qrcode(f"web.bind.{uid}") code, qr_info = make_wx_login_qrcode(f"web.bind.{uid}")
return wx_qr_code_response(ret, code, qr_info) return wx_qr_code_response(ret, code, qr_info, get_real_ip_address(request))
class AuthorizationView(APIView): class AuthorizationView(APIView):
@ -746,7 +746,7 @@ class WeChatLoginView(APIView):
ret = BaseResponse() ret = BaseResponse()
if get_login_type().get('third', '').get('wxp'): if get_login_type().get('third', '').get('wxp'):
code, qr_info = make_wx_login_qrcode() code, qr_info = make_wx_login_qrcode()
return wx_qr_code_response(ret, code, qr_info) return wx_qr_code_response(ret, code, qr_info, get_real_ip_address(request))
return Response(ret.dict) return Response(ret.dict)
@ -758,12 +758,12 @@ class WeChatLoginCheckView(APIView):
ticket = request.data.get("ticket") ticket = request.data.get("ticket")
if ticket: if ticket:
wx_ticket_data = get_wx_ticket_login_info_cache(ticket) wx_ticket_data = get_wx_ticket_login_info_cache(ticket)
if wx_ticket_data: if wx_ticket_data and wx_ticket_data.get('pk'):
if wx_ticket_data == -1: if wx_ticket_data.get('pk', -1) == -1:
ret.msg = "还未绑定用户,请通过手机或者邮箱登录账户之后进行绑定" ret.msg = "还未绑定用户,请通过手机或者邮箱登录账户之后进行绑定"
ret.code = 1005 ret.code = 1005
else: else:
user = UserInfo.objects.filter(pk=wx_ticket_data).first() user = UserInfo.objects.filter(pk=wx_ticket_data['pk']).first()
if user.is_active: if user.is_active:
key, user_info = set_user_token(user) key, user_info = set_user_token(user)
serializer = UserInfoSerializer(user_info) serializer = UserInfoSerializer(user_info)

@ -5,19 +5,21 @@
# date: 2021/3/29 # date: 2021/3/29
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response from rest_framework.response import Response
from django.http.response import HttpResponse
import logging import logging
import random import random
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework_xml.parsers import XMLParser from rest_framework_xml.parsers import XMLParser
from api.models import ThirdWeChatUserInfo, UserInfo from api.models import ThirdWeChatUserInfo, UserInfo, UserCertificationInfo
from api.utils.auth import ExpiringTokenAuthentication from api.utils.auth import ExpiringTokenAuthentication
from api.utils.mp.chat import reply, receive from api.utils.mp.chat import reply, receive
from api.utils.mp.wechat import check_signature, WxMsgCrypt, get_userinfo_from_openid from api.utils.mp.wechat import check_signature, WxMsgCrypt, get_userinfo_from_openid, WxTemplateMsg
from api.utils.response import BaseResponse from api.utils.response import BaseResponse
from api.utils.serializer import ThirdWxSerializer from api.utils.serializer import ThirdWxSerializer
from api.utils.storage.caches import set_wx_ticket_login_info_cache from api.utils.storage.caches import set_wx_ticket_login_info_cache, get_wx_ticket_login_info_cache
from api.views.login import get_login_type from api.views.login import get_login_type
from config import WEB_DOMAIN
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -43,15 +45,27 @@ def reply_login_msg(rec_msg, to_user, from_user, ):
content = f'还未绑定用户,请通过手机或者邮箱登录账户之后进行绑定' content = f'还未绑定用户,请通过手机或者邮箱登录账户之后进行绑定'
u_data_id = -1 u_data_id = -1
wx_user_obj = ThirdWeChatUserInfo.objects.filter(openid=to_user).first() wx_user_obj = ThirdWeChatUserInfo.objects.filter(openid=to_user).first()
wx_ticket_info = get_wx_ticket_login_info_cache(rec_msg.Ticket)
if wx_user_obj: if wx_user_obj:
u_data_id = wx_user_obj.user_id.pk u_data_id = wx_user_obj.user_id.pk
content = f'用户 {wx_user_obj.user_id.first_name} 登录成功' content = f'用户 {wx_user_obj.user_id.first_name} 登录成功'
set_wx_ticket_login_info_cache(rec_msg.Ticket, u_data_id) WxTemplateMsg().login_success_msg(to_user, wx_user_obj.nickname, wx_user_obj.user_id.first_name)
else:
wx_user_info = update_or_create_wx_userinfo(to_user, None, False)
WxTemplateMsg().login_failed_msg(to_user, wx_user_info.get('nickname', ''))
if wx_ticket_info and wx_ticket_info.get('ip_addr'):
ip_addr = wx_ticket_info.get('ip_addr')
logger.info(f"{content} ip:{ip_addr}")
set_wx_ticket_login_info_cache(rec_msg.Ticket, {'pk': u_data_id})
reply_msg = reply.TextMsg(to_user, from_user, content) reply_msg = reply.TextMsg(to_user, from_user, content)
return reply_msg.send() return reply_msg.send()
def update_or_create_wx_userinfo(to_user, user_obj): def update_or_create_wx_userinfo(to_user, user_obj, create=True):
code, wx_user_info = get_userinfo_from_openid(to_user) code, wx_user_info = get_userinfo_from_openid(to_user)
logger.info(f"get openid:{to_user} info:{to_user} code:{code}") logger.info(f"get openid:{to_user} info:{to_user} code:{code}")
if code: if code:
@ -64,7 +78,9 @@ def update_or_create_wx_userinfo(to_user, user_obj):
'address': f"{wx_user_info.get('country')}-{wx_user_info.get('province')}-{wx_user_info.get('city')}", 'address': f"{wx_user_info.get('country')}-{wx_user_info.get('province')}-{wx_user_info.get('city')}",
'subscribe': wx_user_info.get('subscribe'), 'subscribe': wx_user_info.get('subscribe'),
} }
if create:
ThirdWeChatUserInfo.objects.update_or_create(user_id=user_obj, openid=to_user, defaults=wx_user_info) ThirdWeChatUserInfo.objects.update_or_create(user_id=user_obj, openid=to_user, defaults=wx_user_info)
return wx_user_info
class ValidWxChatToken(APIView): class ValidWxChatToken(APIView):
@ -72,7 +88,7 @@ class ValidWxChatToken(APIView):
def get(self, request): def get(self, request):
params = request.query_params params = request.query_params
return Response(check_signature(params)) return HttpResponse(check_signature(params))
def post(self, request): def post(self, request):
params = request.query_params params = request.query_params
@ -100,7 +116,8 @@ class ValidWxChatToken(APIView):
result = reply_msg.send() result = reply_msg.send()
else: else:
result = reply.Msg().send() result = reply.Msg().send()
logger.info(f"replay msg: {result}")
return HttpResponse(result)
elif isinstance(rec_msg, receive.EventMsg): elif isinstance(rec_msg, receive.EventMsg):
to_user = rec_msg.FromUserName to_user = rec_msg.FromUserName
from_user = rec_msg.ToUserName from_user = rec_msg.ToUserName
@ -109,6 +126,57 @@ class ValidWxChatToken(APIView):
content = random.choices(GOOD_XX)[0] content = random.choices(GOOD_XX)[0]
reply_msg = reply.TextMsg(to_user, from_user, content) reply_msg = reply.TextMsg(to_user, from_user, content)
result = reply_msg.send() result = reply_msg.send()
logger.info(f"replay msg: {result}")
return HttpResponse(result)
elif rec_msg.Eventkey == 'flyapps':
reply_msg = reply.TextMsg(to_user, from_user, WEB_DOMAIN)
result = reply_msg.send()
logger.info(f"replay msg: {result}")
return HttpResponse(result)
elif rec_msg.Eventkey in ['query_bind', 'unbind']:
wx_user_obj = ThirdWeChatUserInfo.objects.filter(openid=to_user).first()
if rec_msg.Eventkey == 'query_bind':
if wx_user_obj:
user_obj = wx_user_obj.user_id
content = f'绑定用户 {user_obj.first_name}\n'
if user_obj.email:
content += f' 登录邮箱:{user_obj.email}\n'
if user_obj.mobile:
content += f' 登录手机:{user_obj.mobile}'
if content.endswith('\n'):
content = content[0:-1]
user_cert_obj = UserCertificationInfo.objects.filter(user_id=user_obj,
status=1).first()
if user_cert_obj:
name = user_cert_obj.name
else:
name = user_obj.first_name
WxTemplateMsg().bind_query_success_msg(to_user, wx_user_obj.nickname,
user_obj.first_name, name, user_obj.mobile,
user_obj.email)
else:
content = '暂无登录绑定信息'
wx_user_info = update_or_create_wx_userinfo(to_user, None, False)
WxTemplateMsg().query_bind_info_failed_msg(to_user, wx_user_info.get('nickname'),
"查询登录绑定", content)
elif rec_msg.Eventkey == 'unbind':
if wx_user_obj:
content = f'解绑用户 {wx_user_obj.user_id.first_name} 成功'
WxTemplateMsg().unbind_success_msg(to_user, wx_user_obj.nickname,
wx_user_obj.user_id.first_name)
ThirdWeChatUserInfo.objects.filter(openid=to_user).delete()
else:
content = f'暂无登录绑定信息'
wx_user_info = update_or_create_wx_userinfo(to_user, None, False)
WxTemplateMsg().query_bind_info_failed_msg(to_user, wx_user_info.get('nickname'),
"解除登录绑定", content)
logger.info(f"to_user:{to_user} from_user:{from_user} reply msg: {content}")
reply_msg = reply.TextMsg(to_user, from_user, content)
result = reply_msg.send()
elif rec_msg.Event in ['subscribe', 'unsubscribe']: # 订阅 elif rec_msg.Event in ['subscribe', 'unsubscribe']: # 订阅
reply_msg = reply.TextMsg(to_user, from_user, content) reply_msg = reply.TextMsg(to_user, from_user, content)
result = reply_msg.send() result = reply_msg.send()
@ -129,19 +197,23 @@ class ValidWxChatToken(APIView):
if user_obj and user_obj.uid == wx_user_obj.user_id.uid: if user_obj and user_obj.uid == wx_user_obj.user_id.uid:
content = f'账户 {wx_user_obj.user_id.first_name} 已经绑定成功,感谢您的使用' content = f'账户 {wx_user_obj.user_id.first_name} 已经绑定成功,感谢您的使用'
update_or_create_wx_userinfo(to_user, user_obj) update_or_create_wx_userinfo(to_user, user_obj)
WxTemplateMsg().bind_success_msg(to_user, wx_user_obj.nickname, user_obj.first_name)
else: else:
content = f'账户已经被 {wx_user_obj.user_id.first_name} 绑定' content = f'账户已经被 {wx_user_obj.user_id.first_name} 绑定'
WxTemplateMsg().bind_failed_msg(to_user, wx_user_obj.nickname, content)
else: else:
update_or_create_wx_userinfo(to_user, user_obj) wx_user_info = update_or_create_wx_userinfo(to_user, user_obj)
content = f'账户绑定 {wx_user_obj.user_id.first_name} 成功' content = f'账户绑定 {wx_user_obj.user_id.first_name} 成功'
set_wx_ticket_login_info_cache(rec_msg.Ticket, user_obj.pk) WxTemplateMsg().bind_success_msg(to_user, wx_user_info.get('nickname', ''),
user_obj.first_name)
set_wx_ticket_login_info_cache(rec_msg.Ticket, {'pk': user_obj.pk})
reply_msg = reply.TextMsg(to_user, from_user, content) reply_msg = reply.TextMsg(to_user, from_user, content)
result = reply_msg.send() result = reply_msg.send()
else: else:
logger.error('密文解密失败') logger.error('密文解密失败')
logger.info(f"replay msg: {result}")
return Response(result) return HttpResponse("success")
class PageNumber(PageNumberPagination): class PageNumber(PageNumberPagination):

Loading…
Cancel
Save