智慧社区

heart / 2024-06-08 / 原文

智慧社区

# 智慧社区-小程序
	-欢迎页面
	-首页
    	-轮播图
        -公告
        -信息采集,社区活动,人脸检测,语音识别,心率检测,积分商城
   -信息采集页面
		-采集人数
   -采集详情页面
   -采集统计页面
   -人脸检测页面
   -语音识别页面
   -积分商城页面
   -活动
		-活动列表
    	-报名活动
        -加载更多
   -公告
		-公告列表
   -我的
		-信息展示
   -登录

欢迎页面

后端

models

class Welcome(models.Model):
    img = models.ImageField(upload_to='welcome', default='slash.png')
    order = models.IntegerField()
    link = models.CharField(max_length=32)
    create_time = models.DateTimeField(auto_now=True)
    is_delete = models.BooleanField(default=False)

views

from django.shortcuts import render
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from .serializers import WelcomeSerializer
from .models import Welcome


class WelcomeView(GenericViewSet, ListModelMixin):
    queryset = Welcome.objects.all().filter(is_delete=False).order_by("-order")
    serializer_class = WelcomeSerializer

serializer

class WelcomeSerialzier(serializers.ModelSerializer):
    class Meta:
        model=Welcome
        fields=['img','link']

前端

# 1 欢迎页面
	-onLoad---》向后端发送请求
    -拿回图片,盖住整个屏幕
    -倒计时3s(跳过)--》进入到首页

wxml

<view class="container">
    <text bindtap="doJump" class="jump">跳过{{seconds}}秒</text>
    <image class="img" src="{{img}}" bind:tap="goPage"/>
</view>

wxss

page{
	height: 100%;
}

.container {
  height: 100%;
  width: 100%;
} 

.container .img{
  height: 100%;
  width: 100%;
}

.jump{
  font-size: 30rpx;
  position: absolute;
  left: 600rpx;
  top: 80rpx;
  background-color: #dddddd;
  padding: 10rpx 20rpx;
  border-radius: 20rpx;
}

js

import settings from '../../static/js/settings.js'
Page({

  data: {
    seconds:3,
    img:'/static/img/bg/splash3.png',
    url:'/pages/log/log'
  },
  onLoad(options) {

    // 发送请求,获取欢迎页
    wx.request({
      url: settings.welcome,
      method:'GET',
      success:(res)=>{
        this.setData({
          img:res.data[0].img,
          url:res.data[0].link
        })
      }
    })

    let t=setInterval(()=>{
      if(this.data.seconds <= 0){
        //定时器清除
        clearInterval(t)
        //跳转到项目页面 + 配置tabBar
        wx.switchTab({
          url: '/pages/index/index',
        })
      }else{
        this.setData({
          seconds:this.data.seconds - 1
        })
      }
    },1000)
  },
  goPage(){
    wx.navigateTo({
      url: '/pages/second/collection/collection',
    })
  },
  doJump(){
      wx.switchTab({
        url: '/pages/index/index',
      })
  }
})

image-20240528185350249

首页

"pages": [
    "pages/welcome/welcome",
    "pages/index/index",
    "pages/my/my",
    "pages/activity/activity",
    "pages/notice/notice",
],

前端

wxml

<view class="container">

    <!-- 轮播图 -->
    <view class="banner">
        <swiper autoplay indicator-dots circular indicator-color='#FFFFF' interval='3000'>
            <swiper-item>
                <image src="/static/img/banner/banner1.png" mode="widthFix" />
            </swiper-item>
            <swiper-item>
                <image src="/static/img/banner/banner2.png" mode="widthFix" />
            </swiper-item>
            <swiper-item>
                <image src="/static/img/banner/banner3.png" mode="widthFix" />
            </swiper-item>
        </swiper>
    </view>

    <van-notice-bar left-icon="volume-o" text="在代码阅读过程中人们说脏话的频率是衡量代码质量的唯一标准。" />

    <van-grid column-num="3">
        <van-grid-item icon="/static/img/menu/ht.png" text="信息采集" bind:click="gotoCollection" />
        <van-grid-item icon="/static/img/menu/wyf.png" text="社区活动" bind:click="gotoActivity" />
        <van-grid-item icon="/static/img/menu/wygl.png" text="人脸检测" bind:click="gotoFace" />
        <!-- 可以根据需要添加更多的快捷入口 -->
        <van-grid-item icon="/static/img/menu/wylx.png" text="语音识别" bind:click="gotoVoice" />
        <van-grid-item icon="https://b.yzcdn.cn/vant/icon-demo-1126.png" text="心率检测" bind:click="gotoHeart" />
        <van-grid-item icon="/static/img/menu/ht.png" text="积分商城" bind:click="gotoGoods" />
    </van-grid>
    <view class="bottom">
        <view>
            <image src="/static/img/home/cute_1.jpg" mode="scaleToFill" />
        </view>
        <view>
            <image src="/static/img/home/cute_2.jpg" mode="scaleToFill" />
        </view>
        <view>
            <image src="/static/img/home/cute_3.jpg" mode="scaleToFill" />
        </view>
        <view>
            <image src="/static/img/home/cute_4.jpg" mode="scaleToFill" />
        </view>
    </view>
</view>

wxss

.banner image{
    width: 100%; /* 图片宽度拉伸至容器宽度 */
    height: 100%; /* 图片高度拉伸至容器高度 */
    object-fit: cover; /* 图片将覆盖整个容器区域,可能被裁剪 */
}

.bottom{
    display: flex;
    justify-content: space-evenly;
    margin-top: 20rpx;
    flex-wrap: wrap;
}

.bottom>view>image{
    width: 345rpx;
    height: 200rpx;
}

js

// index.js
Page({
    gotoCollection(){
        wx.navigateTo({
            url: '/pages/second/collection/collection'
        })
    },
    gotoFace(){
        wx.navigateTo({
            url: '/pages/second/face/face'
        })
    },
    gotoVoice(){
        wx.navigateTo({
            url: '/pages/second/voice/voice'
        })
    },

    gotoActivity(){
        wx.switchTab({
            url: '/pages/activity/activity',
        })
    },
    gotoHeart(){
        wx.navigateTo({
            url: '/pages/second/heart/heart',
        })
    },
    gotoGoods(){
        wx.navigateTo({
            url: '/pages/second/goods/goods',
        })
    },
})

image-20240528185421570

信息采集

<view class="container">
    <view class="top">
        <view class="tip">今日采集数量(人)</view>
        <view class="count">{{dataDict.today_count}}</view>
    </view>
    <view class="function">
        <view class="menu" style="border-right:1rpx solid #ddd;" bindtap="bindToForm"> 
            <text class="iconfont icon-xinxicaiji"></text> 信息采集
        </view>
        <view class="menu" bindtap="bindToStatistics">  
            <text class="iconfont icon-shujutongji" ></text> 数据统计
        </view>
    </view>
    <view class="table">
        <view class="item">
            <view class="title">社区信息列表({{dataDict.today_count}}人)</view>
        </view>
        <view class="item" wx:for="{{dataDict.result}}" wx:for-item="row" wx:key="index">
            <view class="record">
                <view class="avatar">
                    <image src="{{row.avatar}}"></image>
                </view>

                <view class="desc">
                    <view class="username">{{row.name}}</view>

                    <view>
                        <view class="txt-group">
                            <label class="zh">网格区域</label>
                            <label class="en"> | {{row.area.desc}}</label>
                        </view>
                        <view class="area"> 
                            <label class="fa fa-map-marker"></label> {{row.area.name}} 
                        </view>
                    </view>
                </view>
                <view class="delete" bindtap="doDeleteRow" data-nid="{{row.id}}" data-index="{{index}}" >
                    <label class="iconfont icon-shanchu"></label>
                </view>
            </view>
        </view>
    </view>
</view>

wxss

.top {
    background-color: #01ccb6;
    height: 200rpx;

    display: flex;
    flex-direction: column;
    align-items: center;
    color: white;
}

.top .tip {
    font-size: 22rpx;
    font-weight: 100;
}

.top .count {
    padding: 10rpx;
    font-size: 58rpx;
}

.function {
    display: flex;
    flex-direction: row;
    justify-content: space-around;
    background-color: #02bfae;
}

.function .menu {
    font-size: 28rpx;
    margin: 25rpx 0;
    color: white;
    width: 50%;
    text-align: center;
    flex-direction: row;
    flex-direction: column;
    align-items: center;
}

.table .item {
    border-bottom: 1rpx solid #e7e7e7;

}

.table .item .title{
    margin: 20rpx 30rpx;
    padding-left: 10rpx;
    border-left: 5rpx solid #02bfae;
    font-size: 26rpx;
}

.record{
    margin: 30rpx 40rpx;
    display: flex;
    flex-direction: row;
    justify-content: flex-start;
}

.record .avatar{
    width: 200rpx;
    height: 200rpx;
}

.record .avatar image{
    width: 100%;
    height: 100%;
    border-radius: 30rpx;
}



.record .desc{
    margin: 0 40rpx;
}
.desc{
    width: 290rpx;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}
.desc .username{
    margin-top: 25rpx;
    font-size: 38rpx;
}

.txt-group{
    font-size: 27rpx;
    margin: 10rpx 0;
}
.txt-group .zh{
    color: #8c8c8c;
}

.txt-group .en{
    color: #cccccc;
}

.area{
    color: #00c8b6;
    font-weight: bold;
}

.delete{
    width: 100rpx;
    color: red;
    text-align: center;
    display: flex;
    flex-direction: column;
    justify-content: center;
}

json

{
    "usingComponents": {},
    "navigationBarBackgroundColor": "#01ccb6",
    "navigationBarTitleText": "",
    "enablePullDownRefresh": true,
    "navigationBarTextStyle":"white",
    "backgroundColor":"#01ccb6"
}

js

// pages/second/collection/collection.js
Page({
    data: {
    },
    onLoad(options) {
    },
    bindToForm(){
        wx.navigateTo({
          url: '/pages/second/camera/camera',
        })
      }
})

image-20240528185732014

camera

wxml

<camera class="camera"  device-position="{{ backFront ? 'back' : 'front' }}"  flash="off" frame-size="medium" ></camera>

 
<view class="function">
  <view class="switch"> </view>
  <view class="record" bindtap="takePhoto">
    <image src="/static/img/camera/record_on.png"></image>
  </view>
  <view class="switch" bindtap="switchCamera">
    <image src="/static/img/camera/rotate-camera-white.png"></image>
  </view>
 </view>
page{
    height: 100%;
}
.camera{
    height: 80%;
    width: 100%;
}

.function{
    height: 20%;
    background-color: black;

    display: flex;
    flex-direction: row;
    justify-content: space-around;
    align-items: center;
}

.record image{
    width: 160rpx;
    height: 160rpx;
}

.switch{
    color: white;
    width: 80rpx;
    height: 80rpx;
}

.switch image{
    width: 80rpx;
    height: 80rpx;
}
Page({
    data: {
      backFront:true
  
    },
  
    switchCamera(e) {
      var old = this.data.backFront
      this.setData({
        backFront: !old
      })
    },
  })

image-20240528185743783

首页轮播图和公告接口

前端

html

<view class="container">

    <!-- 轮播图 -->
    <view class="banner">
        <swiper autoplay indicator-dots circular indicator-color='#FFFFF' interval='3000'>
            <swiper-item wx:for="{{banner_list}}" wx:key="id">
                <image src="{{item.img}}" mode="widthFix" />
            </swiper-item>
        </swiper>
    </view>
    <van-notice-bar left-icon="volume-o" text="{{notice}}" />


    <van-grid column-num="3">
        <van-grid-item icon="/static/img/menu/ht.png" text="信息采集" bind:click="gotoCollection" />
        <van-grid-item icon="/static/img/menu/wyf.png" text="社区活动" bind:click="gotoActivity" />
        <van-grid-item icon="/static/img/menu/wygl.png" text="人脸检测" bind:click="gotoFace" />
        <!-- 可以根据需要添加更多的快捷入口 -->
        <van-grid-item icon="/static/img/menu/wylx.png" text="语音识别" bind:click="gotoVoice" />
        <van-grid-item icon="https://b.yzcdn.cn/vant/icon-demo-1126.png" text="心率检测" bind:click="gotoHeart" />
        <van-grid-item icon="/static/img/menu/ht.png" text="积分商城" bind:click="gotoGoods" />
    </van-grid>
    <view class="bottom">
        <view>
            <image src="/static/img/home/cute_1.jpg" mode="scaleToFill" />
        </view>
        <view>
            <image src="/static/img/home/cute_2.jpg" mode="scaleToFill" />
        </view>
        <view>
            <image src="/static/img/home/cute_3.jpg" mode="scaleToFill" />
        </view>
        <view>
            <image src="/static/img/home/cute_4.jpg" mode="scaleToFill" />
        </view>

    </view>

</view>

css

.banner image{
    width: 100%; /* 图片宽度拉伸至容器宽度 */
    height: 100%; /* 图片高度拉伸至容器高度 */
    object-fit: cover; /* 图片将覆盖整个容器区域,可能被裁剪 */
}
.bottom{
    display: flex;
    justify-content: space-evenly;
    margin-top: 20rpx;
    flex-wrap: wrap;
}

.bottom>view>image{
    width: 345rpx;
    height: 200rpx;
}

js

import settings from '../../static/js/settings.js'
Page({
    data: {
        banner_list: {},
        notice: ''
    },
    onLoad(options) {
        wx.request({
            url: settings.banner,
            method:'GET',
            success:(res)=>{
                if(res.data.code==100){
                    this.setData({
                        banner_list:res.data.banner,
                        notice:res.data.notice.title + "快来玩呀~~~~嗯~~~"
                    })
                }else{
                    wx.showToast({
                        title: '轮播图网络加载失败',
                    })
                }
            }
        })
    },
    gotoCollection() {
        wx.navigateTo({
            url: '/pages/second/collection/collection'
        })
    },
    gotoFace() {
        wx.navigateTo({
            url: '/pages/second/face/face'
        })
    },
    gotoVoice() {
        wx.navigateTo({
            url: '/pages/second/voice/voice'
        })
    },

    gotoActivity() {
        wx.switchTab({
            url: '/pages/activity/activity',
        })
    },
    gotoHeart() {
        wx.navigateTo({
            url: '/pages/second/heart/heart',
        })
    },
    gotoGoods() {
        wx.navigateTo({
            url: '/pages/second/goods/goods',
        })
    },
})

image-20240529150327551

后端

views

from django.shortcuts import render
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from .serializers import WelcomeSerializer, BannerSerializer, NoticeSerializer
from .models import Welcome, Notice, Banner
from rest_framework.response import Response


# 广告
class WelcomeView(GenericViewSet, ListModelMixin):
    queryset = Welcome.objects.all().filter(is_delete=False).order_by("-order")
    serializer_class = WelcomeSerializer


# 轮播图
class BannerView(GenericViewSet, ListModelMixin):
    queryset = Banner.objects.all().filter(is_delete=False).order_by("order")[:2]
    serializer_class = BannerSerializer

    def list(self, request, *args, **kwargs):
        res = super().list(request, *args, **kwargs)
        notice = Notice.objects.all().order_by("create_time").first()
        serializer = NoticeSerializer(instance=notice)
        return Response(
            {"code": 100, "msg": "成功", "banner": res.data, "notice": serializer.data}
        )

models

from django.db import models


# 广告表
class Welcome(models.Model):
    img = models.ImageField(upload_to="welcome", default="slash.png")
    order = models.IntegerField()
    link = models.CharField(max_length=32)
    create_time = models.DateTimeField(auto_now=True)
    is_delete = models.BooleanField(default=False)


# 轮播图
class Banner(models.Model):
    img = models.ImageField(
        upload_to="banner", default="banner1.png", verbose_name="图片"
    )
    order = models.IntegerField(verbose_name="顺序")
    create_time = models.DateTimeField(auto_now=True, verbose_name="创建时间")
    is_delete = models.BooleanField(default=False, verbose_name="是否删除")

    class Meta:
        verbose_name_plural = "轮播图"


# 公告
class Notice(models.Model):
    title = models.CharField(max_length=64, verbose_name="公共标题")
    content = models.TextField(verbose_name="内容")
    img = models.ImageField(
        upload_to="notice", default="notice.png", verbose_name="公告图片"
    )
    create_time = models.DateTimeField(auto_now=True, verbose_name="创建时间")

    class Meta:
        verbose_name_plural = "公告表"

admin

from django.contrib import admin

# Register your models here.
from .models import Welcome, Banner, Notice

admin.site.register(Welcome)
admin.site.register(Banner)
admin.site.register(Notice)

serializer

from rest_framework import serializers
from .models import Welcome, Banner, Notice


class WelcomeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Welcome
        fields = ["img", "link"]


# 轮播图表序列化类
class BannerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Banner
        fields = "__all__"


# 社区通知序列化类
class NoticeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Notice
        fields = ["id", "title"]

url

from django.contrib import admin
from django.urls import path, include
from .views import WelcomeView, BannerView
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register("welcome", WelcomeView, "welcome")
router.register("banner", BannerView, "banner")
urlpatterns = [path("", include(router.urls))]

信息采集拍照上传

# 1 小程序端拍照---》上传到咱们后台--》后台保存一份(media)--》把图片---》传到百度ai人脸识别库中存着

# 2 学习
	-百度ai人脸库上传
    -百度ai人脸库删除
    -百度ai人脸库根据人脸匹配

人脸识别(上传-删除-查询)

详情见 https://ai.baidu.com/ai-doc/FACE/ek37c1qiz

from aip import AipFace
import base64
from pypinyin import lazy_pinyin, Style
class BaiDuAI:
    def __init__(self,APP_ID='',API_KEY='',SECRET_KEY=''):
        """ 你的 APPID AK SK """
        self.APP_ID = APP_ID
        self.API_KEY = API_KEY
        self.SECRET_KEY = SECRET_KEY
        self.client = AipFace(self.APP_ID, self.API_KEY, self.SECRET_KEY)
    def name_to_pinyin(self,text):
        style = Style.TONE3
        name_list=lazy_pinyin(text, style=style)
        return ''.join(name_list)
    def add_user(self):
        image = base64.b64encode(open('./彭于晏.jpg','rb').read()).decode('utf-8')
        imageType = "BASE64"
        groupId = "100"
        userId=self.name_to_pinyin('彭于晏')
        """ 调用人脸注册 """
        res=self.client.addUser(image, imageType, groupId, userId)
        print(res)

        """ 如果有可选参数 """
        # options = {}
        # options["user_info"] = "彭于晏"
        # options["quality_control"] = "NORMAL"
        # options["liveness_control"] = "LOW"
        # options["action_type"] = "REPLACE"
        #
        # """ 带参数调用人脸注册 """
        # self.client.addUser(image, imageType, groupId, userId, options)
        '''
        {'error_code': 0, 'error_msg': 'SUCCESS', 'log_id': 937906163, 'timestamp': 1716948937, 'cached': 0, 'result': {'face_token': '7598884f9d9a349218941e8e6f52c884', 'location': {'left': 493.4, 'top': 329.74, 'width': 348, 'height': 321, 'rotation': -8}}}
        '''
    def search(self):
        image = base64.b64encode(open('./pyy.jpeg', 'rb').read()).decode('utf-8')
        imageType = "BASE64"
        groupIdList = "100,2"
        """ 调用人脸搜索 """
        res=self.client.search(image, imageType, groupIdList);
        """ 如果有可选参数 """
        # options = {}
        # options["match_threshold"] = 70
        # options["quality_control"] = "NORMAL"
        # options["liveness_control"] = "LOW"
        # # options["user_id"] = "233451"
        # options["max_user_num"] = 3
        #
        # """ 带参数调用人脸搜索 """
        # res=self.client.search(image, imageType, groupIdList, options)
        print(res)
        '''
        {'error_code': 0, 'error_msg': 'SUCCESS', 'log_id': 1190062038, 'timestamp': 1716949190, 'cached': 0, 'result': {'face_token': 'bdbc6214eb18ccee1bf72d1f72f0c979', 'user_list': [{'group_id': '100', 'user_id': 'peng2yu2yan4', 'user_info': '', 'score': 14.822490692139}]}}
        '''
    def delete(self):
        userId = ""
        groupId = ""
        faceToken = ""
        """ 调用人脸删除 """
        res=self.client.faceDelete(userId, groupId, faceToken);
        print(res)
        '''
        {'error_code': 0, 'error_msg': 'SUCCESS', 'log_id': 1355696697, 'timestamp': 1716949355, 'cached': 0, 'result': None}
        '''
if __name__ == '__main__':
    ai=BaiDuAI()
    # ai.add_user()
    # ai.search()
    ai.delete()

后端接口

# 1 采集上传接口---》存本地--》存百度ai
# 2 查询今天采集数据--》1 当前用户(没实现) 2 当天采集的数据  3 总条数Collection.objects.all().filter(create_time__gte=datetime.now().date())
# 3 删除采集接口-->id号删除

views

class CollectionView(GenericViewSet, ListModelMixin, DestroyModelMixin, CreateModelMixin):
    # 查出当天的--》没过滤当前用户
    queryset = Collection.objects.all().filter(create_time__gte=datetime.now().date())
    serializer_class = CollectionSerializer

    def get_serializer_class(self):
        if self.action == 'create':
            return CollectionSaveSerializer
        else:
            return CollectionSerializer

    def list(self, request, *args, **kwargs):
        # 过滤当前用户采集的--》多种类型用户,数据权限不一样
        res = super().list(request, *args, **kwargs)
        today_count = len(self.get_queryset())
        return Response({'code': 100, 'msg': '成功', 'result': res.data, 'today_count': today_count})

    ## 删除人脸,没有删除 具体的图片--》定时任务--》每天晚上备份用户头像
    def destroy(self, request, *args, **kwargs):
        from libs.baidu_ai import BaiDuFace
        instance = self.get_object()
        # 百度ai中删除
        baidu = BaiDuFace()
        res = baidu.delete(instance.name_pinyin, instance.face_token)
        print(res)
        self.perform_destroy(instance)
        return Response()

    # def perform_destroy(self, instance):
    #     from libs.baidu_ai import BaiDuFace
    #     baidu = BaiDuFace()
    #     res = baidu.delete(instance.name_pinyin, instance.face_token)
    #     print(res)
    #     super().perform_destroy(instance)

urls

router.register('collection', CollectionView, 'collection') # 3个接口


## 3个接口######
http://192.168.1.96:8000/api/v1/collection/  # get 获取数据
http://192.168.1.96:8000/api/v1/collection/  # post 提交采集
http://192.168.1.96:8000/api/v1/collection/1/  # delete 删除

serializer

# 上传人脸序列化类
class CollectionSaveSerializer(serializers.ModelSerializer):
    class Meta:
        model = Collection
        fields = ['name', 'avatar', 'area']
    def create(self, validated_data):
        # 在百度ai注册
        from libs.baidu_ai import BaiDuFace
        baidu=BaiDuFace()
        avatar_file_object = validated_data.get('avatar')
        print(avatar_file_object)
        name = validated_data.get('name')
        name_pinyin=baidu.name_to_pinyin(name)
        res=baidu.add_user(avatar_file_object,name,name_pinyin)
        validated_data['name_pinyin'] = name_pinyin
        validated_data['face_token'] = res.get('result').get('face_token')
        instance=super().create(validated_data)
        return instance
# 采集序列化类
class CollectionSerializer(serializers.ModelSerializer):  # 查询所有序列化类
    class Meta:
        model = Collection
        fields = ['id', 'name', 'avatar', 'area']
        depth = 1  # area 外键关联详情拿到

image-20240529154533105

采集列表页面 collection

wxml

<view class="container">
  <view class="top">
    <view class="tip">今日采集数量(人)</view>
    <view class="count">{{dataDict.today_count}}</view>
  </view>

  <view class="function">

    <view class="menu" style="border-right:1rpx solid #ddd;" bindtap="bindToForm"> 
      <text class="iconfont icon-xinxicaiji"></text> 信息采集
    </view>

    <view class="menu" bindtap="bindToStatistics">  
      <text class="iconfont icon-shujutongji" ></text> 数据统计
    </view>

  </view>
  
  <view class="table">
    <view class="item">
      <view class="title">社区信息列表({{dataDict.today_count}}人)</view>
    </view>

    <view class="item" wx:for="{{dataDict.result}}" wx:for-item="row" wx:key="index">
      <view class="record">
        <view class="avatar">
          <image src="{{row.avatar}}"></image>
        </view>

        <view class="desc">
          <view class="username">{{row.name}}</view>
          
          <view>
            <view class="txt-group">
              <label class="zh">网格区域</label>
              <label class="en"> | {{row.area.desc}}</label>
            </view>
            <view class="area"> 
              <label class="fa fa-map-marker"></label> {{row.area.name}} 
            </view>
          </view>
        </view>
        <view class="delete" bindtap="doDeleteRow" data-nid="{{row.id}}" data-index="{{index}}" >
          <label class="iconfont icon-shanchu"></label>
        </view>
      </view>
    </view>

  </view>
</view>

wxss

.top {
  background-color: #01ccb6;
  height: 200rpx;

  display: flex;
  flex-direction: column;
  align-items: center;
  color: white;
}

.top .tip {
  font-size: 22rpx;
  font-weight: 100;
}

.top .count {
  padding: 10rpx;
  font-size: 58rpx;
}

.function {
  display: flex;
  flex-direction: row;
  justify-content: space-around;
  background-color: #02bfae;
}

.function .menu {
  font-size: 28rpx;
  margin: 25rpx 0;
  color: white;
  width: 50%;
  text-align: center;
  flex-direction: row;
  flex-direction: column;
  align-items: center;
}

.table .item {
  border-bottom: 1rpx solid #e7e7e7;

}

.table .item .title{
  margin: 20rpx 30rpx;
  padding-left: 10rpx;
  border-left: 5rpx solid #02bfae;
  font-size: 26rpx;
}

.record{
  margin: 30rpx 40rpx;
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
}

.record .avatar{
  width: 200rpx;
  height: 200rpx;
}

.record .avatar image{
  width: 100%;
  height: 100%;
  border-radius: 30rpx;
}



.record .desc{
  margin: 0 40rpx;
}
.desc{
  width: 290rpx;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
.desc .username{
  margin-top: 25rpx;
  font-size: 38rpx;
}

.txt-group{
  font-size: 27rpx;
  margin: 10rpx 0;
}
.txt-group .zh{
  color: #8c8c8c;
}

.txt-group .en{
  color: #cccccc;
}

.area{
  color: #00c8b6;
  font-weight: bold;
}

.delete{
  width: 100rpx;
  color: red;
  text-align: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

js

import api from '../../../static/js/api.js'
Page({

  data: {
    dataDict: {
      result: [
      
    ],
      today_count: 0,
    }

  },

  onLoad(options) {
    //发送网络请求... 
    this.refresh();
  },
  onShow(){
    this.refresh();
  },
  refresh() {
    //1.发送网络请求
    //2.数据绑定
    wx.showLoading({
      mask: true
    })
    wx.request({
      url: api.collection,
      method: "GET",
      success: (res) => {
        this.setData({
          dataDict: res.data
        })
      },
      complete() {
        wx.hideLoading()
      }
    })},

  bindToForm(){
    wx.navigateTo({
      url: '/pages/second/form/form',
    })
  },
  doDeleteRow(e){
    wx.showModal({
      title: '确认是否删除?',
      confirmColor: "#ff461f",
      success: (res) => {
        if (!res.confirm) {
          return
        }
       
        var nid = e.currentTarget.dataset.nid
       
        wx.showLoading({
          title: '删除中',
          mask:true
        })
        wx.request({
          url: api.collection + nid + '/',
          method:'DELETE',
          success:(res) =>{
            this.refresh()
          },
          complete() {
            wx.hideLoading()
          }
        })
       
      }
    })
  },
})

信息采集form页

html

<view class="avatar">
  <image src='{{avatar}}' bindtap="bindToCamera"></image>
</view>

<view class="form">
  <view class="row-group">
    <input placeholder="请填写姓名" placeholder-class='txt' model:value="{{name}}" bindinput="bindNameChange" />
  </view>

  <view class="picker-group">
    <picker bindchange="bindPickerChange" value="{{index}}" range="{{objectArray}}" range-key="name">

      <view wx:if="{{ index > -1}}" class="picker-txt picker">当前网格:{{objectArray[index].name}}</view>
      <view wx:else class="picker-txt" >请选择网格</view>
      
    </picker>
  </view>
  <view>
    <button class="submit" bindtap="postUser" > 提 交 </button>
  </view>
</view>

wxss

.avatar{
  display: flex;
  flex-direction: column;
  align-items: center;
}

.avatar image {
  margin-top: 140rpx;
  width: 300rpx;
  height: 300rpx;
  border-radius: 30rpx;
  border: 1px solid #ddd;
}

.form{
  padding: 40rpx;
}

.form .row-group{
  padding: 10rpx 0;
  border-bottom: 1rpx solid #ddd;
  position: relative;
  margin-top: 30rpx;
}



.form .row-group text{
  font-size: 28rpx;
  padding:20rpx 0;
}

.form .row-group input{
  padding: 10rpx 0;
}

.form .row-group .txt{
  color: #ccc;
  font-size: 28rpx;
}

.form .picker-group{
  border-bottom: 1rpx solid #ddd;
}

.form .picker-group .picker-txt{
  color: #ccc;
  font-size: 28rpx;
  padding: 40rpx 0 20rpx 0;
}

.form .picker-group .picker{
  color: black;
}

.form .submit{
  margin-top: 80rpx;
  color: #fff;
  border: 2rpx solid #00c8b6;
  background-color: #00c8b6;
  font-size: 32rpx;
  font-weight: bold;
}

js

.avatar{
  display: flex;
  flex-direction: column;
  align-items: center;
}

.avatar image {
  margin-top: 140rpx;
  width: 300rpx;
  height: 300rpx;
  border-radius: 30rpx;
  border: 1px solid #ddd;
}

.form{
  padding: 40rpx;
}

.form .row-group{
  padding: 10rpx 0;
  border-bottom: 1rpx solid #ddd;
  position: relative;
  margin-top: 30rpx;
}



.form .row-group text{
  font-size: 28rpx;
  padding:20rpx 0;
}

.form .row-group input{
  padding: 10rpx 0;
}

.form .row-group .txt{
  color: #ccc;
  font-size: 28rpx;
}

.form .picker-group{
  border-bottom: 1rpx solid #ddd;
}

.form .picker-group .picker-txt{
  color: #ccc;
  font-size: 28rpx;
  padding: 40rpx 0 20rpx 0;
}

.form .picker-group .picker{
  color: black;
}

.form .submit{
  margin-top: 80rpx;
  color: #fff;
  border: 2rpx solid #00c8b6;
  background-color: #00c8b6;
  font-size: 32rpx;
  font-weight: bold;
}

拍照页面camera

wxml

<camera class="camera"  device-position="{{ backFront ? 'back' : 'front' }}"  flash="off" frame-size="medium" ></camera>

 
<view class="function">
  <view class="switch"> </view>
  <view class="record" bindtap="takePhoto">
    <image src="/static/img/camera/record_on.png"></image>
  </view>
  <view class="switch" bindtap="switchCamera">
    <image src="/static/img/camera/rotate-camera-white.png"></image>
  </view>
 </view>

wxss

page{
	height: 100%;
}
.camera{
  height: 80%;
  width: 100%;
}


.function{
  height: 20%;
  background-color: black;
  
  display: flex;
  flex-direction: row;
  justify-content: space-around;
  align-items: center;
}
.record image{
  width: 160rpx;
  height: 160rpx;
}

.switch{
  color: white;
  width: 80rpx;
  height: 80rpx;
}
.switch image{
  width: 80rpx;
  height: 80rpx;
}

js

// pages/second/camera/camera.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    backFront:true

  },

  takePhoto(){
    const ctx = wx.createCameraContext()
    ctx.takePhoto({
      quality: 'high',
      success: (res) => {    
        // 获取照片 
        //console.log(res);

        // 对上一个页面中的值,进行修改
        var pages = getCurrentPages();
        var prevPage = pages[pages.length - 2];
        // 把上个页面的图片位置设为刚刚拍的照
        prevPage.setData({
          avatar: res.tempImagePath
        })

        //跳转回上一个页面  [v1,v2,v2]
        wx.navigateBack({});
      }
    })
  },


  switchCamera(e) {
    var old = this.data.backFront
    this.setData({
      backFront: !old
    })
  },
})

查询所有网格接口

不完善-当前用户网格

#### 路由####
router.register('area', AreaView, 'area')

## 视图类####
class AreaView(GenericViewSet, ListModelMixin):
    queryset = Area.objects.all()
    serializer_class = AreaSerializer
    # 配置一个过滤类--》在过滤类中对 queryset进行过滤---》过滤出当前用户的网格
## 序列化类#### 
class AreaSerializer(serializers.ModelSerializer):
    class Meta:
        model = Area
        fields = ['id', 'name', 'desc']
#### 网格数据录入使用admin####

信息采集统计

前端

wxml

<view class="container">
  <view class="menu" wx:for="{{dataList}}" wx:key="index">
    <view> <label class="iconfont  icon-SCHEDULE" ></label>   {{item.date}}</view>
    <label>{{item.count}}个</label>
  </view>
</view>

wxss

.container{
  border-top: 1px solid #ddd;
}

.container .menu{
  font-size: small;
  padding: 10px 40rpx;
  border-bottom: 1px dotted #ddd;
  text-align: center;

  display: flex;
  flex-direction: row;
  justify-content: space-between;
  background-color: white;
}

js

import settings from '../../../static/js/settings.js'
Page({
  /**
   * 页面的初始数据
   */
  data: {
    dataList:[{'date':'2024年4月20日','count':22},{'date':'2024年4月21日','count':12},{'date':'2024年4月22日','count':232}]
  },
  getRecord:function(){
    wx.showLoading({mask:true})
    wx.request({
      url: settings.statistics,
      method:"GET",
      success :(res) =>{
        this.setData({
          dataList:res.data
        })
      },
      complete:()=>{
        wx.hideLoading()
      }
    })
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {
      this.getRecord();
  },
  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh() {
    this.getRecord();
  },
})

json

{
  "usingComponents": {},
  "navigationBarTitleText": "采集统计",
  "enablePullDownRefresh": true
}

后端

views

class StatisticsView(GenericViewSet, ListModelMixin):
    queryset = Collection.objects.annotate(date=Trunc('create_time', 'day')).values('date').annotate(count=Count('id')).values('date', 'count')
    serializer_class = StatisticsListSerializer

serializer

# 采集统计序列化类
class StatisticsListSerializer(serializers.Serializer):
    date = serializers.DateTimeField(format="%Y年%m月%d日")
    count = serializers.IntegerField()

models

class Collection(models.Model):
    name = models.CharField(max_length=32, verbose_name='采集人员姓名')
    name_pinyin=models.CharField(max_length=32, verbose_name='姓名拼音',null=True)
    avatar = models.ImageField(upload_to='collection/%Y/%m/%d/', default='default.png', verbose_name='头像')
    create_time = models.DateTimeField(verbose_name='采集时间',default=datetime.now())
    face_token=models.CharField(max_length=128, verbose_name='百度ai的Token',null=True)
    area = models.ForeignKey(to='Area', null=True, verbose_name='网格区域', on_delete=models.CASCADE)

    class Meta:
        verbose_name_plural = '采集表'
    def __str__(self):
        return self.name

image-20240529154807975

人脸检测功能

前端

<view class="header">
    <camera class="camera" device-position="{{ backFront ? 'back' : 'front' }}" flash="off" frame-size="medium"></camera>


    <view class="switch" bindtap="switchCamera">
        <image src="/images/camera/rotate-camera-white.png"></image>
    </view>
    <button class="submit" bindtap="takePhoto"> 拍照检测 </button>
</view>


<view class="table">
    <view class="item">
        <view class="title">检测记录</view>
    </view>

    <view class="item" wx:for="{{record}}" wx:for-item="row" wx:key="index">
        <view class="record">
            <view class="avatar">
                <image src="{{row.avatar}}"></image>
            </view>
            <view class="desc">
                <view wx:if="{{row.code == 100}}" class="username">检测成功:{{row.user_id}}</view>
                <view wx:else class="username">检测失败:{{row.msg}}</view>
                <view>
                    <view class="txt-group">
                        <label class="zh">{{row.error_msg}}</label>
                    </view>
                </view>
            </view>
            <view class="delete">
                <block wx:if="{{row.code == 100}}">
                    <label class="iconfont icon-ziyuanxhdpi" style="color:green"></label>
                </block>
                <block wx:else>
                    <label class="iconfont icon-ziyuanxhdpi" style="color:red"></label>
                </block>

            </view>
        </view>
    </view>
</view>

css

/* pages/face/face.wxss */
.header{
  position: relative;
}
.camera{
  height: 600rpx;
  width: 100%;
}


.switch{
  position: absolute;
  top: 10rpx;
  right: 20rpx;

  height: 80rpx;
  width: 80rpx;
}

.switch image{
  height: 100%;
  width: 100%;
}

.submit{
  margin-top: 40rpx;
  color: #fff;
  border: 2rpx solid #00c8b6;
  background-color: #00c8b6;
  font-size: 32rpx;
  font-weight: normal;
}

.table{
  margin-top: 40rpx;
  border-top: 1rpx solid #e7e7e7;
}

.table .item {
  border-bottom: 1rpx solid #e7e7e7;

}

.table .item .title{
  margin: 20rpx 30rpx;
  padding-left: 10rpx;
  border-left: 5rpx solid #02bfae;
  font-size: 26rpx;
}

.record{
  margin: 10rpx 40rpx;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}

.record .avatar{
  width: 100rpx;
  height: 100rpx;
}

.record .avatar image{
  width: 100%;
  height: 100%;
  border-radius: 30rpx;
}

.record .desc{
  margin: 0 40rpx;
}
.desc{
  width: 290rpx;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
}
.desc .username{
  font-size: 25rpx;
}

.txt-group{
  font-size: 20rpx;
  margin: 5rpx 0;
}
.txt-group .zh{
  color: #8c8c8c;
}

.txt-group .en{
  color: #cccccc;
}

.area{
  color: #00c8b6;
  font-weight: bold;
}

.delete{
  width: 100rpx;
  text-align: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

js

import settings from '../../../static/js/settings.js'

Page({
    data: {
        backFront:true,
        record:[]
    },
    switchCamera(e) {
        var old = this.data.backFront
        this.setData({
            backFront: !old
        })
    },
    takePhoto(e){
        wx.showLoading({
            title: '检测中',
            mask:true
        })

        const ctx = wx.createCameraContext()
        ctx.takePhoto({
            quality: 'high',
            success: (res) => {
                wx.uploadFile({
                    url: settings.face,
                    filePath: res.tempImagePath,
                    name: 'avatar',
                    success:(response)=>{
                        let resdata = JSON.parse(response.data)
                        console.log(resdata)
                        if(resdata.code==100 || resdata.code==102){
                            console.log(resdata)
                            resdata.avatar = res.tempImagePath
                            var oldRecord = this.data.record
                            oldRecord.unshift(resdata)
                            console.log(oldRecord)
                            this.setData({
                                record:oldRecord
                            })
                        }else{
                            wx.showToast({
                                title: '请正常拍照'
                            })
                        }
                    },
                    complete:function(){
                        wx.hideLoading()
                    }
                })
            }
        })
    },
})

json

{
  "usingComponents": {},
  "navigationBarTitleText": "人脸检测"
}

后端

# 人脸检测接口
class FaceView(GenericViewSet):
    def create(self, request, *args, **kwargs):
        avatar_object = request.data.get('avatar')
        if not avatar_object:
            return Response({"msg": "未提交图像", "code": 101})
        from libs.baidu_ai import BaiDuAI
        ai = BaiDuAI()
        result = ai.search(avatar_object)
        if result.get('error_code') == 0:  # 查询到
            # {'error_code': 0, 'error_msg': 'SUCCESS', 'log_id': 2159604393, 'timestamp': 1713864959, 'cached': 0, 'result': {'face_token': '095994eca64424cee347b59e0a7edc0e', 'user_list': [{'group_id': '100', 'user_id': 'li3si1xian4', 'user_info': '', 'score': 98.035797119141}]}}
            user = result.get('result').get('user_list')[0]
            user_info = user.get('user_info')
            user_id = user.get('user_id')
            score = user.get('score')
            return Response({"code": 100, 'msg': '匹配成功', 'user_info': user_info, 'user_id': user_id, 'score': score,'avatar':''})
        else:
            return Response({"code": 102, 'msg': '匹配失败,该人员可能不是我社区人员,注意防范'})

baidu_api

...

image-20240529155244440

语音识别

前端

wxml

<textarea class="text" placeholder="等待语音识别自动录入..." placeholder-class="hoder" model:value="{{content}}" maxlength="{{-1}}"></textarea>

<button class="btn" hover-class="press" bind:longpress="recordStart" bind:touchcancel="recordCancel" bind:touchend="recordStop"> <label class="fa fa-microphone"></label> 按住说话</button>

wxss

page{
  background-color: #f5f5f5;
}
.text{
  height: 400rpx;
  background-color: white;
  width: 100%;
  padding: 20rpx;
}

.btn{
  margin-top: 30rpx;
  /* color: #fff; */
  border: 2rpx solid #ddd;
  background-color: white; 
  font-size: 32rpx;
  font-weight: normal;
}

.press label{
  color: #179B16;
}
.press{
  background-color: #ddd;
}
.hoder{
  font-size: 28rpx;
}

#####json####
{
  "usingComponents": {},
  "navigationBarTitleText": "语音识别"
}

js

const recorderManager = wx.getRecorderManager()
import settings from '../../../static/js/settings.js'

Page({
  /**
   * 页面的初始数据
   */
  data: {
    content:"",
    record:false
  },
  recordStart:function(){
    this.setData({record:true})
    const options = {
      // duration: 6000,//指定录音的时长,单位 ms
      sampleRate: 16000,//采样率
      numberOfChannels: 1,//录音通道数
      encodeBitRate: 48000,//编码码率
      format: 'wav'//音频格式,有效值 
    }
    //开始录音
    recorderManager.start(options)
  },
  recordCancel:function(){
    console.log("停止");
    this.setData({record:false})
    wx.hideLoading()
  },
  recordStop:function(){
    if(!this.data.record){return}
    recorderManager.stop();
    recorderManager.onStop((res) => {
      // this.tempFilePath = res.tempFilePath
      wx.showLoading()
      wx.uploadFile({
        filePath: res.tempFilePath,
        name: 'voice',
        url: settings.voice,
        success:(response)=>{
          console.log(response)
          // {'code': 100, 'msg':'成功','result': ['欢迎欢迎']}
          let voiceResponse = JSON.parse(response.data)
          if(voiceResponse.code == 100){
            console.log(voiceResponse)
              this.setData({
                content:this.data.content + voiceResponse.result[0]
              })
          }else{
            wx.showToast({
              title: '识别失败,请重新操作!',
              icon: "none"
            })
          }
        },
        complete:()=>{
          wx.hideLoading()
        }
      },
      )
    })
    this.setData({record:false})
  },
})

后端

baidu_ai

# pip install baidu-aip
from aip import AipSpeech
import base64

# 语音识别
class BaiDuVoice:
    def __init__(
        self,
        APP_ID="",
        API_KEY="",
        SECRET_KEY="",
    ):
        """你的 APPID AK SK"""
        self.APP_ID = APP_ID
        self.API_KEY = API_KEY
        self.SECRET_KEY = SECRET_KEY
        self.client = AipSpeech(self.APP_ID, self.API_KEY, self.SECRET_KEY)

    def speed(self, voice_object):
        res = self.client.asr(
            voice_object.read(),
            "pcm",
            16000,
            {
                "dev_pid": 1537,
            },
        )
        return res

views

from libs.baidu_ai import BaiDuVoice

class VoiceView(GenericViewSet):
    def create(self, request, *args, **kwargs):
        voice_object = request.data.get('voice')
        # with open('./a.wav','wb') as f:
        #     f.write(voice_object.read())
        ai = BaiDuVoice()
        result = ai.speed(voice_object)
        # {'corpus_no': '6847771638436561158', 'result': ['你是不是打过来?'], 'sn': '15921476781594371078', 'err_msg': 'success.', 'err_no': 0}
        if result.get('err_no') == 0:
            return Response({'code': 100, 'msg': '识别成功', 'result': result.get('result')})
        else:
            return Response({'code': 101, 'msg': '识别失败'})

image-20240530210822410

活动页面

前端

wxml

<view class="container">
  <!-- 使用wx:for循环遍历活动报名列表 -->
  <view wx:for="{{activityList}}" wx:key="index" class="activity-item">
    <!-- 活动内容 -->
    <view class="activity-content">
      <view class="activity-title">{{item.title}}</view>
      <view class="activity-enrollment">报名人数:{{item.count}}  |  总人数:{{item.total_count}}</view>
      <view class="activity-time">获得积分:{{item.score}}</view>
      <view class="activity-time">{{item.date}}</view>
      <view class="activity-description">{{item.text}}</view> 
    </view>
    <!-- 报名按钮 -->
    <button class="signup-btn" bindtap="handleSignup">报名</button>
  </view>
</view>

wxss

.container {
  padding: 20rpx;
}

.activity-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 20rpx;
  border-bottom: 1px solid #ebebeb;
  padding-bottom: 20rpx;
}

.activity-content {
  flex: 1;
}

.activity-title {
  font-size: 28rpx;
  font-weight: bold;
  margin-bottom: 10rpx;
}

.activity-time {
  font-size: 24rpx;
  color: #666666;
  margin-bottom: 10rpx;
}

.activity-enrollment {
  font-size: 24rpx;
  color: #999999;
  margin-bottom: 10rpx;
}

.activity-description {
  font-size: 24rpx;
  color: #333333;
  margin-top: 10rpx;
  white-space: pre-wrap; /* 自动换行 */
}

.signup-btn {
  background-color: #50c8ff;
  color: #ffffff;
  border: none;
  border-radius: 4rpx;
  padding: 10rpx 20rpx;
  font-size: 24rpx;
}

js

var app = getApp();
import settings from '../../static/js/settings.js'
Page({
  data: {
    activityList: [
    ]
  },
  onLoad: function () {
    // 页面加载时执行的逻辑
    this.refresh()
  },
  refresh(){
    wx.showLoading({
      mask: true
    })
    wx.request({
      url: settings.activity,
      method: "GET",
      success: (res) => {
        this.setData({
          activityList: res.data
        })
      },
      complete() {
        wx.hideLoading()
      }
    })
  },
  handleSignup: function (event) {
    // 处理报名按钮点击事件
    var index = event.currentTarget.dataset.index; // 获取当前点击的活动索引
    console.log('点击了报名按钮,索引为:', index);
  }
})

后端

views

class ActivityView(GenericViewSet,ListModelMixin):
    queryset =Activity.objects.all().order_by('date')
    serializer_class = ActivitySerializer

serializer

class ActivitySerializer(serializers.ModelSerializer):
    class Meta:
        model = Activity
        fields = ['id', 'title','text','date','count','score','total_count']
        extra_kwargs={
            'date':{'format':"%Y-%m-%d"}
        }

models

class UserInfo(models.Model):
    name = models.CharField(verbose_name="姓名", max_length=32)
    avatar = models.FileField(verbose_name="头像", max_length=128, upload_to='avatar')
    create_date = models.DateField(verbose_name="日期", auto_now_add=True)
    score = models.IntegerField(verbose_name="积分", default=0)

    class Meta:
        verbose_name_plural = '用户表'
    def __str__(self):
        return self.name

#  活动表
class Activity(models.Model):
    title = models.CharField(verbose_name="活动标题", max_length=128)
    text = models.TextField(verbose_name="活动描述", null=True, blank=True)
    date = models.DateField(verbose_name="举办活动日期")

    count = models.IntegerField(verbose_name='报名人数', default=0)
    total_count = models.IntegerField(verbose_name='总人数', default=0)
    score = models.IntegerField(verbose_name="积分", default=0)

    join_record = models.ManyToManyField(verbose_name="参与者",
                                         through="JoinRecord",
                                         through_fields=("activity", "user"),
                                         to="UserInfo")

    class Meta:
        verbose_name_plural = '活动表'

    def __str__(self):
        return self.title
#  活动报名记录
class JoinRecord(models.Model):
    user = models.ForeignKey(verbose_name='用户', to="UserInfo", on_delete=models.CASCADE)
    activity = models.ForeignKey(verbose_name="活动", to="Activity", on_delete=models.CASCADE, related_name='ac')

    exchange = models.BooleanField(verbose_name="是否已兑换", default=False)

    class Meta:
        verbose_name_plural = '活动报名记录'

image-20240530210808124

公告页面

前端

wxml

<view class="container">
  <!-- 使用wx:for循环遍历社区公告列表 -->
  <view wx:for="{{noticeList}}" wx:key="index" class="notice-item">
    <!-- 左侧图片 -->
    <image class="notice-image" src="{{item.img}}" mode="aspectFill"></image>
    <!-- 右侧内容 -->
    <view class="notice-content">
      <view class="notice-title">{{item.title}}</view>
      <view class="notice-time">{{item.create_time}}</view>
      <view class="notice-details">{{item.content}}</view>
    </view>
  </view>
</view>

wxss

.container {
  padding: 20rpx;
}

.notice-item {
  display: flex;
  align-items: flex-start;
  margin-bottom: 20rpx; /* 添加间距 */
  border-bottom: 1px solid #f0f0f0; /* 添加底部边框 */
  padding-bottom: 20rpx; /* 增加底部内边距 */
}

.notice-image {
  width: 150rpx;
  height: 120rpx;
  border-radius: 6rpx;
  margin-right: 20rpx;
}

.notice-content {
  flex: 1;
}

.notice-title {
  font-size: 28rpx;
  font-weight: bold;
  margin-bottom: 10rpx;
}

.notice-time {
  font-size: 24rpx;
  color: #666666;
  margin-bottom: 10rpx;
}

.notice-details {
  font-size: 24rpx;
  color: #333333;
}

js

import settings from '../../static/js/settings.js'
Page({
  data: {
    noticeList: [
      {
        title: '公告标题1',
        create_time: '2024-04-25',
        content: '公告内容描述1,公告内容描述1,公告内容描述1。', // 可以根据实际情况添加更多内容
        igm: '/images/notice/notice1.jpg' // 图片路径,根据实际情况修改
      },
      {
        title: '公告标题2',
        create_time: '2024-04-26',
        content: '公告内容描述2,公告内容描述2,公告内容描述2。', // 可以根据实际情况添加更多内容
        igm: '/images/notice/notice2.jpg' // 图片路径,根据实际情况修改
      },
      // 可以添加更多社区公告数据
    ]
  },
  onLoad: function () {
    // 页面加载时执行的逻辑
    this.refresh()
  },
  refresh(){
    wx.showLoading({
      mask: true
    })
    wx.request({
      url: settings.notice,
      method: "GET",
      success: (res) => {
        this.setData({
          noticeList: res.data
        })
      },
      complete() {
        wx.hideLoading()
      }
    })

  }
})

后端

views

class NoticeView(GenericViewSet,ListModelMixin):
    queryset =Notice.objects.all().order_by('create_time')
    serializer_class = NoticeSerializer

serializer

class NoticeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Notice
        fields = ['id', 'title','img','create_time','content']
        extra_kwargs={
            'create_time':{'format':"%Y-%m-%d"}
        }

image-20240530210758857

登陆功能

前端

个人中心页面

<block wx:if="{{userInfo==null}}">
<view class="container1">
  <view class="main">
    <view class="icon-view">
      <!-- 应用图标 -->
      <image src="/static/img/icon/icon.png" class="app-icon"></image>
      <text class="title">智慧社区</text>
    </view>
  </view>
  <van-cell-group>
    <van-cell>
      <button type="warn" open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">手机号快捷登录</button>
    </van-cell>
  </van-cell-group>

  <!-- 其他手机号登录 -->
  <van-cell-group>
    <van-cell>
      <button type="primary" plain bindtap="handleOtherLogin">其他手机号登录</button>
    </van-cell>
  </van-cell-group>

  <!-- 用户协议同意 -->
  <view class="agreement-container">
    <checkbox class="checkbox" value="{{agreed}}" bindchange="handleAgreeChange"></checkbox>
    <text class="agreement-text">我已阅读并同意</text>
    <navigator url="" class="agreement-link">《用户协议》</navigator>
  </view>
</view>
</block>


<block wx:else>

  <view class="container">
  <view class="top-view">
    <view class="user">
      <view class="row">
        <image class="avatar" src="{{userInfo.avatar}}"></image>
        <view class="name">
          <view bindtap="logout">{{userInfo.name}}</view>
        </view>
      </view>

    </view>
    <view class="numbers">
      <view class="row">
        <text>{{userInfo.score}}</text>
        <text>积分</text>
      </view>
      <view class="row">
        <text>55</text>
        <text>其他</text>
      </view>
      <view class="row">
        <text>77</text>
        <text>其他</text>
      </view>
      <view class="row">
        <text>56</text>
        <text>其他</text>
      </view>
    </view>
  </view>
  <van-list>
    <van-cell title="积分兑换记录" is-link />
    <van-cell title="我参加的活动" is-link />
    <van-cell title="分享应用" is-link />
    <van-cell title="联系客服" is-link />
    <van-cell title="退出登录" is-link bind:tap="handleLogout"/>
  </van-list>
</view>


</block>

wxss

page{
  height: 100%;
}

.login-area{
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.login-area .btn{
  width: 200rpx;
  height: 200rpx;
  border-radius: 500%;
  background-color: #5cb85c;
  color: white;
  
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
}

.user-area{
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.user-area image{
  width: 200rpx;
  height: 200rpx;
  border-radius: 500%;
}
.user-area .name{
  font-size: 30rpx;
  padding: 30rpx 0;
}

.user-area .logout{
  color: #a94442;
}

.top-view{
  background-color: #01ccb6;

  color: white;
  padding: 40rpx;
}

.top-view .user{
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
}
.top-view .user .row{
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
}
.top-view .user .avatar{
  width: 100rpx;
  height: 100rpx;
  border-radius: 50%;
}

.top-view .user .name{
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  padding-left: 20rpx;
}
.top-view .user .name navigator{
  padding: 0 5rpx;
}

.top-view .site{
  background-color: rgba(0, 0, 0, 0.16);
  padding: 20rpx;
  border-top-left-radius: 32rpx;
  border-bottom-left-radius: 32rpx;
}

.top-view .numbers{
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  font-size: 28rpx;
  padding: 40rpx;
  padding-bottom: 0rpx;
}

.top-view .numbers .row{
   display: flex;
  flex-direction: column;
  align-items: center;
}


/* login.wxss */
.container1 {
  padding: 20rpx;
}
.main{
  display: flex;
  justify-content: center;
  align-items: center;
}
.icon-view{
  display: flex;
  flex-direction: column;
  margin-bottom: 50rpx;
}

.app-icon {
  width: 100rpx;
  height: 100rpx;
  margin: 40rpx auto 20rpx; /* 上边距为40rpx,下边距为20rpx,左右居中 */
}

.quick-login-header {
  display: flex;
  align-items: center;
}

.icon {
  width: 40rpx;
  height: 40rpx;
  margin-right: 20rpx;
}

.title {
  font-size: 28rpx;
  font-weight: bold;
  color: #333333;
}

.divider {
  height: 20rpx;
}

.login-option {
  font-size: 28rpx;
  color: #333333;
}

.login-option .van-cell__icon {
  color: #07c160;
}

.agreement-container {
  display: flex;
  align-items: center;
  margin-top: 20rpx;
}

.checkbox {
  margin-right: 10rpx;
}

.agreement-text {
  font-size: 24rpx;
  color: #666666;
}

.agreement-link {
  font-size: 24rpx;
  color: #07c160;
}

js

var app = getApp(); // 拿到的是 app.js中data的数据
import settings from '../../static/js/settings.js'
Page({
  data: {
    userInfo: null,
  },
  getPhoneNumber(event) {
    console.log(event)
    // 通过获取手机号返回的code--传递给后端--后端调用:POST https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=ACCESS_TOKEN -->获取手机号--》后端签发token给前端
    wx.request({
      url: settings.quick_login,
      method: 'POST',
      data: {
        code: event.detail.code
      },
      success: (res) => {
        console.log(res)
        //在此返回登录信息,用户登录
        var data = res.data;
        console.log(data)
        if (data.code == 100) {
          console.log('---', data)
          var token = data.token
          var name = data.name
          var score = data.score
          var avatar = data.avatar
          app.initUserInfo(name, score, avatar, token)
          var info = app.globalData.userInfo
          console.log('globalData.userInfo', info)
          if (info) {
            this.setData({
              userInfo: info
            })
          }
        } else {
          wx.showToast({
            title: '登录失败',
          })
        }
      }

    })
  },
  handleOtherLogin(e) {
    wx.navigateTo({
      url: '/pages/second/otherlogin/otherlogin'
    })
  },
  onShow() {
    var info = app.globalData.userInfo
    console.log('globalData.userInfo', info)
    if (info) {
      this.setData({
        userInfo: info
      })
    }

  },
  handleLogout() {
    app.logoutUserInfo()
    this.setData({
      userInfo: null
    })
  }
})

app.js

// app.js
App({
    globalData: {
        userInfo: null
    },
    initUserInfo: function (name, score, avatar, token) {
        var info = {
            name: name,
            score: score,
            avatar: avatar,
            token: token
        };
        this.globalData.userInfo = info
        wx.setStorageSync('userInfo', info);
    },
    logoutUserInfo: function () {
        wx.removeStorageSync('userInfo');
        this.globalData.userInfo = null;
    },
    onLaunch() {
        var info = wx.getStorageSync('userInfo')
        console.log(info)
        this.globalData.userInfo = info
    }
})

登录页面

wxml

<view class="container">
  <view class="main">
    <view class="icon-view">
      <!-- 应用图标 -->
      <image src="/static/img/icon/icon.png" class="app-icon"></image>
      <text class="title">智慧社区</text>
    </view>
  </view>
  <van-field value="{{ phone }}" bind:input="onPhoneInput" label="手机号" type="tel" placeholder="请输入手机号" clearable="{{ true }}" />
  <van-field value="{{code}}" bind:input="onCodeInput" center clearable label="验证码" placeholder="请输入验证码" use-button-slot>
    <van-button slot="button" size="small" type="primary" bind:tap="sendCode" disabled='{{sendCodeDisabled}}'>
      {{buttonText}}
    </van-button>
  </van-field>
  <van-button type="info" block="{{ true }}" bind:tap="login">登录</van-button>
</view>

wxss

.container {
  padding: 20rpx;

}
.main{
  display: flex;
  justify-content: center;
  align-items: center;
}
.icon-view{
  display: flex;
  flex-direction: column;
  margin-bottom: 50rpx;
}
.title {
  font-size: 28rpx;
  font-weight: bold;
  color: #333333;
}
.app-icon {
  width: 100rpx;
  height: 100rpx;
  margin: 40rpx auto 20rpx; /* 上边距为40rpx,下边距为20rpx,左右居中 */
}

js

import settings from '../../../static/js/settings.js'
var app = getApp()
Page({
  data: {
    phone: '',
    code: '',
    agreed: false,
    sendCodeDisabled: false,
    buttonText: '发送验证码',
    loading: false,
    timer: null,
    countDown: 60
  },

// 监听手机号输入
onPhoneInput(event) {
  this.setData({
    phone: event.detail
  });
},

// 监听验证码输入
onCodeInput(event) {
  this.setData({
    code: event.detail
  });
},

  // 发送验证码
  sendCode() {
    // 在这里编写发送验证码的逻辑,此处仅做示例
    console.log('发送验证码',this.data.phone,this.data.code);

    if(this.data.phone){
      wx.request({
        url: settings.send_sms+'?mobile='+this.data.phone,
        method:'GET',
        success:(res)=>{
         wx.showToast({
           title: res.data.msg,
         })
        }
      })
      this.setData({
        sendCodeDisabled: true,
        timer: setInterval(this.countDown, 1000)
      });
    }else{
      wx.showToast({
        title: '请输入手机号',
      })
    }

  },

  // 登录
  login() {
    // 在这里编写登录逻辑,此处仅做示例
    console.log('登录');
    if(this.data.phone&&this.data.code){
      wx.request({
        url: settings.login,
        method:'POST',
        data:{mobile:this.data.phone,code:this.data.code},
        success:(res)=>{
          var data = res.data;
          console.log(data)
          if (data.code == 100) {
            console.log('---', data)
            var token = data.token
            var name = data.name
            var score = data.score
            var avatar = data.avatar
            app.initUserInfo(name, score, avatar, token)
            var info = app.globalData.userInfo
            console.log('globalData.userInfo', info)
            wx.navigateBack()
          } else {
            wx.showToast({
              title: '登录失败',
            })
          }
        }
      })
      this.setData({
        sendCodeDisabled: true,
        timer: setInterval(this.countDown, 1000)
      });
    }else{
      wx.showToast({
        title: '请输入手机号和验证码',
      })
    }

  },

  // 倒计时
  countDown() {
    let countDown = this.data.countDown;
    if (countDown === 0) {
      clearInterval(this.data.timer);
      this.setData({
        buttonText: '发送验证码',
        sendCodeDisabled: false,
        countDown: 60
      });
      return;
    }
    this.setData({
      buttonText: countDown + 's',
      countDown: countDown - 1
    });
  },

  onUnload() {
    clearInterval(this.data.timer);
  }
});

后端

views

from rest_framework.decorators import action
from django.core.cache import cache
from faker import Faker
from libs.send_tx_sms import send_sms_by_phone, get_code
from rest_framework_simplejwt.tokens import RefreshToken

class LoginView(GenericViewSet):
    @action(methods=['GET'], detail=False)
    def send_sms(self, request, *args, **kwargs):
        # 1 取出前端传入手机号
        mobile = request.query_params.get('mobile')
        # 2 获取随机验证码
        code = get_code()
        # 3 验证码放到缓存
        cache.set(f'sms_{mobile}', code)
        # 4 发送短信
        res = send_sms_by_phone(mobile, code)
        if res:
            return Response({'code': 100, 'msg': '短信发送成功'})
        else:
            return Response({'code': 101, 'msg': '短信发送失败,请稍后再试'})

    @action(methods=['POST'], detail=False)
    def login(self, request, *args, **kwargs):
        # 1 取出手机号和验证码
        mobile = request.data.get('mobile')
        code = request.data.get('code')
        # 2 校验验证码是否正确
        old_code = cache.get(f'sms_{mobile}')
        if old_code == code:
            # 3 数据库查询用户,如果存在直接签发token登录成功

            user = UserInfo.objects.filter(mobile=mobile).first()
            if not user:
                # 4 如果用户不存在,创建用户,再签发token
                fake = Faker('zh_CN')
                username = fake.name()
                user = UserInfo.objects.create(mobile=mobile, name=username)
            refresh = RefreshToken.for_user(user)
            return Response(
                {'code': 100, 'msg': '登录成功', 'token': str(refresh.access_token), 'name': user.name,
                 'score': user.score, 'avatar': 'http://127.0.0.1:8000/media/' + str(user.avatar)})
        else:
            return Response({'code': 101, 'msg': '验证码错误'})

    @action(methods=['POST'], detail=False)
    def quick_login(self, request, *args, **kwargs):
        # 1 取出前端传入的code
        code = request.data.get('code')
        # 2 通过code,调用微信开发平台接口,换取手机号
        # 3 拿到手机号再自己库中查,能查到,签发token
        # 4 查不到注册再签发token
        # 假数据---》都签发成第一个用户
        user = UserInfo.objects.filter(pk=1).first()
        refresh = RefreshToken.for_user(user)
        return Response(
            {'code': 100, 'msg': '登录成功', 'token': str(refresh.access_token), 'name': user.name, 'score': user.score,
             'avatar': 'http://127.0.0.1:8000/media/' + str(user.avatar)})

image-20240530215426185

image-20240530215436628

image-20240530215503582

活动报名功能

前端

activity.js

var app = getApp();
import settings from '../../static/js/settings.js'
Page({
    data: {
        activityList: [

        ]
    },
    onLoad: function () {
        // 页面加载时执行的逻辑
        this.refresh()
    },
    refresh(){
        wx.showLoading({
            mask: true
        })
        wx.request({
            url: settings.activity,
            method: "GET",
            success: (res) => {
                this.setData({
                    activityList: res.data
                })
            },
            complete() {
                wx.hideLoading()
            }
        })

    },
    handleSignup: function (event) {
        var index = event.currentTarget.dataset.mark; // 获取当前点击的活动索引
        // 1 校验用户是否登录
        var info = app.globalData.userInfo
        console.log(info)
        if (info) {
            //2 处理报名按钮点击事件
            console.log('点击了报名按钮,索引为:', index);
            wx.request({
                url: settings.join,
                method:'POST',
                data:{'id':index},
                header:{token:info.token},
                success:(res)=>{
                    console.log(res.data)
                    wx.showToast({
                        title: res.data.msg,
                    })
                }
            })
        } else {
            wx.showToast({
                title: '请先登录',
            })
        }
    }
})

后端

views

class ActivityJoinView(GenericViewSet):
    authentication_classes = [MyJSONWebTokenAuthentication]
    @action(methods=['POST'], detail=False)
    def join(self, request, *args, **kwargs):
        # 1 取出要参加的活动id
        activity_id = request.data.get('id')
        # 2 取出当前登录用户
        user = request.user
        # 2 查到当前活动
        activity = Activity.objects.filter(pk=activity_id).first()
        # 3 判断时间,判断人数
        # 4 判断是否报名过
        join_record=JoinRecord.objects.filter(activity_id=activity_id,user=user).first()
        if join_record:
            return Response({'code': 101, 'msg': "已经报名过,不用重复报名"})
        else:
            # 5 包名人数+1,报名报存入
            activity.count = activity.count + 1
            activity.save()
            JoinRecord.objects.create(activity=activity,user=user)
            # 6 返回报名成功
            return Response({'code': 100, 'msg': "报名成功"})

Authentication1.py

from .models import UserInfo
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_simplejwt.authentication import JWTAuthentication

class MyJSONWebTokenAuthentication(JWTAuthentication):
    def authenticate(self, request):
        jwt_value = request.META.get("HTTP_TOKEN")
        if not jwt_value:
            raise AuthenticationFailed('token 字段是必须的')
        validated_token = self.get_validated_token(jwt_value)
        print(validated_token['user_id'])
        user = UserInfo.objects.filter(pk=validated_token['user_id']).first()
        return user, jwt_value

image-20240530215408361

商城兑换页面

前端

wxml

<van-dropdown-menu active-color="#1989fa">
  <van-dropdown-item value="{{ value1 }}" options="{{ option1 }}" />
  <van-dropdown-item value="{{ value2 }}" options="{{ option2 }}" />
</van-dropdown-menu>
    <van-grid column-num="3" border="{{ true }}">
      <van-grid-item use-slot wx:for="{{ 8 }}" wx:for-item="index" border>
        <image style="width: 100%; height: 90px;" src="https://img.yzcdn.cn/vant/apple-{{ index + 1 }}.jpg" />
        <view class="desc">
          <view class="title">{{item.title}}</view>
          <view class="exchange">
            <view>{{item.price}}积分</view>
            <van-button color="linear-gradient(to right, #4bb0ff, #6149f6)" bindtap="doExchange" data-gid="{{item.id}}" size="mini">兑换</van-button>
          </view>
        </view>
      </van-grid-item>
    </van-grid>

js

import settings from '../../../static/js/settings.js'
var app = getApp()
Page({
  data: {
    option1: [
      { text: '全部商品', value: 0 },
      { text: '最新上架', value: 1 },
      { text: '活动商品', value: 2 },
    ],
    option2: [
      { text: '默认排序', value: 'a' },
      { text: '好评排序', value: 'b' },
      { text: '销量排序', value: 'c' },
    ],
    value1: 0,
    value2: 'a',
  },
})

image-20240530215354510

小程序上线https

# 开发者工具上传
# 小程序后台--》提交审核--》审核通过,全网可用
	-备案
# 配置好合法域名

# 1 小程序后台,配置合法地址
https://mp.weixin.qq.com/wxamp/devprofile/get_profile?token=948853190&lang=zh_CN
# 2 在小程序源码中,访问后台地址,改成
const rootUrl = 'https://www.liuqingzheng.top/smart'

# 3 小程序上线
	-在开发者工具选上传,填入版本号
    
# 4 小程序后台
	-把刚刚上传的小程序设为体验版
    -只有允许体验的人才能用(测试人员)
    
    
# 5 提交审核--》一个周审核
	-需要备案

后端上线

#1  后端要部署在https地址上
# 2 http和https区别
	https=http+ssl/tls 证书
    
# 3 部署在https上,需要申请证书
	-合法机构申请
    -如果证书过期了,不合法--》浏览器会提示安全
# 4 证书有收费,也有免费
	-阿里云
    -七牛云
    -https://zhuanlan.zhihu.com/p/561907474
    

nginx

events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    client_max_body_size 20M;
	server {
    	listen 443 ssl;
     	ssl_certificate /usr/local/nginx/cert/liuqingzheng.top.pem;
     	ssl_certificate_key /usr/local/nginx/cert/liuqingzheng.top.key;
        server_name  liuqingzheng.top;
        location / {
            include uwsgi_params;
            uwsgi_pass 127.0.0.1:8080;
            uwsgi_param UWSGI_SCRIPT smart_backend.wsgi;
            uwsgi_param UWSGI_CHDIR /root/smart_backend/;
            }

       location /static {
            alias /home/static;
       }
    }
}

# https://www.liuqingzheng.top/api/v1/banner/

安装python3.9

#1 linux 服务器它有些系统服务,使用python2写的,linux操作系统要运行,需要有python2的解释器环境
	-自带
    
# 2 阿里云云服务装了linux--》自带python3.6

# 3 centos7.9
	-python    python2.7.5  -——》 pip
    -python3   python3.6.8  --->pip3
# 4 在这个基础上装一个项目需要的3.9
	-到时候,机器上有3个python解释器环境,每个解释器环境又一一个pip--》千万别乱了
    -pip 安装--》模块装在哪个解释器中了
    
    
# 5 如果只想装个python解释器  yum install python---》这样装指定不了版本--》咱们不用这种方式


# 6 咱们选择源码编译安装--》下载python解释器源码--》自己编译
#1  源码安装python,依赖一些第三方zlib* libffi-devel,如果不装,编译会报错
yum install openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel psmisc libffi-devel zlib* libffi-devel  -y

# 2 前往用户根目录
cd ~

#3 下载  3.9.10 源码 服务器终端
# https://registry.npmmirror.com/binary.html?path=python/
wget https://registry.npmmirror.com/-/binary/python/3.9.10/Python-3.9.10.tgz
# wget https://www.python.org/ftp/python/3.9.16/Python-3.9.16.tgz

#4  解压安装包
tar -xf Python-3.9.10.tgz 

#5 进入目标文件
cd Python-3.9.10

#6  配置安装路径:/usr/local/python3
# 把python3.9.10 编译安装到/usr/local/python38路径下
./configure --prefix=/usr/local/python39

#6  编译并安装,如果报错,说明缺依赖
yum install openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel psmisc libffi-devel zlib* libffi-devel  -y
# make只是编译----》可执行文件,没有安装
# 类似于在win上下载了安装包,但是没安装
# make install 安装---》类似于在win上下了安装包,一路下一步安装了,指定安装位置---》/usr/local/python39
make &&  make install

#7  建立软连接:/usr/local/python38路径不在环境变量,终端命令 python3,pip3
ln -s /usr/local/python39/bin/python3 /usr/bin/python3.9
ln -s /usr/local/python39/bin/pip3 /usr/bin/pip3.9

# 机器上有多个python和pip命令,对应关系如下
python       2.x      pip 
python3      3.6      pip3
python3.9    3.9      pip3.9

#8  删除安装包与文件:
rm -rf Python-3.9.10
rm -rf Python-3.9.10.tar.xz

安装nginx

# 软件:反向代理服务器  (搜一下:什么是正向代理,什么是反向代理)  反向带代理服务器
  - 做请求转发    (前端来了个请求---》打在了80端口上---》转到本地8888端口,或者其他机器的某个端口)
  - 静态资源代理    前端项目直接放在服务器上某个位置----》请求来了,使用nginx拿到访问的内容,直接返回
  - 负载均衡       假设来了1000个请求--》打在nginx上,nginx性能很高,能顶住---》只转发到某个django项目,可能顶不住---》集群化的不是3台django---》均匀的打在3台机器上
    

    
    
#1 前往用户根目录
cd ~

#2 下载nginx 1.24.0
 wget http://nginx.org/download/nginx-1.24.0.tar.gz

#3 解压安装包
tar -xf nginx-1.24.0.tar.gz

#4 进入目标文件
cd nginx-1.24.0

#5 配置安装路径:/usr/local/nginx
./configure --prefix=/usr/local/nginx --with-http_ssl_module

#6 编译并安装
 make &&  make install

#7 建立软连接:终端命令 nginx
ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx 

#8 删除安装包与文件:
cd ~
rm -rf nginx-1.13.7
rm -rf nginx-1.13.7.tar.xz

#9 测试Nginx环境,服务器运行nginx,本地访问服务器ip
nginx   # 启动nginx服务,监听80端口----》公网ip 80 端口就能看到页面了
服务器绑定的域名 或 ip:80

# 静态文件放的路径
/usr/local/nginx/html

# 查看进程
ps aux | grep nginx


# 关闭和启动
关闭:nginx -s stop 
启动:nginx

安装mysql5.7

# mysql 5.7 
#1 前往用户根目录

# pwd  查看我在哪个目录下
# cd 切换到某个路径下
 cd ~  # 回到家路径

#2 下载mysql57 
wget http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm
# ls  查看当前目录下的文件和文件夹

#3 安装mysql57
 yum -y install mysql57-community-release-el7-10.noarch.rpm
 yum install mysql-community-server --nogpgcheck -y

#4 启动mysql57并查看启动状态
systemctl start mysqld  # 启动mysql服务
systemctl status mysqld  # 查看mysql状态

#5 查看默认密码并登录(第一次安装,root密码是随机的)
# 去/var/log/mysqld.log 中过滤出包含 password 的多行
grep "password" /var/log/mysqld.log   # Gxn*u8rqM=ol
mysql -uroot -p


#6 修改root密码
 ALTER USER 'root'@'localhost' IDENTIFIED BY 'Lqz12345?';
 grant all privileges on *.* to 'root'@'%' identified by 'Lqz12345?';
 如果还连不上,就是mysql 3306的安全组没开---》防火墙端口没开
  
  
# 7 远程连接:win---》navicate--》
	连接成功
    如果链接不成功,需要在阿里云开启安全组
   
# 8 安装mysqlclient
yum install python3-devel -y
yum install mysql-devel --nogpgcheck -y
pip3.9 install mysqlclient

# 9
pip3.9 install urllib3==1.26.15
pip3.9 install chardet

#10 
mkdir static
STATIC_ROOT = '/root/smart_backend/static/'

编写uwsgi配置文件

# 项目运行,不是使用 python manage.py runserver 运行,这样运行性能很低,没有并发,开发测试阶段用

# 上线项目,需要使用一个性能很高的web服务,c语言写的,uwsgi,并发量高,线上环境用他

# 在项目中写uwsgi的配置文件--》用uwsgi软件,根据这个配置文件运行django项目


#1 smart.xml
<uwsgi>
	<socket>127.0.0.1:8080</socket>
    <chdir>/home/smart_backend/</chdir>
    <module>smart_backend.wsgi</module>
    <processes>4</processes>
    <daemonize>uwsgi.log</daemonize>
</uwsgi>

上传后端项目到云服务器

# 1 配置文件修改
DEBUG = False
ALLOWED_HOSTS = ['*']
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'smart',
        'HOST':'127.0.0.1',
        'PORT':3306,
        'USER':'root',
        'PASSWORD':'Lqz12345?'
    }
}
BACKEND_URL='https://www.liuqingzheng.top'
# BACKEND_URL='https://106.14.134.13'
STATIC_URL='/static/'


# 2 导出项目依赖,项目根路径下有个 requirements.txt--->放了当前项目所有依赖
pip3 install pipreqs
pipreqs ./ --encoding=utf-8
# requirements.txt
baidu_aip==4.16.13
Django==3.2.22
djangorestframework==3.14.0
djangorestframework_simplejwt==5.3.1
Faker==25.0.1
pypinyin==0.51.0
tencentcloud_sdk_python==3.0.1115
urllib3==1.26.15 
django-simpleui
pillow
chardet


# 3 项目压缩 zip 

# 4 服务器上来到 home 目录
cd /home/
yum install lrzsz unzip -y
rz 上传

unzip smart_backend.zip


# 5 在服务端,安装项目依赖
cd smart_backend
pip3.9 install -r requirements.txt 

# 6 服务端链接mysql ,需要装连mysql模块
# pip3.9 install mysqlclient  # linux上装会报错
yum install python3-devel -y
yum install mysql-devel --nogpgcheck -y
pip3.9 install mysqlclient


# 7 装uwsig,运行django项目
pip3.9 install uwsgi
ln -s /usr/local/python39/bin/uwsgi /usr/bin/uwsgi

# 8 启动uwsgi
uwsgi -x smart.xml  # 等同于原来的 python manage.py runserver 127.0.0.1:8080

# 9 查看
ps aux |grep uwsgi

# 10 停止
pkill -9 uwsgi

nginx配置--后端接口--(http)

# 1 修改nginx 配置文件
cd /usr/local/nginx/conf
mv nginx.conf nginx.conf.bak
vi nginx.conf  # 敲 a  才能粘贴

events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
	server {
        listen 80;
        server_name  127.0.0.1;
        charset utf-8;
        location / {
            include uwsgi_params;
            uwsgi_pass 127.0.0.1:8080;
            uwsgi_param UWSGI_SCRIPT smart_backend.wsgi; 
            uwsgi_param UWSGI_CHDIR /root/smart_backend/;
            }
        }
}

# 按esc
# 敲 :
# 敲 wq
# 敲 回车

# 2 重启nginx即可
nginx -s reload # 之前运行着
# 之前没运行
nginx  # 运行起来了

创建数据库,迁移表,导入数据

# 1 在navicat中创建 smart 库

# 2 迁移表
	python3.9 manage.py makemigrations
    python3.9 manage.py migrate
# 3 根据报错,安装缺的模块
pip3.9 install django-simpleui
# 4 报错:python3.9 manage.py makemigrations
pip3.9 install urllib3==1.26.15 

# 5 根据报错 :chardet
pip3.9 install chardet

# 6 缺pillow
pip3.9 install pillow

# 7 去后台admin,录入数据

# 8 直接导入一个sql文件----之前项目测试的数据

# 9 关闭uwsgi,重启
pkill -9 uwsgi
uwsgi -x smart.xml

# 10 对于小程序来讲,接口能用了,但是admin访问,加载不了静态资源
	-uwsgi不能代理静态资源,需要使用nginx代理

配置admin访问管理后台

# 0 创建文件号
mkdir /home/smart_backend/static
# 1 在项目配置文件中,加入
STATIC_ROOT = '/home/smart_backend/static/'
# 2 执行采集命令--》把静态文件采集到static文件夹下
python3.9 manage.py collectstatic

# 3 配置nginx代理
cd /usr/local/nginx/conf 

events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
        server {
            listen 80;
            server_name  127.0.0.1;
            charset utf-8;
            location / {
                include uwsgi_params;
                uwsgi_pass 127.0.0.1:8080;
                uwsgi_param UWSGI_SCRIPT smart_backend.wsgi;
                uwsgi_param UWSGI_CHDIR /root/smart_backend/;
            }
            location /static {
                alias /home/smart_backend/static;
           }
        }
}

#4 重启nginx
nginx -s reload

# 5 访问admin即可
http://106.14.134.13/admin

证书

配置https访问

# 1 购买证书
	-https://zhuanlan.zhihu.com/p/561907474
# 2 申请了证书,会有俩文件
	key
    pem
    
# 3 传到服务器上
	cd /usr/local/nginx
    mkdir cert
    rz  # 把证书放在这个位置
    unzip 解压
    # 保证在/usr/local/nginx/cert 有俩文件
  
# 4 修改nginx的配置文件
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    client_max_body_size 20M;
	server {
    	listen 443 ssl;
     	ssl_certificate /usr/local/nginx/cert/liuqingzheng.top.pem;
     	ssl_certificate_key /usr/local/nginx/cert/liuqingzheng.top.key;
        server_name  liuqingzheng.top;
        location / {
            include uwsgi_params;
            uwsgi_pass 127.0.0.1:8080;
            uwsgi_param UWSGI_SCRIPT smart_backend.wsgi;
            uwsgi_param UWSGI_CHDIR /root/smart_backend/;
            }

       location /static {
            alias /home/smart_backend/static;
       }
    }
}

# 重启nginx
nginx -s reload

app.json

{
  "pages": [
    "pages/welcome/welcome",
    "pages/index/index",
    "pages/my/my",
    "pages/activity/activity",
    "pages/notice/notice",
    "pages/second/collection/collection",
    "pages/second/face/face",
    "pages/second/voice/voice",
    "pages/second/heart/heart",
    "pages/second/goods/goods",
    "pages/second/camera/camera",
    "pages/second/form/form",
    "pages/second/statistics/statistics",
    "pages/second/otherlogin/otherlogin",
    "pages/second/store/store"
  ],
  "window": {
    "navigationBarBackgroundColor": "#ffffff",
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "test",
    "backgroundColor": "#eeeeee",
    "backgroundTextStyle": "light"
},
"tabBar": {
    "selectedColor": "#1c1c1b",
    "position": "bottom",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页",
        "iconPath": "/static/img/icon/home.png",
        "selectedIconPath": "/static/img/icon/home-o.png"
      },
      {
        "pagePath": "pages/activity/activity",
        "text": "活动",
        "iconPath": "/static/img/icon/aid.png",
        "selectedIconPath": "/static/img/icon/aid-o.png"
      },
      {
        "pagePath": "pages/notice/notice",
        "text": "公告",
        "iconPath": "/static/img/icon/circle.png",
        "selectedIconPath": "/static/img/icon/circle-o.png"
      },
      {
        "pagePath": "pages/my/my",
        "text": "我的",
        "iconPath": "/static/img/icon/my.png",
        "selectedIconPath": "/static/img/icon/my-o.png"
      }
    ]
  },
  "componentFramework": "glass-easel",
  "sitemapLocation": "sitemap.json",
  "lazyCodeLoading": "requiredComponents",
  "usingComponents": {
    "van-button": "@vant/weapp/button/index",
    "van-grid": "@vant/weapp/grid/index",
    "van-grid-item": "@vant/weapp/grid-item/index",
    "van-notice-bar": "@vant/weapp/notice-bar/index",
    "van-field": "@vant/weapp/field/index",
    "van-cell": "@vant/weapp/cell/index",
    "van-cell-group": "@vant/weapp/cell-group/index"
  }
}

app.wxss

@import "/static/css/iconfont.wxss"

settings.js

const BASE_URL = 'http://192.168.1.38:8000/api/v1/'

export default {
    welcome:BASE_URL + 'welcome/',
    banner:BASE_URL + 'banner/',
    collection:BASE_URL + 'collection/',
    area:BASE_URL + 'area/',
    statistics:BASE_URL + 'statistics/',
    face:BASE_URL + 'face/',
    voice:BASE_URL + 'voice/',
    activity:BASE_URL + 'activity/',
    notice:BASE_URL + 'notice/',
    quick_login:BASE_URL+'user/quick_login/',
    send_sms:BASE_URL+'user/send_sms/',
    login:BASE_URL + 'user/login/',
    join:BASE_URL + 'join/join/',
}

image-20240529155533364

app.js

// app.js
App({
    globalData: {
        userInfo: null
    },
    initUserInfo: function (name, score, avatar, token) {
        var info = {
            name: name,
            score: score,
            avatar: avatar,
            token: token
        };
        this.globalData.userInfo = info
        wx.setStorageSync('userInfo', info);
    },
    logoutUserInfo: function () {
        wx.removeStorageSync('userInfo');
        this.globalData.userInfo = null;
    },
    onLaunch() {
        var info = wx.getStorageSync('userInfo')
        console.log(info)
        this.globalData.userInfo = info
    }
})

urls.py

from django.contrib import admin
from django.urls import path, include
from .views import (
    WelcomeView,
    BannerView,
    CollectionView,
    AreaView,
    StatisticsView,
    FaceView,
    VoiceView,
    ActivityView,
    NoticeView,
    LoginView,
    ActivityJoinView,
)
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register("welcome", WelcomeView, "welcome")
router.register("banner", BannerView, "banner")
router.register("collection", CollectionView, "collection")
router.register("area", AreaView, "area")
router.register("statistics", StatisticsView, "statistics")
router.register("face", FaceView, "face")
router.register("voice", VoiceView, "voice")
router.register("activity", ActivityView, "activity")
router.register("notice", NoticeView, "notice")
router.register("user", LoginView, "user")
router.register("join", ActivityJoinView, "join")


urlpatterns = [path("", include(router.urls))]

admin.py

from django.contrib import admin

# Register your models here.
from .models import *

admin.site.register(Welcome)
admin.site.register(Banner)
admin.site.register(Notice)
admin.site.register(UserInfo)
admin.site.register(Area)
admin.site.register(Collection)
admin.site.register(Activity)

Authentication1.py

from .models import UserInfo
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_simplejwt.authentication import JWTAuthentication


class MyJSONWebTokenAuthentication(JWTAuthentication):
    def authenticate(self, request):
        jwt_value = request.META.get("HTTP_TOKEN")
        if not jwt_value:
            raise AuthenticationFailed("token 字段是必须的")
        validated_token = self.get_validated_token(jwt_value)
        print(validated_token["user_id"])
        user = UserInfo.objects.filter(pk=validated_token["user_id"]).first()
        return user, jwt_value

models.py

from django.db import models


# 广告表
class Welcome(models.Model):
    img = models.ImageField(upload_to="welcome", default="slash.png")
    order = models.IntegerField()
    link = models.CharField(max_length=32)
    create_time = models.DateTimeField(auto_now=True)
    is_delete = models.BooleanField(default=False)


# 轮播图
class Banner(models.Model):
    img = models.ImageField(
        upload_to="banner", default="banner1.png", verbose_name="图片"
    )
    order = models.IntegerField(verbose_name="顺序")
    create_time = models.DateTimeField(auto_now=True, verbose_name="创建时间")
    is_delete = models.BooleanField(default=False, verbose_name="是否删除")

    class Meta:
        verbose_name_plural = "轮播图"


# 公告
class Notice(models.Model):
    title = models.CharField(max_length=64, verbose_name="公共标题")
    content = models.TextField(verbose_name="内容")
    img = models.ImageField(
        upload_to="notice", default="notice.png", verbose_name="公告图片"
    )
    create_time = models.DateTimeField(auto_now=True, verbose_name="创建时间")

    class Meta:
        verbose_name_plural = "公告表"


# 采集表
class Collection(models.Model):
    name = models.CharField(max_length=32, verbose_name="采集人员姓名")
    # 做为人脸识别的id号
    name_pinyin = models.CharField(max_length=32, verbose_name="姓名拼音", null=True)
    avatar = models.ImageField(
        upload_to="collection/%Y/%m/%d/", default="default.png", verbose_name="头像"
    )
    create_time = models.DateTimeField(auto_now=True, verbose_name="采集时间")
    # face_token---->人脸识别的token唯一码
    face_token = models.CharField(max_length=64, verbose_name="百度Token", null=True)
    # 区域是外键关联
    area = models.ForeignKey(
        to="Area", null=True, verbose_name="网格区域", on_delete=models.CASCADE
    )

    class Meta:
        verbose_name_plural = "采集表"

    def __str__(self):
        return self.name


# 区域表
class Area(models.Model):
    name = models.CharField(max_length=32, verbose_name="网格区域名")
    desc = models.CharField(max_length=32, verbose_name="网格简称")
    # 跟用户一对多---》一个网格员,可以采集多个网格
    user = models.ForeignKey(
        to="UserInfo",
        on_delete=models.CASCADE,
        null=True,
        verbose_name="负责用户",
        blank=True,
    )

    class Meta:
        verbose_name_plural = "区域表"

    def __str__(self):
        return self.name


# 用户表
class UserInfo(models.Model):
    name = models.CharField(verbose_name="姓名", max_length=32)
    mobile = models.CharField(max_length=11, verbose_name="手机号", null=True)
    avatar = models.FileField(verbose_name="头像", max_length=128, upload_to="avatar")
    create_date = models.DateField(verbose_name="日期", auto_now_add=True)
    score = models.IntegerField(verbose_name="积分", default=0)

    class Meta:
        verbose_name_plural = "用户表"

    def __str__(self):
        return self.name


#  活动表
class Activity(models.Model):
    title = models.CharField(verbose_name="活动标题", max_length=128)
    text = models.TextField(verbose_name="活动描述", null=True, blank=True)
    date = models.DateField(verbose_name="举办活动日期")

    count = models.IntegerField(verbose_name="报名人数", default=0)
    total_count = models.IntegerField(verbose_name="总人数", default=0)
    score = models.IntegerField(verbose_name="积分", default=0)

    join_record = models.ManyToManyField(
        verbose_name="参与者",
        through="JoinRecord",
        through_fields=("activity", "user"),
        to="UserInfo",
    )

    class Meta:
        verbose_name_plural = "活动表"

    def __str__(self):
        return self.title


#  活动报名记录
class JoinRecord(models.Model):
    user = models.ForeignKey(
        verbose_name="用户", to="UserInfo", on_delete=models.CASCADE
    )
    activity = models.ForeignKey(
        verbose_name="活动", to="Activity", on_delete=models.CASCADE, related_name="ac"
    )

    exchange = models.BooleanField(verbose_name="是否已兑换", default=False)

    class Meta:
        verbose_name_plural = "活动报名记录"

serializer.py

from rest_framework import serializers
from .models import *


class WelcomeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Welcome
        fields = ["img", "link"]


# 轮播图表序列化类
class BannerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Banner
        fields = "__all__"


# 社区通知序列化类
class NoticeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Notice
        fields = ["id", "title"]


# 上传人脸序列化类
class CollectionSaveSerializer(serializers.ModelSerializer):
    class Meta:
        model = Collection
        fields = ["name", "avatar", "area"]

    def create(self, validated_data):
        # 在百度ai注册
        from libs.baidu_ai import BaiDuFace

        baidu = BaiDuFace()
        avatar_file_object = validated_data.get("avatar")
        print(avatar_file_object)
        name = validated_data.get("name")
        name_pinyin = baidu.name_to_pinyin(name)
        res = baidu.add_user(avatar_file_object, name, name_pinyin)
        validated_data["name_pinyin"] = name_pinyin
        validated_data["face_token"] = res.get("result").get("face_token")
        instance = super().create(validated_data)
        return instance


# 采集序列化类
class CollectionSerializer(serializers.ModelSerializer):  # 查询所有序列化类
    class Meta:
        model = Collection
        fields = ["id", "name", "avatar", "area"]
        depth = 1  # area 外键关联详情拿到


# 网格序列化类
class AreaSerializer(serializers.ModelSerializer):
    class Meta:
        model = Area
        fields = ["id", "name", "desc"]


### 采集统计
class StatisticsListSerializer(serializers.Serializer):
    date = serializers.DateTimeField(format="%Y年%m月%d日")
    count = serializers.IntegerField()


class ActivitySerializer(serializers.ModelSerializer):
    class Meta:
        model = Activity
        fields = ["id", "title", "text", "date", "count", "score", "total_count"]
        extra_kwargs = {"date": {"format": "%Y-%m-%d"}}


class NoticeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Notice
        fields = ["id", "title", "img", "create_time", "content"]
        extra_kwargs = {"create_time": {"format": "%Y-%m-%d"}}

views.py

from django.shortcuts import render
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin, DestroyModelMixin, CreateModelMixin
from .serializers import (
    WelcomeSerializer,
    BannerSerializer,
    NoticeSerializer,
    CollectionSerializer,
    CollectionSaveSerializer,
    ActivitySerializer,
)
from .models import Welcome, Notice, Banner, Collection, Activity, UserInfo, JoinRecord
from rest_framework.response import Response
from datetime import datetime
from libs.baidu_ai import BaiDuVoice
from rest_framework.decorators import action
from django.core.cache import cache
from faker import Faker
from libs.send_tx_sms import send_sms_by_phone, get_code
from rest_framework_simplejwt.tokens import RefreshToken
from core.Authentication1 import MyJSONWebTokenAuthentication


# 广告
class WelcomeView(GenericViewSet, ListModelMixin):
    queryset = Welcome.objects.all().filter(is_delete=False).order_by("-order")
    serializer_class = WelcomeSerializer


# 轮播图
class BannerView(GenericViewSet, ListModelMixin):
    queryset = Banner.objects.all().filter(is_delete=False).order_by("order")[:2]
    serializer_class = BannerSerializer

    def list(self, request, *args, **kwargs):
        res = super().list(request, *args, **kwargs)
        notice = Notice.objects.all().order_by("create_time").first()
        serializer = NoticeSerializer(instance=notice)
        return Response(
            {"code": 100, "msg": "成功", "banner": res.data, "notice": serializer.data}
        )


class CollectionView(
    GenericViewSet, ListModelMixin, DestroyModelMixin, CreateModelMixin
):
    # 查出当天的--》没过滤当前用户
    queryset = Collection.objects.all().filter(create_time__gte=datetime.now().date())
    serializer_class = CollectionSerializer

    def get_serializer_class(self):
        if self.action == "create":
            return CollectionSaveSerializer
        else:
            return CollectionSerializer

    def list(self, request, *args, **kwargs):
        # 过滤当前用户采集的--》多种类型用户,数据权限不一样
        res = super().list(request, *args, **kwargs)
        today_count = len(self.get_queryset())
        return Response(
            {"code": 100, "msg": "成功", "result": res.data, "today_count": today_count}
        )

    ## 删除人脸,没有删除 具体的图片--》定时任务--》每天晚上备份用户头像
    def destroy(self, request, *args, **kwargs):
        from libs.baidu_ai import BaiDuFace

        instance = self.get_object()
        # 百度ai中删除
        baidu = BaiDuFace()
        res = baidu.delete(instance.name_pinyin, instance.face_token)
        print(res)
        self.perform_destroy(instance)
        return Response()


from .models import Area
from .serializers import AreaSerializer


# 获取所有网格--》应该获取当前用户所管理的网格
class AreaView(GenericViewSet, ListModelMixin):
    queryset = Area.objects.all()
    serializer_class = AreaSerializer
    # 配置一个过滤类--》在过滤类中对 queryset进行过滤---》过滤出当前用户的网格


#### 采集统计
from django.db.models import Count
from django.db.models.functions import Trunc
from .models import Collection
from .serializers import StatisticsListSerializer


class StatisticsView(GenericViewSet, ListModelMixin):
    # 统计时按天分组---》数个数
    queryset = (
        Collection.objects.annotate(date=Trunc("create_time", "day"))
        .values("date")
        .annotate(count=Count("id"))
        .values("date", "count")
    )
    serializer_class = StatisticsListSerializer


### 人脸检测视图类
class FaceView(GenericViewSet):
    def create(self, request, *args, **kwargs):
        avatar_object = request.data.get("avatar")
        if not avatar_object:
            return Response({"msg": "未提交图像", "code": 101})
        from libs.baidu_ai import BaiDuFace

        ai = BaiDuFace()
        result = ai.search(avatar_object)
        print(result)
        if result.get("error_code") == 0:  # 查询到

            # {'error_code': 0, 'error_msg': 'SUCCESS', 'log_id': 2159604393, 'timestamp': 1713864959, 'cached': 0, 'result': {'face_token': '095994eca64424cee347b59e0a7edc0e', 'user_list': [{'group_id': '100', 'user_id': 'li3si1xian4', 'user_info': '', 'score': 98.035797119141}]}}
            user = result.get("result").get("user_list")[0]
            user_info = user.get("user_info")
            user_id = user.get("user_id")
            score = user.get("score")
            return Response(
                {
                    "code": 100,
                    "msg": "匹配成功",
                    "user_info": user_info,
                    "user_id": user_id,
                    "score": score,
                    "avatar": "",
                }
            )
        else:
            return Response(
                {"code": 102, "msg": "匹配失败,该人员可能不是我社区人员,注意防范"}
            )


# 语音识别
class VoiceView(GenericViewSet):
    def create(self, request, *args, **kwargs):
        voice_object = request.data.get("voice")
        # with open('./a.wav','wb') as f:
        #     f.write(voice_object.read())
        ai = BaiDuVoice()
        result = ai.speed(voice_object)
        # {'corpus_no': '6847771638436561158', 'result': ['你是不是打过来?'], 'sn': '15921476781594371078', 'err_msg': 'success.', 'err_no': 0}
        if result.get("err_no") == 0:
            return Response(
                {"code": 100, "msg": "识别成功", "result": result.get("result")}
            )
        else:
            return Response({"code": 101, "msg": "识别失败"})


class ActivityView(GenericViewSet, ListModelMixin):
    queryset = Activity.objects.all().order_by("date")
    serializer_class = ActivitySerializer


class NoticeView(GenericViewSet, ListModelMixin):
    queryset = Notice.objects.all().order_by("create_time")
    serializer_class = NoticeSerializer


class LoginView(GenericViewSet):
    @action(methods=["GET"], detail=False)
    def send_sms(self, request, *args, **kwargs):
        # 1 取出前端传入手机号
        mobile = request.query_params.get("mobile")
        # 2 获取随机验证码
        code = get_code()
        # 3 验证码放到缓存
        cache.set(f"sms_{mobile}", code)
        # 4 发送短信
        res = send_sms_by_phone(mobile, code)
        if res:
            return Response({"code": 100, "msg": "短信发送成功"})
        else:
            return Response({"code": 101, "msg": "短信发送失败,请稍后再试"})

    @action(methods=["POST"], detail=False)
    def login(self, request, *args, **kwargs):
        # 1 取出手机号和验证码
        mobile = request.data.get("mobile")
        code = request.data.get("code")
        # 2 校验验证码是否正确
        old_code = cache.get(f"sms_{mobile}")
        if old_code == code:
            # 3 数据库查询用户,如果存在直接签发token登录成功

            user = UserInfo.objects.filter(mobile=mobile).first()
            if not user:
                # 4 如果用户不存在,创建用户,再签发token
                fake = Faker("zh_CN")
                username = fake.name()
                user = UserInfo.objects.create(mobile=mobile, name=username)
            refresh = RefreshToken.for_user(user)
            return Response(
                {
                    "code": 100,
                    "msg": "登录成功",
                    "token": str(refresh.access_token),
                    "name": user.name,
                    "score": user.score,
                    "avatar": "http://127.0.0.1:8000/media/" + str(user.avatar),
                }
            )
        else:
            return Response({"code": 101, "msg": "验证码错误"})

    @action(methods=["POST"], detail=False)
    def quick_login(self, request, *args, **kwargs):
        # 1 取出前端传入的code
        code = request.data.get("code")
        # 2 通过code,调用微信开发平台接口,换取手机号
        # 3 拿到手机号再自己库中查,能查到,签发token
        # 4 查不到注册再签发token
        # 假数据---》都签发成第一个用户
        user = UserInfo.objects.filter(pk=1).first()
        refresh = RefreshToken.for_user(user)
        return Response(
            {
                "code": 100,
                "msg": "登录成功",
                "token": str(refresh.access_token),
                "name": user.name,
                "score": user.score,
                "avatar": "http://127.0.0.1:8000/media/" + str(user.avatar),
            }
        )


class ActivityJoinView(GenericViewSet):
    authentication_classes = [MyJSONWebTokenAuthentication]

    @action(methods=["POST"], detail=False)
    def join(self, request, *args, **kwargs):
        # 1 取出要参加的活动id
        activity_id = request.data.get("id")
        # 2 取出当前登录用户
        user = request.user
        # 2 查到当前活动
        activity = Activity.objects.filter(pk=activity_id).first()
        # 3 判断时间,判断人数
        # 4 判断是否报名过
        join_record = JoinRecord.objects.filter(
            activity_id=activity_id, user=user
        ).first()
        if join_record:
            return Response({"code": 101, "msg": "已经报名过,不用重复报名"})
        else:
            # 5 包名人数+1,报名报存入
            activity.count = activity.count + 1
            activity.save()
            JoinRecord.objects.create(activity=activity, user=user)
            # 6 返回报名成功
            return Response({"code": 100, "msg": "报名成功"})