diff --git a/db.sqlite3 b/db.sqlite3 index c68be68..d40dd53 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/django_weixinapp/settings.py b/django_weixinapp/settings.py index 4d886b0..16add90 100644 --- a/django_weixinapp/settings.py +++ b/django_weixinapp/settings.py @@ -108,11 +108,11 @@ AUTH_PASSWORD_VALIDATORS = [ LANGUAGE_CODE = 'en-us' -TIME_ZONE = 'UTC' +TIME_ZONE = 'Asia/Shanghai' USE_I18N = True -USE_TZ = True +USE_TZ = False # Static files (CSS, JavaScript, Images) @@ -124,3 +124,11 @@ STATIC_URL = 'static/' # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_simplejwt.authentication.JWTAuthentication', # 使用simplejwt + ) +} + diff --git a/frontend/pages/index/index.js b/frontend/pages/index/index.js index 8f78228..8b8cdf6 100644 --- a/frontend/pages/index/index.js +++ b/frontend/pages/index/index.js @@ -1,18 +1,10 @@ Component({ data: { - // 数据 + // 测试数据 items: [{ - id: 3, + id: 1, content: '打游戏', checked: false - }, { - id: 2, - content: '吃饭', - checked: false, - }, { - id: 1, - content: '睡觉', - checked: false, }], // 输入框当前内容 inputedValue: "", @@ -20,6 +12,13 @@ Component({ methods: { // 监听多选框的状态改变事件 checkboxChange(e) { + // 登录后才可以修改待办事项的状态 + this.login(() => { + this._checkboxChange(e); + }) + }, + // 多选框状态改变实际处理函数 + _checkboxChange(e) { // 页面特有的数据 // 获取本地数据的写法为this.data.xxx const items = JSON.parse(JSON.stringify(this.data.items)); @@ -51,6 +50,9 @@ Component({ // 打印的内容会展现在调试器中 // console.log(this.data.items) + // 上传数据到后端 + this.uploadData(items); + }, // 监听键盘输入事件 keyInput(e) { @@ -58,10 +60,18 @@ Component({ inputedValue: e.detail.value }) }, - // 监听提交按钮 + // 监听按钮点击事件 inputSubmit() { + // 登录之后才可以添加新的待办事项 + this.login(() => { + this._inputSubmit(); + }) + }, + // 点击提交按钮后实际执行的函数 + _inputSubmit() { // 读取数据 let items = JSON.parse(JSON.stringify(this.data.items)); + // console.log("上传前: ",items); // 设置新条目的id let newId = 1; if (this.data.inputedValue === "") { @@ -94,6 +104,117 @@ Component({ key: "items", data: items }); + // items和this.data.items指向的值一模一样 + // console.log("上传后: ", items); + // console.log("上传后:", this.data.items); + this.uploadData(items); + }, + // 检查token是否过期 + isTokenAvailable() { + const now = Date.parse(new Date()); + try { + const accessTime = wx.getStorageSync('access_time'); + // token的有效时间为5分钟 + if ((accessTime !== '') && (now - accessTime < 5 * 60 * 1000)) { + return true; + } + } catch { + // do something + } + return false; + }, + // 获取token + getToken(callback) { + // -------------- + // 步骤一:获取code + // -------------- + wx.login({ + success(res) { + if (res.code) { + // 查看code + // console.log("code: ", res.code); + // -------------- + // 步骤二:用code换取token + // -------------- + wx.request({ + url: 'http://127.0.0.1:8000/api/weixin/login/', + method: 'POST', + data: { + code: res.code, + }, + success: res => { + // 查看是否收到token + // console.log(res.data); + console.log("重新获取token成功!"); + const access = res.data.access; + // 将token保存到本地 + wx.setStorage({ + key: 'access', + data: access, + }); + // 保存获取token的时间 + wx.setStorage({ + key: 'access_time', + data: Date.parse(new Date()), + }); + // -------------- + // 步骤三:用token获取用户数据 + // -------------- + // wx.request({ + // url: 'http://127.0.0.1:8000/api/weixin/data/', + // type: 'GET', + // header: { + // 'Authorization': 'Bearer ' + access + // }, + // success: res => { + // console.log(res.data); + // // 调用回调函数 + // // 以便登录成功后执行后续操作 + // // 因为wx.login,wx.request都是异步的, + // // 这样写才能保证callback一定晚于wx.login执行 + // callback(); + // } + // }) + callback(); + } + }); + } else { + console.log("登录失败! " + res.errMsg); + } + } + }); + }, + // 登录函数 + login(callback = (() => {})) { + if (!this.isTokenAvailable()) { + console.log("token已失效,需要重新向django请求token!"); + // 获取token并传入callback + this.getToken(callback); + } else { + console.log("token有效!从缓存中直接读取!"); + // const access = wx.getStorageSync('access'); + // console.log("token: ", access); + callback(); + } + }, + // 将清单数据上传django + uploadData(items) { + const access = wx.getStorageSync('access'); + wx.request({ + url: 'http://127.0.0.1:8000/api/weixin/data/', + method: 'POST', + header: { + 'Authorization': 'Bearer ' + access, + }, + data: { + items: items, + }, + success: res => { + // 上传成功后打印一下 + console.log("上传成功"); + console.log(res); + }, + }) } }, //生命周期函数 @@ -106,19 +227,29 @@ Component({ key: 'items', success: res => { this.setData({ - items: res.data.filter(v=>v.checked===false) + items: res.data.filter(v => v.checked === false) }) } - }); - - // 请求后端数据 - wx.request({ - url: 'http://127.0.0.1:8000/api/weixin/login/', - success: res => { - console.log(res.data); - } - }) + }); + this.login(() => { + const access = wx.getStorageSync('access'); + // 从后端获取items数据 + wx.request({ + url: 'http://127.0.0.1:8000/api/weixin/data/', + type: 'GET', + header: { + 'authorization': 'Bearer ' + access, + }, + success: res => { + console.log("读取数据成功!"); + // 只读取未完成的清单 + this.setData({ + items: res.data.items.filter(v => v.checked === false) + }) + // console.log(this.data.items); + } + }) + }); } } - }) \ No newline at end of file diff --git a/identifier.sqlite b/identifier.sqlite new file mode 100644 index 0000000..e69de29 diff --git a/weixin/admin.py b/weixin/admin.py index 8c38f3f..aa0533a 100644 --- a/weixin/admin.py +++ b/weixin/admin.py @@ -1,3 +1,5 @@ from django.contrib import admin - +from weixin.models import Profile # Register your models here. + +admin.site.register(Profile) diff --git a/weixin/migrations/0001_initial.py b/weixin/migrations/0001_initial.py new file mode 100644 index 0000000..1fb007e --- /dev/null +++ b/weixin/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 4.1.1 on 2022-10-06 16:48 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import weixin.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('items', models.JSONField(default=weixin.models.empty_items)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/weixin/models.py b/weixin/models.py index 71a8362..966b389 100644 --- a/weixin/models.py +++ b/weixin/models.py @@ -1,3 +1,29 @@ from django.db import models +from django.contrib.auth.models import User +from django.db.models.signals import post_save +from django.dispatch import receiver -# Create your models here. + +# 默认items为空 +def empty_items(): + return {'items': []} + + +class Profile(models.Model): + # 与User外键链接 + user = models.OneToOneField(User, on_delete=models.CASCADE) + # 待办事项的json字段 + items = models.JSONField(default=empty_items) + + +# 每当User创建或者保存时通知对应的@receiver装饰的函数 +# 创建或保存对应的Profile模型 +@receiver(post_save, sender=User) +def create_user_profile(sender, instance, created, **kwargs): + if created: + Profile.objects.create(user=instance) + + +@receiver(post_save, sender=User) +def save_user_profile(sender, instance, **kwargs): + instance.profile.save() diff --git a/weixin/urls.py b/weixin/urls.py index b086e02..dbe1306 100644 --- a/weixin/urls.py +++ b/weixin/urls.py @@ -1,7 +1,8 @@ from django.urls import path -from weixin.views import WeixinLogin +from weixin.views import WeixinLogin, UserData app_name = 'weixin' urlpatterns = [ - path('login/', WeixinLogin.as_view(), name='login') + path('login/', WeixinLogin.as_view(), name='login'), + path('data/', UserData.as_view(), name='data'), ] \ No newline at end of file diff --git a/weixin/views.py b/weixin/views.py index 92296e7..5c1d6f3 100644 --- a/weixin/views.py +++ b/weixin/views.py @@ -1,10 +1,77 @@ from rest_framework.views import APIView from rest_framework.response import Response +from django.contrib.auth.models import User +from rest_framework_simplejwt.tokens import RefreshToken +from rest_framework.permissions import IsAuthenticated +import requests +import json -class WeixinLogin(APIView): +# 获取用户数据 +class UserData(APIView): + # 鉴权方式 + permission_classes = [IsAuthenticated] + def get(self, request, format=None): - """ - 提供get请求 - """ - return Response({"data": "Hello World!"}) + """将当前用户的清单数据items返回""" + print('Get data: ',request.user.profile.items) + return Response({ + 'code': 'Get ok', + 'items': request.user.profile.items['items'] + }) + + def post(self, request, format=None): + """将用户上传的数据更新到数据库""" + user = request.user + user.profile.items = request.data + user.save() + print('Post data: ', user.profile.items) + return Response({'code': 'Post ok'}) + + +class WeixinLogin(APIView): + def post(self, request, format=None): + """提供post请求""" + # 从请求中获取code + code = json.loads(request.body).get('code') + print("code: " + code); + # 填写测试号的AppID和AppSecret + appid = 'wxe35222de7aa53383' + appsecret = '424c7145423396a00c987ca0e5ea8ae9' + # 微信接口服务地址 + base_url = 'https://api.weixin.qq.com/sns/jscode2session' + # 拼接参数形成完整url + url = base_url + '?appid=' + appid + '&secret=' + appsecret + '&js_code=' + code + '&grant_type=authorization_code' + response = requests.get(url) + + # 获取openid和session_key + try: + openid = response.json()['openid'] + session_key = response.json()['session_key'] # session_key是对用户数据进行了加密签名的密钥,不要泄露 + except KeyError: + return Response({'code': 'failed'}) + else: + # 打印到后端命令行 + print("openid: " + openid) + print("session_key: " + session_key) + # 根据openid确定用户的本地身份 + try: + user = User.objects.get(username=openid) + except User.DoesNotExist: + user = None + + if user: + user = User.objects.get(username=openid) + # 如果用户不存在,则创建openid用户 + else: + user = User.objects.create( + username=openid, + password=openid, + ) + # 用于给用户提供临时token + refresh = RefreshToken.for_user(user) + return Response({ + 'code': 'success', + 'refresh': str(refresh), + 'access': str(refresh.access_token), + })