XCX阶段
-
要开发微信小程序就必须申请微信公众平台账户,在开发管理下找到开发控制并保存
AppIdD
,还需要下载小程序开发工具。使用小程序创建项目需要登录,然后添加小程序并指定目录,最后粘贴AppID
并选择JavaScript
。开发小程序会默认创建文件,其pages、utils、js、json、wxss、wxml、app.json
。 -
app.json
决定页面路劲、窗口表现、设置网络超时时间、设置多tab等。小程序根目录下的app.json
文件用来对微信小程序进行全局配置。entryPagePath { // type string 不是必填项 用于指定默认启动的首页。如果不填,将默认为pages列表的第一项。不支持带页面路径参数。 "entryPagePath": "pages/index/index", // type string 是个必填项 页面路径列表。文件名不需要写文件后缀,框架会自动去寻找对应位置的四个文件进行处理。未指定 entryPagePath时,数组的第一项代表小程序的初始页面(首页)。则需要在app.json中写。 "pages": ["pages/index/index", "pages/logs/logs"], // type Object 不是必填项 全局的默认窗口表现。用于设置小程序的状态栏、导航条、标题、窗口背景色。 "window": { navigationBarBackgroundColor:#000000, // HexColor, 导航栏背景颜色,如 #000000 navigationBarTextStyle:white, // string, 导航栏标题颜色,仅支持black/white navigationBarTitleText:, // string, 导航栏标题文字内容 navigationStyle:default, // string, 导航栏样式,仅支持以下值:default默认样式custom自定义导航栏,只保留右上角胶囊按钮。 backgroundColor:#ffffff, // HexColor, 窗口的背景色 backgroundTextStyle:dark, // string, 下拉 loading 的样式,仅支持dark/light backgroundColorTop:#ffffff, // string, 顶部窗口的背景色,仅 iOS 支持 backgroundColorBottom:#ffffff, // string, 底部窗口的背景色,仅 iOS 支持 enablePullDownRefresh:false, // boolean, 是否开启全局的下拉刷新。详见 Page.onPullDownRefresh onReachBottomDistance:50, // number, 页面上拉触底事件触发时距页面底部距离,单位为 px。详见 Page.onReachBottom pageOrientation:portrait, // string, 屏幕旋转设置,支持auto/portrait/landscape restartStrategy:homePage, // string, 重新启动策略配置 initialRenderingCache:, // string, 页面初始渲染缓存配置,支持static/dynamic visualEffectInBackground:none, // string, 切入系统后台时,隐藏页面内容,保护用户隐私。支持 hidden/none handleWebviewPreload:static, // string, 控制预加载下个页面的时机)。支持static/manual/auto }, // type Object 不是必填项 底部tab栏的表现。如果小程序是一个多 tab 应用(客户端窗口的底部或顶部有 tab 栏可以切换页面),可以通过 tabBar 配置项指定 tab 栏的表现,以及 tab 切换时显示的对应页面。 "tabBar": { color | HexColor | 是 | | tab 上的文字默认颜色,仅支持十六进制颜色 selectedColor | HexColor | 是 | | tab 上的文字选中时的颜色,仅支持十六进制颜色 backgroundColor | HexColor | 是 | | tab 的背景色,仅支持十六进制颜色 borderStyle | string | 否 | black | tabbar 上边框的颜色, 仅支持black/white list | Array | 是 | | tab 的列表,详见list属性说明,最少 2 个、最多 5 个 tab position | string | 否 | bottom | tabBar 的位置,仅支持bottom/top custom | boolean | 否 | false | 自定义 tabBar // 只能配置最少 2 个、最多 5 个 tab。tab 按数组的顺序排序 "list": [{ pagePath | string | 是 | 页面路径,必须在 pages 中先定义 text | string | 是 | tab 上按钮文字 iconPath | string | 否 | 图片路径,icon 大小限制为 40kb,建议尺寸为 81px*81px,不支持网络图片。 当position为top时,不显示 icon. selectedIconPath | string | 否 | 选中时的图片路径,icon 大小限制为 40kb,建议尺寸为 81px*81px,不支持网络图片。 当position为top时,不显示 icon。 "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/logs/index", "text": "我的" }] } }
常用组件
-
view
视图容器,属性说明。hover-class=none // string,否,指定按下去的样式类。当hover-class="none"时,没有点击态效果 hover-stop-propagation=false // boolean,否,指定是否阻止本节点的祖先节点出现点击态 hover-start-time=50 // number,否,按住后多久出现点击态,单位毫秒 hover-stay-time=400 // number,否,手指松开后点击态保留时间,单位毫秒 <view hover-class="none"></view>
-
text
文本,属性说明。selectable=false // boolean,否,文本是否可选 (已废弃) user-select=false // boolean,否,文本是否可选,该属性会使文本节点显示为 inline-block space= // string ,否,显示连续空格 ensp // 中文字符空格一半大小 emsp // 中文字符空格大小 nbsp // 根据字体设置的空格大小 decode=false //boolean,否,是否解码 <text selectable=false></text>
-
image
图片,支持 JPG、PNG、SVG、WEBP、GIF 等格式,2.3.0 起支持云文件ID,属性说明。src= // string,否,图片资源地址 mode=scaleToFill // string,否,图片裁剪、缩放的模式 scaleToFill // 缩放模式,不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素 aspectFit // 缩放模式,保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来. aspectFill // 缩放模式,保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取 widthFix // 缩放模式,宽度不变,高度自动变化,保持原图宽高比不变 heightFix // 缩放模式,高度不变,宽度自动变化,保持原图宽高比不变 top // 裁剪模式,不缩放图片,只显示图片的顶部区域 bottom // 裁剪模式,不缩放图片,只显示图片的底部区域 center // 裁剪模式,不缩放图片,只显示图片的中间区域 left // 裁剪模式,不缩放图片,只显示图片的左边区域 right // 裁剪模式,不缩放图片,只显示图片的右边区域 top left // 裁剪模式,不缩放图片,只显示图片的左上边区域 top right // 裁剪模式,不缩放图片,只显示图片的右上边区域 bottom left // 裁剪模式,不缩放图片,只显示图片的左下边区域 bottom right // 裁剪模式,不缩放图片,只显示图片的右下边区域 webp=false // boolean,否,默认不解析 webP 格式,只支持网络资源 lazy-load=false // boolean,否,图片懒加载,在即将进入一定范围(上下三屏)时才开始加载 show-menu-by-longpress=false // boolean,否,长按图片显示发送给朋友、收藏、保存图片、搜一搜、打开名片/前往群聊/打开小程序(若图片中包含对应二维码或小程序码)的菜单。 binderror= // eventhandle,否,当错误发生时触发,event.detail = {errMsg} bindload= // eventhandle,否,当图片载入完毕时触发,event.detail = {height, width} <image src="a.png"></image>
-
flex布局,一种非常方便的布局方式,在容器中记住4个样式即可。
display:flex // flex布局显示 flex-direction:row/column // 指定是x或y轴 justify-content:value-1 // 指定轴的排列方式 align-items:value-1 // 指定斥轴的排列方式,value-1 table flex-start // 左对齐 flex-end // 右对齐 space-around // 平均分布 space-between // 无边框平均分布
-
像素,
px
都见过,是用来指定块级标签大小的。而rpx
应该没有人见过,是为移动端所使用的动态像素,可以通过不同的屏幕大小调整像素大小。 -
通过事件跳转
// index.wxml <!-- bindtap绑定事件,data-是规定后面写变量名 --> <view bindtap="clickMe" data-nid="123" >点击跳转</view> // index.js Component({ /*组件的方法列表*/ methods: { /*绑定的事件*/ clickMe (e) { var nid = e.currentTarget.dataset.nid; console.log(nid); // 跳转 wx.navigateTo({ // 通过拼接来在url上携带数据 url: '/pages/redirect/redirect?id='+nid, }) } } }) // redirect.js Page({ /** * 生命周期函数--监听页面加载 */ onLoad(options) { console.log(123456789); console.log(options); } })
-
通过标签跳转
<navigator url="/pages/redirect/redirect?nid=666">跳转页面</navigator>
-
数据绑定
// bind.wxml <!-- 数据绑定就这样写就对了 --> <text>李永好:{{message}}</text> <!-- 按钮标签 --> <button bindtap="changeData">点击知道你</button> // bind.js Page({ /*页面的初始数据*/ data: { // 需要在data中写才能做到数据绑定。 message:"傻到窒息" }, changeData() { //this就代表page这个字典 // 获取数据 console.log(this.data.message); // 修改数据 this.setData({message:"沙雕"}) } })
-
提取用户信息, 可以通过wx.openSetting({})永久授权(无用)。
// login.wxml <view>图片: <image src="{{page}}" style="height:200rpx;width:200rpx;"></image> </view> <view>名称:{{name}}</view> <!-- 绑定事件 --> <button bindtap="getUserName">获取信息</button> // login.js Page({ /** * 页面的初始数据 */ data: { // 需要在data中写才能做到数据绑定。 message:"傻到窒息", name:"傻逼", page:"/statlc/images/a.jpg" }, getUserName(){ //wx.openSetting({}) // 应为调用了函数后this就不等于初始this了 var that = this; // 调用微信的接口,获取当前用户信息 wx.getUserProfile({ desc: '获取用户信息', success:(res) => { // 调用成功后触发 that.setData({ // 拿到名称和图片并赋值 name:res.userInfo.nickName, page:res.userInfo.avatarUrl }) }, fail(res){ //调用失败后触发 console.log("fail",res) } }) }, })
-
选择定位
// wxml <view bindtap="getLocalpath">{{localpath}}</view> // js pages/redirect/redirect.js Page({ /** * 页面的初始数据 */ data: { // 需要在data中写才能做到数据绑定。 message:"傻到窒息", name:"傻逼", page:"/statlc/images/a.jpg", localpath:"请选择位置" }, getLocalpath(){ var that = this; // 选择位置 wx.chooseLocation({ success (res) { // 位置名称 that.setData({localpath:res.address}); } }) },
-
for
// wxml <text>for</text> <view> <!-- 循环数据, --> <view wx:for="{{datalist}}" wx:key="index">{{index}}-{{item}}</view> <!-- 给指定的调用名称起别名 --> <view wx:for="{{datalist}}" wx:for-index="v" wx:for-item="value">{{v}}-{{value}}</view> </view> <view> <!-- 调用字典 --> {{userinfo.name}} {{userinfo.age}} </view> <view> <view wx:for="{{userinfo}}">{{index}}-{{item}}</view> </view> // js data:{ datalist:["猴","狗","李永昊"], userinfo:{ name:"alex", age:18, } },
-
上传图片到内存
<!--pages/publish/publish.wxml--> <text>pages/publish/publish.wxml</text> <view bindtap="uploadImage">请上传图片</view> <view> <image style="width: 200rpx;height: 200rpx; margin-right: 5rpx;" wx:for="{{imageList}}" src="{{item}}"></image> </view> // pages/publish/publish.js Page({ /** * 页面的初始数据 */ data: { imageList:["/statlc/images/a.jpg",] }, uploadImage(){ var that = this; wx.chooseImage({ //最大选择数 count: 9, //图片个数original原图,compressed压缩图 sizeType:["compressed","original"], //camera相机取图片,album本地相册 sourceType:["album","camera"], success(res){ //设置imagelist,页面上自动修改 // that.setData({ // imageList:res.tempFilePaths // }); //先列表中添加单条数据 // that.data.imageList.push("/statlc/images/a.jpg"); //怎加图片并保留之前图片 that.setData({ imageList:that.data.imageList.concat(res.tempFilePaths) }) } }) } })
-
双向绑定
// wxml <view>电话号码:{{phone}}</view> <!-- 输入数据时触发bindinput的指定方法 --> <input value="{{phone}}"bindinput="bindPhone"placeholder="请输入手机号"/> // js pages/telphone/telphone.js Page({ /** * 页面的初始数据 */ data: { phone:"" }, bindPhone(e){ //e中会有从input输入的数据 this.setData({phone:e.detail.value}); }, }
-
连接服务器,右上角点击详情里的本地设置。选择不验证合法域名。。。
// wxml <view>验证码:</view> <input value="{{code}}"bindinput="bindCode"placeholder="请输入验证码"/> <button bindtap="login">登陆</button> // js pages/telphone/telphone.js Page({ /*页面的初始数据*/ data: { code:"" }, bindCode(e){ this.setData({code:e.detail.value}); }, login(){ // xw.request向后台发送 wx.request({ url: 'http://127.0.0.1:8000/api/login/', data: {phone:this.data.phone,code:this.data.code}, method: "POST", dataType:"JSON", success: (result) => { console.log(result) }, fail: (res) => {}, }) }, }
-
弹窗
wx.showToast({ // 要显示的错误信息 title: '错误', // 要显示的图片 icon:"none" }) title: // string,是,提示的内容 icon:success // string,否,图标 success // 显示成功图标,此时 title 文本最多显示 7 个汉字长度 error // 显示失败图标,此时 title 文本最多显示 7 个汉字长度 loading // 显示加载图标,此时 title 文本最多显示 7 个汉字长度 none // 不显示图标,此时 title 文本最多可显示两行,1.9.0及以上版本支持 image: // string,否,自定义图标的本地路径,image 的优先级高于 icon duration:1500 // number,否,提示的延迟时间 mask:false // boolean,否,是否显示透明蒙层,防止触摸穿透 success: // function,否,接口调用成功的回调函数 fail: // function,否,接口调用失败的回调函数 complete: // function,否,接口调用结束的回调函数(调用成功、失败都会执行)
-
页面之间数据传递
// 父页面:wxml <text bindtap="getTopic">{{topicText}}</text> // 父页面:js Page({ data: { topicText:"请选择话题", topiId:null }, getTopic (){ //跳转 wx.navigateTo({ url: '/pages/topic/topic', }) }, setTopicData(res){ //设置data中的数据 this.setData({ topicText:res.title, topiId:res.id }); }, }) // 子页面:wxml <view class="container"> <view class="item" wx:for="{{topicList}}" bindtap="chostTopic" data-xx="{{item}}"> <text>{{item.title}}</text> <text>{{item.count}}</text> </view> </view> // 子页面:wxss .item{ padding: 40rpx; display: flex; flex-direction: row; justify-content: space-between; } // 子页面:js Page({ /*页面的初始数据*/ data: { topicList:[ {id:1,title:"你吧把",count:10000}, {id:1,title:"吧把",count:10}, {id:1,title:"wo吧把",count:1010}, {id:1,title:"我吧把",count:1200}, {id:1,title:"你把",count:100}, ] }, chostTopic(e){ //获取触发事件时传入的指定值 var topicInfo = e.currentTarget.dataset.xx; // 把这个值传递给它的副页面 // 他会按顺序拿到每个页面的对象。 var pages = getCurrentPages(); var prevPage = pages[pages.length-2]; //调用对象的指定事件 prevPage.setTopicData(topicInfo); //放回上级页面 wx.navigateBack({}); } })
-
进度条
// wxml <!--默认是绿色,也可以修改--> <progress percent="{{percent2}}" activeColer="#DC143C" ></progress> <view wx:for="{{imageList}}"> <view>{{item.title}}</view> <progress percent="{{item.percent}}"></progress> </view> <button bindtap="changePercent">点击</button> // js data: { imageList:[ {id:1,title:"image1",percent:30}, {id:2,title:"image2",percent:60}, {id:3,title:"image3",percent:90}, ] }, changePercent(){ var num =2; this.setData({ //要实现嵌套数据的修改,必须使用这种 //中括号包含字符型路径:数据 //也可以使用字符拼接路径 ["imageList[0].percent"]:80, ["imageList["+num+"].percent"]:100, }) },
-
闭包,闭包是将数据封装后,异步后数据不会出现错误选定。
var dataList = ["a","b","c"] //每个发出的repuest是一个包(将传入的值装入函数) //如果不写一个函数将值传入请求来后答应的会是“c” for (var i in dataList){ (function(data){ wx.repuest({ url:"xxx", success(res){ console.log(data); } }) })(dataList[i]) } // 自执行函数 (function(data){ console.log(data); })("我是你baba") //用两个括号第一个括号写函数,第二个写要传入的参数。
-
下拉页面刷新,需要在
json
中开启全局和局部允许下拉刷新选项enablePullDownRefresh:True
。Page({ onPullDownRefresh () { wx.stopPullDownRefresh() // 停止当前页面下拉刷新 success // function,否,接口调用成功的回调函数 fail // function,否,接口调用失败的回调函数 complete // function,否,接口调用结束的回调函数(调用成功、失败都会执行) wx.startPullDownRefresh() // 开始下拉刷新。调用后触发下拉刷新动画,效果与用户手动下拉刷新一致。 success // function,否,接口调用成功的回调函数 fail // function,否,接口调用失败的回调函数 complete // function,否,接口调用结束的回调函数(调用成功、失败都会执行) } })
-
嵌套验证,后端在验证时只能验证一个值,使用下面的代码可以循环验证。
from rest_framework import serializers class B(serializers.Serializer): pass class A(serializers.ModelSerializer): image = B(many=True) # 如果不写many的话只接收一个,写了之后接收一个列表。 pass
-
小程序api管理,
api
在本地的时候使用的是127.0.0.1,而你要发布的时候需要将其改为自己的域名,使用以下代码可以简便处理。// config/api.js var rootURL = "http://127.0.0.1:8000/api/" module.exports = { //如果想在其他文件调用就需要在这里写一下。 indexUrl:rootURL + "news/" } // pages/news/news.js //使用require调用其他文件 var AAAA = require("../../config/api.js") AAAA.indexUrl
-
后端数据初始化,后台数据初始化用于对一些钩子将数据获取后进行,据库的记录。
import os import sys import django base_dir = os.path.dirname(os.path.dirname(os.path.abspath(_file_))) sys.path.append(base_dir) os.environ.setdefault("DJANGO_SETTINGS_MODULE","demos.settings") django.setup() pass # 箭头函数和表的深操作 function (opt){ wx.request({ ... success:(res) => { //这里的this就等于外面的this,这样写并不会新创建一个this this } }) } from django.forms import model_to_dict model_to_dict(obj.user,fields=["id","nickanme"],exclude=[]) # 使用它可以很轻松的将其使用,fields是需要的,exclude是不需要的。
-
分页器,在api中也带了,一个分页器帮助我们工作的。
js
的三元运算条件?条件成立返回:条件不成立返回
。from rest_framework.pagination import LimitOffsetPagination # http://127.0.0.1:8000/api/news/?limit=2&offset=0 class OldBoyLimitPagination(LimitOffsetPagination): default_limit = 5 # 一次几条数据 max_limit =50 # 最大几条数据 limit_query_param = "limit" offset_query_param = "offset" def get_offset(self,request): # 开始值 return 0 def get_paginated_response(self,data): return Response(data) # 他会帮我们套壳,但是我们不需要所以直接返回即可。 class NewsView(ListAPIView): serializer_class = NewsModelSerializer queryset = models.News.objects.all().order_by("-id") pagination_class = OldBoyLimitPagination
-
瀑布流有两种实现方式,一种是靠样式实现,一种是标签实现。
<!--样式实现--> <!--wxcc--> .falls{ -moz-column-count:2; -webkit-column-count:2; column-count:2; -moz-column-gap:20rpx; -webkit-column-gap:20rpx; column-gap:20rpx; } .falls .item{ break-inside:avoid-column; -webkit-column-break-inside:avoid; } <!--wxml--> <view class="falls"> <view class='item'> <image src="..."></image> </view> <view class='item'> <image src="..."></image> </view> </view> <!--标签实现--> <!--wxcc--> .falls{ display:flex; flex-direciton:row; } .falls .item{ width:50%; } <!--wxml--> <view class="falls"> <view class='item'> <image src="..."></image> </view> <view class='item'> <image src="..."></image> </view> </view>
-
自定义TabBar,根目录下创建一个文件夹“component”,在文件夹中创建文件夹"tabbar",在tabbar下右键新建Component。
-
tabbar
# tabbar.js // Component/tabbar.js Component({ /** * 组件的属性列表 */ properties: { selected: { type:Number, value:0 }, }, /** * 组件的初始数据 */ data: { color: "#7A7E83", selectedColor: "#3cc51f", list: [{ pagePath: "/pages/index/index", iconPath: "/statlc/images/a.jpg", selectedIconPath: "/statlc/images/a.jpg", text: "home" }, { text: "fabu" },{ pagePath: "/pages/logs/logs", iconPath: "/statlc/images/a.jpg", selectedIconPath: "/statlc/images/a.jpg", text: "Me" }] }, /** * 组件的方法列表 */ methods: { switchTab(e) { var data = e.currentTarget.dataset var url = data.path; if(url){ wx.switchTab({url}); }else{ if(false){ # 判断条件,自己写,判断登录的。 wx.navigateTo({ url: '/pages/pub/pub', }) }else{ wx.navigateTo({ url: "/pages/atuh/atuh", }) } } } } }) # tabbar.json { "component": true, "usingComponents": {} } # tabbar.wxml <!--Component/tabbar.wxml--> <view class="tab-bar"> <view class="tab-bar-border"></view> <view wx:for="{{list}}" wx:key="index" class="tab-bar-item" data-path="{{item.pagePath}}" bindtap="switchTab"> <block wx:if="{{index === 1}}"> <view>{{item.text}}</view> </block> <block wx:else> <image src="{{selected === index ? item.selectedIconPath : item.iconPath}}"></image> <view style="color: {{selected === index ? selectedColor : color}}">{{item.text}}</view> </block> </view> </view> # tabbar.wxss .tab-bar { position: absolute; bottom: 0; left: 0; right: 0; height: 80px; background: white; display: flex; flex-direction: row; padding-bottom: env(safe-area-inset-bottom); pointer-events: auto; } .tab-bar-border { background-color: rgba(0, 0, 0, 0.33); position: absolute; left: 0; top: 0; width: 100%; height: 1px; transform: scaleY(0.5); } .tab-bar-item { flex: 1; text-align: center; display: flex; justify-content: center; align-items: center; flex-direction: column; } .tab-bar-item image { width: 27px; height: 27px; } .tab-bar-item view { font-size: 10px; }
-
Me
# me.json { "component": true, "usingComponents": { "tabbar":"/Component/tabbar/tabbar" } } # me.wxml <tabbar selected="{{2}}"></tabbar>
-
home
# home.json { "component": true, "usingComponents": { # 引入自定义的tabbar "tabbar":"/Component/tabbar/tabbar" } } # home.wxml <tabbar selected="{{2}}"></tabbar> # 使用自定义的tabbar,selected是传显示判断值的。
-
app.json
{ "pages": [ "pages/index/index", "pages/logs/logs", "pages/redirect/redirect", "pages/userinfo/userinfo", "pages/publish/publish", "pages/telphone/telphone", "pages/atuh/atuh", "pages/pub/pub", "pages/topic/topic", "pages/progress/progress", "pages/logss" ], "window": { "navigationBarBackgroundColor": "#FFDAB9", "navigationBarTextStyle": "black", "navigationBarTitleText": "Demo" }, "tabBar": { "custom": true, # 取消默认tabbar的显示 "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/logs/logs", "text": "我的" } ] }, "sitemapLocation": "sitemap.json" }
登录注册
小程序
-
App.js
配置App({ /* 当小程序初始化完成时,会触发 onLaunch(全局只触发一次) */ onLaunch: function () { //清理在session的数据 //wx.removeStorageSync('userinfo') var userinfo = wx.getStorageSync('userinfo'); if(userinfo){ //在内存中存储一份,加快读取速度。 this.globalData.userinfo=userinfo } }, globalData:{ userinfo:{nickName:"登录", url:"/pages/atuh/atuh" } }, initUserInfo(res,localInfo){ var info = { token:res.token, phone:res.phone, nickName:localInfo.nickName, avatarUrl:localInfo.avatarUrl, url:"null" } // 1.去公共的app.js中调用globalData,里面赋值 this.globalData.userinfo = info; // 在本地“cookie”中赋值(并不是cookie只是功能一样,就这样叫) wx.setStorageSync('userinfo',info); }, })
-
我的页面
// my.wxml <view class="main_candidate"> <view class="inputbox flex"> <text class="input-label">手机号</text> <input name="name" placeholder="请输入手机号" bindinput="bindPhone" maxlength="11" class="primary" value="{{phone}}"/> </view> <view class="inputbox flex"> <text class="input-label" >验证码</text> <input name="code" placeholder="请输入验证码" bindinput="bindCode" maxlength="4" class="primary" value="{{code}}"/> <button class="getCode" bindtap="messageCode">发送验证码</button> </view> <button class="login" form-type="submit"bindtap="onClicksubmit">立即登录</button> <view class="register"> <text bindtap="register">没有账号?去注册</text> </view> </view> // my.wxss /* 使用page就是为了保证 满屏 */ page { width: 100%; height: 100%; } .view_contain { width: 100%; height: 100%; background: #f0eeed } /* 第一部分 */ .view_1 { display: flex; justify-content: center; width: 100%; height: 25%; background: #a0deee; } .view_image_text { width: 100%; display: flex; align-items: center; justify-content: center; flex-direction: column; } .image_radius { height: 50px; width: 50px; border-radius: 30px; } /* 第二部分 */ .view_2 { width: 100%; height: 15%; display: flex; flex-direction: row; align-items: center; justify-content: center; background: white; } .view_tupianwenzi { display: flex; flex-direction: column; width: 120rpx; align-items: center; margin-left: 25rpx; margin-right: 25rpx; } .image_tupian { display: flex; width: 100rpx; height: 100rpx; } /* 第三部分 */ .view_3 { width: 100%; height: 50%; /* background: #f0eeed; */ } .list-item { display: flex; flex-direction: row; align-items: center; width: 100%; height: 80rpx; margin-top: 20rpx; position: relative; /*父元素位置要设置为相对*/ background: white; } .item-image { width: 50rpx; height: 50rpx; margin: 20rpx; } .item-text { color: gray; font-size: 35rpx; margin-left: 20rpx; } .image-jiantou { width: 20rpx; height: 35rpx; position: absolute; /* 要约束所在位置的子元素的位置要设置成绝对 */ right: 0; /* 靠右调节 */ margin-right: 35rpx; } /* 黑线 使得产生阴影效果 */ .line { width: 100%; height: 3rpx; background: lightgray; margin-left: 90rpx; } // my.js //获取全局app文件的数据 var app = getApp(); Page({ /** * 页面的初始数据 */ data: { phone:"登录" }, /** * 生命周期函数--监听页面加载(第一次访问时执行) */ onLoad(options) {}, /** * 生命周期函数--监听页面初次渲染完成(第一次访问时执行) */ onReady() {}, /** * 生命周期函数--监听页面显示(每次访问时执行) */ onShow() { //本地storage中获取值 var phone = wx.getStorageSync('phone'); if(phone){ this.setData({ //查全局文件的globalData // phone:app.globalData.phone phone:phone }) }else{ this.setData({ phone:"登录" }) } }, }) // pages/logs/logs.js var app = getApp(); Page({ /** * 页面的初始数据 */ data: { userinfo:"登录" }, /** * 生命周期函数--监听页面显示(每次访问时执行) */ onShow() { //本地storage中获取值 this.setData({ userinfo:app.globalData.userinfo }) }, })
-
登录页面
// login.wxml <view class="main_candidate"> <view class="inputbox flex"> <text class="input-label">手机号</text> <input name="name" placeholder="请输入手机号" bindinput="bindPhone" maxlength="11" class="primary" value="{{phone}}"/> </view> <view class="inputbox flex"> <text class="input-label" >验证码</text> <input name="code" placeholder="请输入验证码" bindinput="bindCode" maxlength="4" class="primary" value="{{code}}"/> <button class="getCode" bindtap="messageCode">发送验证码</button> </view> <button class="login" form-type="submit"bindtap="login">立即登录</button> <view class="register"> <text bindtap="register">没有账号?去注册</text> </view> </view> // login.wxss .input-label { color: #888; font-size: 12pt; height: 25rpx; line-height: 25rpx; padding: 0 25rpx; border-right: 1px solid #d8d8d8; } .main_candidate{ width: 100%; height: 100%; background-color: #ffffff; margin-top: 30px; } .inputbox{ padding-left: 6px; box-sizing: border-box; border-bottom: 1px solid #dadada; width: 100%; height: 50px; line-height: 50px; font-size: 14px; background-color: #fff; } .flex{ border-radius: 5px; border: 2px solid #f4f4f4; display: flex; align-items: center; margin: 40rpx 0; } .primary{ flex:1; } .inputbox button{ width: 110px; height: 38px; color:#fff; background-color: #5dd5c8; font-size: 16px; } .login{ margin-top: 20px; background-color: #5dd5c8; color: #fff; font-size: 20px; } .register{ color: blue; font-size: 16px; margin: 0 auto; width: 40%; margin-top: 10px; } // login.js // pages/atuh/atuh.js var app = getApp(); Page({ /** * 页面的初始数据 */ data: { phone:"", code:"" }, bindPhone(e){ this.setData({phone:e.detail.value}); }, bindCode(e){ this.setData({code:e.detail.value}); }, /** * 发送短信验证码 */ messageCode(e){ // 输入的数据不等于11执行 if (this.data.phone.length !=11){ //弹窗 wx.showToast({ title: '手机号长度错误', icon:"none" }) return; } //正则匹配手机格式 //"/xxx/"中写正则 var reg = /^(1[3|4|5|6|7|8|9]\d{9})$/; if(!reg.test(this.data.phone)){ //弹窗 wx.showToast({ title: '手机号长度错误', icon:"none" }) return; } //发送短信验证码,登陆成功之后获取jwt和微信用户信息,保存到globalData和本地存储中。 wx.request({ url: 'http://127.0.0.1:8000/api/message/', data: {phone:this.data.phone}, method: "GET", success: (result)=>{ if(result.data.status){ //倒计时计数器 wx.showToast({title: result.data.message,icon:"none"}) }else{ //短信发送失败 wx.showToast({title: result.data.message,icon:"none"}) } }, }) }, /** * 用户登陆 */ onClicksubmit(){ // xw.request向后台发送 wx.request({ url: 'http://127.0.0.1:8000/api/login/', data: {phone:this.data.phone,code:this.data.code}, method: "POST", header:{Authorization:"token dfgrthtryhtjh"}, success: (result) => { if(result.data.status){ wx.getUserProfile({ desc: '获取用户信息', success:(res) => { // 初始化用户信息 app.initUserInfo(result.data.data,res.userInfo); //登陆成功跳到上一级 wx.navigateBack({}); } }) //查看跳转层级记录 //var pages = getCurrentPages(); //prevPage = pages[pages.length-2] }else{ wx.showToast({title: result.data.message,}) } } }) } })
后端
login_api
-
models.py
from django.db import models class UserInfo(models.Model): #创建唯一索引 phone = models.CharField(verbose_name="手机号",max_length=11,unique=True) token = models.CharField(verbose_name="用户TOKEN",max_length=64,null=True,blank=True)
-
urls.py
from django.contrib import admin from django.urls import path,re_path,include from api1 import views urlpatterns = [ path('admin/', admin.site.urls), re_path('^login/', views.ListView.as_view()), re_path('^message/', views.MessageView.as_view()), re_path('^credential/', views.CredentialView.as_view()), ]
-
views.py
from rest_framework.response import Response from rest_framework.views import APIView from . import models import random import uuid # Create your views here. # value就是指定字段的指定数据 from .serializer import account class MessageView(APIView): def get (self, request, *args, **kwargs): # 1.获取手机号 # 2.手机格式校验 ser = account.MessageSerialiaer(data=request.query_params) if not ser.is_valid(): return Response({"status":False,"message":"手机格式错误"}) phone = ser.validated_data.get("phone") # 3.生成随机验证码 random_code = random.randint(1000,9999) # 4.验证码发送到手机上,购买服务器进行发送短信:腾讯云 from .utils.tencent import msg result = msg.send_message(phone,random_code) if not result: return Response({"status":False,"message":"短信发送失败"}) # 5.吧验证码+手机号保留(过期时间30s) # 5.1 搭建redis服务器(云redis) # 我这里没redis就不用了 from django_redis import get_redis_connection conn = get_redis_connection() conn.set(phone,random_code,ex=60) return Response({"status":True,"message":"发送成功"}) class ListView(APIView): def post (self, request, *args, **kwargs): print(request.data) """ 1.校验手机号是否合法 2.校验验证码,redis 验证码获取状态:无数据/有错误/有无错误 3.去数据库中获取用户信息 4.将一些信息返回给小程序 """ ser = account.LoginSerializer(data=request.data) if not ser.is_valid(): return Response({"status":False,"message":"验证码错误"}) # 验证后使用它获取数据 phone = ser.validated_data.get("phone") # 会帮你做如果有则返回一个查询对象的参数和一个逻辑值有是真没有是假,如果没有则创建并返回数据和False user_object,flag = models.UserInfo.objects. get_or_create(phone=phone) # 修改或添加token的值 user_object.token = str(uuid.uuid4()) user_object.save() return Response({"status":True,"data":{"token":user_object.token,"phone":phone}}) class CredentialView(APIView): def get (self,*args,**kwargs): import json import os from sts.sts import Sts config = { 'url': 'https://sts.tencentcloudapi.com/', # 域名,非必须,默认为 sts.tencentcloudapi.com 'domain': 'sts.tencentcloudapi.com', # 临时密钥有效时长,单位是秒 'duration_seconds': 1800, 'secret_id': 'AKIDzAB4nInCPOAk5DrMhoheMogIgqjNn35s', # 固定密钥 'secret_key': 'c09IAm9HCxZ4Vu9AwTSFsPq6A2f7MK5b', # 换成你的 bucket 'bucket': 'static-1312898916', # 换成 bucket 所在地区 'region': 'ap-nanjing', # 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径 # 例子: a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用) 'allow_prefix': '*', # 密钥的权限列表。简单上传和分片需要以下的权限,其他权限列表请看 https://cloud.tencent.com/document/product/436/31923 'allow_actions': [ # 简单上传 'name/cos:PostObject', ], } try: sts = Sts(config) response = sts.get_credential() print('get data : ' + json.dumps(dict(response), indent=4)) return Response(response) except Exception as e: print(e)
serializer
-
account.py
# !/usr/bin/env python # -*- coding:utf-8 -*- from rest_framework import serializers from .validators import phone_validator from django_redis import get_redis_connection from rest_framework.exceptions import ValidationError # 我们没有号码对应的表所以需要继承serializers.Serializer class MessageSerialiaer(serializers.Serializer): # 数据进来后先去判断是否为空,再去判断列表中的函数 phone = serializers.CharField(label="手机号",validators=[phone_validator,]) class LoginSerializer(serializers.Serializer): #这些都是自定义的验证字段 # 数据进来后先去判断是否为空,再去判断列表中的函数 phone = serializers.CharField(label="手机号",validators=[phone_validator,]) code = serializers.CharField(label="短信验证码",) #钩子验证code字段validate_+字段名 def validate_code(self,value): #应该写开应为主动推送的不一样 if len(value) !=4 or not value.isdecimal(): #主动推送错误信息 raise ValidationError("短信格式错误") #在函数内部取值时,使用initial_data应为用了request.data后会将其赋值给它 phone = self.initial_data.get("phone") #去取验证码 """ conn = get_redis_connection() code = conn.get() if not code: raise ValidationError("验证码过期") if value != code.decode("utf-8"): raise ValidationError("验证码错误") """ return value
-
validators.py
# !/usr/bin/env python # -*- coding:utf-8 -*- import re from rest_framework.exceptions import ValidationError def phone_validator(value): if not re.match("^(1[3|4|5|6|7|8|9]\d{9})$",value): # 主动推送特定的错误 raise ValidationError("手机号格式错误")
utils
-
\tencent\msg.py
# !/usr/bin/env python # -*- coding:utf-8 -*- from tencentcloud.common import credential from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException from tencentcloud.sms.v20190711 import sms_client,models def send_message(phone,code): # TODO tencent.send_message(phone,random_code) """ 1.注册腾讯云,开通腾讯云短信。 2.创建应用 sdk appid = 1400705782 3.申请签名(个人:小程序) 476491 python小程序 4.申请模板 1473776 普通短信 5.申请腾讯云api https://console.cloud.tencent.com/cam/capi appid 1312898916 SecretId AKIDzAB4nInCPOAk5DrMhoheMogIgqjNn35s SecretKey c09IAm9HCxZ4Vu9AwTSFsPq6A2f7MK5b 6.调用相关接口去发送短信 pip install --upgrade tencentcloud-sdk-python sdk,写好的工具 """ try: phone = "{}{}".format("+86",phone) cred = credential.Credential("AKIDW3Rgszw84ylQxMzNn7KOJ6kFPSLSL5c5MU","GQSMXmtsjR0QhuIalzTp250nU6digZSD") client = sms_client.SmsClient(cred,"ap-guangzhou") req = models.SendSmsRequest() # 短信应用id req.SmsSdkAppid = "1400302209" # 短信签名内容 req.Sign = "python之路" # 下发手机号+86标准 req.PhoneNumberSet = [phone] # 模板id req.TemplateID = "516680" # 模板参数 req.TemplateParamSet = [code] # 发送短信 resp = client.SendSms(req) # 输出json格式的字符串回包 if resp.SendStatusSet[0].Code == "Ok": return True #print(resp.to_json_string(indent=2)) except TencentCloudSDKException as err: #print(err) pass
settings
-
urls.py
from django.contrib import admin from django.urls import path,re_path,include from api1 import urls urlpatterns = [ path('admin/', admin.site.urls), re_path("^api/", include("api1.urls")), ]
-
setting.py
from pathlib import Path INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'api1.apps.Api1Config' ] DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' # 配置django-redis CACHES = { "default":{ "BACKEND":"django_redis.cache.RedisCache", # 主机ip和端口 "LOCATION":"redis://127.0.0.1:6000", "OPTIONS":{ "CLIENT_CLASS":"django_redis.client.DefaultClient", # 最大访问数 "CONNECTION_POOL_KWARGS":{"max_connections":100} # 密码 # "POASSWORD":"密码", } } }
云存储
- 第一阶段:文件服务器,将文件存储在某个指定的服务器上(支持目录结构的划分)。
- 第二阶段:分为文件服务器和对象服务器,对象存储优化了存储和操作但是无目录结构。
- 第三阶段:使用云服务存储,实现为每个人提供存储服务器。
小程序
-
登录腾讯云并访问https://console.cloud.tencent.com/cos/bucket。
-
点击存储桶列表并创建桶、右上角选择sdk文档中的小程序中详细介绍。
-
wxml
<!--pages/publish/publish.wxml--> <text>pages/publish/publish.wxml</text> <view bindtap="uploadImage">请上传图片</view> <view> <image style="width: 200rpx;height: 200rpx; margin-right: 5rpx;" wx:for="{{imageList}}" src="{{item}}"></image> </view> <view bindtap="uploadFile">点击上传</view>
-
js
// pages/publish/publish.js var COS = require("../../utils/cos-wx-sdk-v5.js") Page({ /** * 页面的初始数据 */ data: { imageList:[], onlineImageList:[] }, uploadImage(){ var that = this; wx.chooseImage({ count: 9, sizeType:["compressed","original"], sourceType:["album","camera"], success(res){ //设置imagelist,页面上自动修改 // that.setData({ // imageList:res.tempFilePaths // }); //先列表中添加单条数据 // that.data.imageList.push("/statlc/images/a.jpg"); //怎加图片并保留之前图片 that.setData({ imageList:that.data.imageList.concat(res.tempFilePaths) }) } }) }, uploadFile(){ // 存储图片在服务器上的url var onlineImageList = []; var that = this; //创建连接对象 var cos = new COS({ // 必选参数 getAuthorization: function (options, callback) { // 服务端其他语言参考 COS STS SDK :https://github.com/tencentyun/qcloud-cos-sts-sdk // STS 详细文档指引看:https://cloud.tencent.com/document/product/436/14048 wx.request({ url: 'http://127.0.0.1:8000/api/credential/', data: { // 可从 options 取需要的参数 }, success: function (result) { var data = result.data; var credentials = data && data.credentials; if (!data || !credentials) return console.error('credentials invalid'); callback({ TmpSecretId: credentials.tmpSecretId, TmpSecretKey: credentials.tmpSecretKey, // v1.2.0之前版本的sdk使用XCosSecurityToken而不是SecurityToken SecurityToken: credentials.sessionToken, // 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误 StartTime: data.startTime, // 时间戳,单位秒,如:1580000000 ExpiredTime: data.expiredTime, // 时间戳,单位秒,如:1580000900 }); } }); } }); // var cos = new COS({ // SecretId: 'AKIDzAB4nInCPOAk5DrMhoheMogIgqjNn35s', // SecretKey: 'c09IAm9HCxZ4Vu9AwTSFsPq6A2f7MK5b', // }); for(var index in this.data.imageList){ var filePath = this.data.imageList[index]; // 先选择文件,得到临时路径 cos.postObject({ Bucket:"db-1316378440", Region:"ap-nanjing", Key:index+"xzx.png", FilePath:filePath, onProgress(info){ console.log(JSON.stringify(info)); } }, function (err,data){ console.log(data); onlineImageList.push(data.Location); }) } } })
后端
-
云存储前后台需要将oss临时上传下载密钥发给小程序。
-
urls
from django.urls import re_path from api1 import views urlpatterns = [ re_path('^credential/', views.CredentialView.as_view()), ]
-
view
from rest_framework.response import Response from rest_framework.views import APIView class CredentialView(APIView): def get (self,*args,**kwargs): import json import os from sts.sts import Sts config = { 'url': 'https://sts.tencentcloudapi.com/', # 域名,非必须,默认为 sts.tencentcloudapi.com 'domain': 'sts.tencentcloudapi.com', # 临时密钥有效时长,单位是秒 'duration_seconds': 1800, 'secret_id': 'AKIDzAB4nInCPOAk5DrMhoheMogIgqjNn35s', # 固定密钥 'secret_key': 'c09IAm9HCxZ4Vu9AwTSFsPq6A2f7MK5b', # 换成你的 bucket 'bucket': 'static-1312898916', # 换成 bucket 所在地区 'region': 'ap-nanjing', # 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径 # 例子: a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用) 'allow_prefix': '*', # 密钥的权限列表。简单上传和分片需要以下的权限,其他权限列表请看 https://cloud.tencent.com/document/product/436/31923 'allow_actions': [ # 简单上传 'name/cos:PostObject', ], } try: sts = Sts(config) response = sts.get_credential() print('get data : ' + json.dumps(dict(response), indent=4)) return Response(response) except Exception as e: print(e)
提交和获取新闻
-
api/url
path("^news/$",news.NewsView.as_view())
-
api/view
class CreateNewsTopicModelSerializer(serializers.Serializer): key = serializers.CharField() cos_path = serializers.CharField() class CreateNewsModelSerializer(serializers.ModelSerializer): imageList = CreateNewsTopicModelSerializer(many=True) class Meta: model = models.News exclude = ["user","viewer_count","comment_count"] def create(self,validated_data): image_list = validated_data.pop("imageList") news_object = models.News.objects.create(**validated_data) data_list = models.NewsDetail.objects.bulk_create( [models.NewsDetail(**info,news=news_object)for info in image_list] ) news_object.imageList = data_list if news_object.topic: news_object.topic.count +=1 news_object.save() return news_object class NewsView(CreateAPIView,ListAPIView): queryset=models.News.objects.prefetch_related("user","topic").order_by("-id") filter_backends = [ReachBottomFilter,PullDownRefreshFilter] def perform_create(self,serializer): new_object = serializer.save(user_id=1) return new_object def get_serializer_class(self): if self.request.method == "POST": return CreateNewsModelsSerializer if self.request.method == "GET": return ListNewsModelsSerializer
-
当你访问网页时会发现新闻网可以查看新闻,但是点赞或评论时需要你登录后才可以,通过api我们要实现这种其实也不难。在执行认证之前会先执行get_authenticators函数,它会将认证返回。我们可以自己编写和返回需要的认证。
Class CommentView(APIView): def get_authenticators(self): if self.request.method == "POST": return [UserAuthentication(),] return [GeneralAuthentication(),] def get(self, request, *args, **kwargs): pass def post(self, request, *args, **kwargs): pass
-
当我们登录注册后发现有些页面可以分享但是有些不可以分享,有两种方式可以实现,第一种是找到页面的js文件中的onShareAppMessage将其注释即可,第二种是在onload中写入
wx.hideShareMenu({});
实现。
微信支付
- 微信小程序平台需要是企业级、在微信支付中还需要绑定商户平台账号也是企业级的。
小程序
app设置
-
app.json
{ "pages": [ "pages/home/home", "pages/logs/logs" ], "window": { "navigationBarBackgroundColor": "#FFDAB9", "navigationBarTextStyle": "black", "navigationBarTitleText": "Demo" }, "tabBar": { "list": [{ "pagePath": "pages/home/home", "text": "home" }, { "pagePath": "pages/logs/logs", "text": "logs" }] } }
主页页面
-
home.wxml
<radio-group bindchange="changeGoods"> <view class="row" wx:for="{{goodsList}}" wx:key="index"> <text>{{item.title}}-{{item.price}}</text> <radio class="radio" value="{{item.id}}"></radio> </view> </radio-group> <button bindtap="doPay">购买</button>
-
home.wxss
.row{ display:flex; flex-direction:row; justify-content:space-between; align-items:center; }
-
home.js
const app = getApp() Page({ data:{ goodsList:[], seletedId:null }, doPayment:function(){ this.data.seletedId wx.request({ url:"http://127.0.0.1:8002/payment/", data:{ goodsId:this.data.seletedId }, method:"POST", dataType:"json", responseType:"text", success:(res) =>{ wx.requestPayment( { "timeStamp":res.data.timeStamp, "nonceStr":res.data.nonceStr, "package":res.data.package, "signType":res.data.signType, "paySign":res.data.paySign, "success":function(res){}, "fail":function(res){}, "complete":function(res){} } ) } }) }, changeGoods:function(e){ this.setData({ seletedId:e.detail.value }) } onLoad:function(){ wx.request({ url:"http://127.0.0.1:8002/goods/", method:"GET", dataType:"json", responseType:"text", success:(res) = >{ this.setData({ goodsList:res.data }) }, }) } })
登录页面
-
logs.wxml
<input placeholder="请输入手机号" bindinput="inputPhone" value="{{phone}}"></input> <button open-type="getUserInfo" bindgetuserinfo="doSubmit">login</button>
-
logs.js
data:{ phone:null } inputPhone:function(e){ this.setData({ phone:e.detail.value }) }, doSubmit:function(e){ wx.login({ success:(result) => { wx.request({ url:"http://127.0.0.1:8002/login", data:{ phone:this.data.phone, //获取一个临时凭证(只能用一次/5分钟) wx_code:result.code }, method:"POST", dataType:"json", responseType:"text", success:(res)=>{ console.log(res) } }) } }) }
后端
app01
-
models.py
from django.db import models class UserInfo(models.Model): phone = models.CharField(verbose_name="手机号",max_length=11,unique=True) token = models.CharField(verbose_name="用户TOKEN",max_length=64,null=True,blank=True) openid = models.CharField(verbose_name="微信唯一标识",max_length=32) class Goods(models.Model): title = models.CharField(verbose_name="商品名称", max_length=32) price = models.PositiveIntegerField(verbose_name="价格") class Order(models.Model): status_choices = ( (1,"待支付"), (2,"已支付") ) status = models.SmallIntegerField(verbose_name="状态",choices=status_choices) goods = models.ForeignKey(verbose_name="商品",to="Goods") user = models.ForeignKey(verbose_name="用户",to="UserInfo") uid = models.Charfield(verbose_name="订阅号", max_length=64)
-
urls.py
from django.contrib import admin from django.urls import path,re_path,include from api1 import views urlpatterns = [ path('admin/', admin.site.urls), re_path('^login/', views.ListView.as_view()), re_path('^goods/', views.GoodsView.as_view()), re_path('^payment/', views.PaymentView.as_view()), re_path('^notify/', views.NotifyView.as_view()), ]
-
views.py
from django.shortcuts import render from rest_framework.views import APIView from rest_framework.response import Response from . import models import uuid import random import requests from rest_framework.generics import ListAPIView from rest_framework import serializers class LoginView(APIView): def post(self, request,*args,**kwargs): phone = request.data.get("phone") wx_code = request.data.get("wx_code") # openid的获取:需要拿着wx_code去微信申请 info = { "appid":"wx55cca0b94f723dc7", "secret":"c000e3ddc95d2ef723b9b010f0ae05d5", "js_code":wx_code, "grant_type":"authorization_code", } result = requests.get(url="https://api.weixin.qq.com/sns/jscode2session",params=info) openid = result.json()["openid"] exists = models.UserInfo.objects.filter(phone=phone).exists() token = str(uuid.uuid4()) if not exists: models.UserInfo.objects.create( phone = phone, token = token, openid = openid ) else: models.UserInfo.objects.filter(phone=phone).update(token=token,openid = openid) return Response({"token":token}) class GoodsModelSerializer(serializers.ModelSerializer): class Meta: model = models.Goods filds = "__all__" def md5(string): import hashlib m = hashlib.md5() m.update(string.encode("utf-8")) return m.hexdigest() class GoodsView(ListAPIView): queryset = models.Goods.objects return Response(queryset) class PaymentView(APIView): def post(self,request,*args,**kwargs): goods_id = request.data.get("goodsId") order_random_string = str(int(time.time())) user_object = models.UserInfo.objects.filter(id=1).first() goods_object = models.Goods.objects.filter(id=goods_id).first() order_object = models.Order.objects.create(goods=goods_object,user==user_object,uid=order_random_string,status=1) # 按照微信的规则,去生成支付需要的一大堆数据 # 1.调用支付统一下单 info = { "appid":"wx55cca0b94f723dc7", "mch_id":"1526049051", "device_info":"wupeiqi-min-program", "nonce_str":"".join([chr(random.randit(65, 90)) for _ in range(12)]), "sign_type":"MD5", "body":"保证经", "detail":"这是一个产品详细信息", "attach":"微信小程序", "out_trade_no":order_random_string, "total_fee":goods_object.price, "spbill_create_ip":request.META.get("REMOTE_ADDR"), #修改为自己的就可以了,返回的信息就可以接收到了 # http://ip:端口/notify/ "notify_url":"http://47.93.4.198:8012/pay/notify/", "trade_type":"JSAPI", "openid":user_object.openid } # 1.1 签名 pay_key = "2SzCvaKgYExuItWBdfsadfasdf" temp = "&".join(["{0}={1}".format(k, info[k]) for k in sorted(info)] + ["{0}={1}".format("key",pay_key,),]) pre_sign = md5(temp).upper() info["sign"] = sign xml_string = "<xml>{0}</xml>".format("".join(["<{0}>{1}</{0}>".format(k,v) for k,v in info.items()])) prepay = requests.post("https://api.mch.weixin.qq.com/pay/unifiedorder",data=xml_string.encode("utf-8")) # 1.3 从结果xml中提取prepay_id from xml.etree import ElementTree as ET root = ET.XML(prepay.content.decode("utf-8")) prepay_dict = {child.tag:child.text for child in root} prepay_id = prepay_id["prepay_id"] # 再次签名 info_dict = { "appId":"wx55cca0b94f723dc7", "timeStamp":str(int(time.time())), "nonceStr":"".join([chr(random.randint(65, 90)) for _ in range(12)]), "package":"prepay_id={0}".format(prepay_id), "signType":"MD5", } temp = "&".join( ["{0}={1}".format(k, info[k]) for k in sorted(info)]+["{0}={1}".format("key",pay_key,),]) sign = md5(temp).upper() return Response(info_dict) class NotifyView(APIView): def post(self, request, *args,**kwargs): # 1. 腾讯会先给我们发送一个xml格式的数据request.body # 2. 将xml转换成json # 3. 去json中获取订单号 out_trade_no # 4. 校验 # 1.获取结果吧结果xml转换为字典格式 root = ET.XML(request.body.decode("utf-8")) result = {child.tag:child.text for child in root} # 2.校验签名是否正确,防止恶意请求。 sign = result.pop("sign") # key为商户平台设置的密钥 key = "ndtghjghjhghkgh" temp = "&".join( ["{0}={1}".format(k, result[k]) for k in sorted(result)]+["{0}={1}".format("key",key,),]) sign = md5(temp).upper() # 签名一致 if local_sign == sign: # 根据订单号,把数据库的订单状态修改 out_trade_no = result.get("out_trade_no") response = """<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>""" return HttpResponse(response)
settings
-
urls.py
from django.contrib import admin from django.urls import path,re_path,include from api1 import urls urlpatterns = [ path('admin/', admin.site.urls), re_path("^app01/", include("app01.urls")), ]
-
setting.py
from pathlib import Path INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'app01.apps.app01Config' ]
celery
-
celery是一个基于python开发的模块,可以帮助我们对任务进行分发和处理。
pip3 install celery # 安装broker:redis或rabbitMQ pip3 install redis/pika
-
快速上手
# s1.py import time from celery import Celery app = Celery('tasks', broker='redis://192.168.10.48:6379', backend='redis://192.168.10.48:6379') @app.task def xxxxxx(x, y): time.sleep(10) return x + y # s2.py #!/usr/bin/env python # -*- coding:utf-8 -*- from s1 import xxxxxx # 立即告知celery去执行xxxxxx任务,并传入两个参数 result = xxxxxx.delay(4, 4) print(result.id) # s3.py from celery.result import AsyncResult from s1 import app async = AsyncResult(id="f0b41e83-99cf-469f-9eff-74c8dd600002", app=app) if async.successful(): result = async.get() print(result) # result.forget() # 将结果删除 elif async.failed(): print('执行失败') elif async.status == 'PENDING': print('任务等待中被执行') elif async.status == 'RETRY': print('任务异常后正在重试') elif async.status == 'STARTED': print('任务已经开始被执行') # 执行 celery worker -A s1 -l info # 注意windows下会报一个错误,需要安装一个模块才可以 # pip install eventlet # celery worker -A s1 -l info -P eventlet python3 s2.py python3 s3.py
Django中应用Celery
-
基本使用
django_celery_demo ├── app01 │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ ├── models.py │ ├── tasks.py │ ├── tests.py │ └── views.py ├── db.sqlite3 ├── django_celery_demo │ ├── __init__.py │ ├── celery.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py ├── red.py └── templates
-
django_celery_demo/celery.py
#!/usr/bin/env python # -*- coding:utf-8 -*- import os from celery import Celery # set the default Django settings module for the 'celery' program. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_celery_demo.settings') app = Celery('django_celery_demo') # Using a string here means the worker doesn't have to serialize # the configuration object to child processes. # - namespace='CELERY' means all celery-related configuration keys # should have a `CELERY_` prefix. app.config_from_object('django.conf:settings', namespace='CELERY') # Load task modules from all registered Django app configs. app.autodiscover_tasks()
-
django_celery_demo/init.py
from .celery import app as celery_app __all__ = ('celery_app',)
-
app01/tasks.py
from celery import shared_task @shared_task def add(x, y): return x + y @shared_task def mul(x, y): return x * y @shared_task def xsum(numbers): return sum(numbers)
-
django_celery_demo/settings.py
... .... ..... # ######################## Celery配置 ######################## CELERY_BROKER_URL = 'redis://10.211.55.20:6379' CELERY_ACCEPT_CONTENT = ['json'] CELERY_RESULT_BACKEND = 'redis://10.211.55.20:6379' CELERY_TASK_SERIALIZER = 'json'
-
app01/views.py
from django.shortcuts import render, HttpResponse from app01 import tasks from django_celery_demo import celery_app from celery.result import AsyncResult def index(request): result = tasks.add.delay(1, 8) print(result) return HttpResponse('...') def check(request): task_id = request.GET.get('task') async = AsyncResult(id=task_id, app=celery_app) if async.successful(): data = async.get() print('成功', data) else: print('任务等待中被执行') return HttpResponse('...')
-
django_celery_demo/urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^index/', views.index), url(r'^check/', views.check), ]
-
定时任务
- 安装
install django-celery-beat
- 安装
-
注册app
INSTALLED_APPS = ( ..., 'django_celery_beat', )
-
数据库去迁移生成定时任务相关表
python manage.py migrate
-
设置定时任务,代码中配置或数据表录入。
# django_celery_demo/celery.py #!/usr/bin/env python # -*- coding:utf-8 -*- import os from celery import Celery # set the default Django settings module for the 'celery' program. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_celery_demo.settings') app = Celery('django_celery_demo') # Using a string here means the worker doesn't have to serialize # the configuration object to child processes. # - namespace='CELERY' means all celery-related configuration keys # should have a `CELERY_` prefix. app.config_from_object('django.conf:settings', namespace='CELERY') app.conf.beat_schedule = { 'add-every-5-seconds': { 'task': 'app01.tasks.add', 'schedule': 5.0, 'args': (16, 16) }, } # Load task modules from all registered Django app configs. app.autodiscover_tasks()
-
后台进程创建任务
celery -A django_celery_demo beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
-
启动worker执行任务
celery -A django_celery_demo worker -l INFO