购物车实现
一、切换有效期选项
1.改变课程有效期
要实现课程有效期的计算,则必须我们要清楚一个课程可以有1到多个有效期选项。默认保存在课程模型中的价格如果有值,则这个值是永久有效的购买价格。如果有别的购买期限,则我们需要另行创建一个模型来保存!
course/models.py
... class CourseExpire(BaseModel): """课程有效期模型""" # 后面可以在数据库把course和expire_time字段设置为联合索引 course = models.ForeignKey("Course", related_name='course_expire', on_delete=models.CASCADE, verbose_name="课程名称") # 有效期限,天数 expire_time = models.IntegerField(verbose_name="有效期", null=True, blank=True, help_text="有效期按天数计算") # 一个月有效等等 expire_text = models.CharField(max_length=150, verbose_name="提示文本", null=True, blank=True) # 每个有效期的价格 price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程价格", default=0) class Meta: db_table = "ly_course_expire" verbose_name = "课程有效期" verbose_name_plural = verbose_name def __str__(self): return "课程:%s,有效期:%s,价格:%s" % (self.course, self.expire_text, self.price)
讯享网
执行迁移
讯享网python manage.py makemigrations python manage.py migrate
添加测试数据
INSERT INTO `ly_course_expire` (`id`,`orders`,`is_show`,`is_deleted`,`created_time`,`updated_time`,`expire_time`,`expire_text`,`course_id`,`price`) VALUES (1,1,1,0,'2019-08-19 02:05:22.','2019-08-19 02:05:22.',30,'一个月有效',1,398.00), (2,2,1,0,'2019-08-19 02:05:37.','2019-08-19 02:05:37.',60,'2个月有效',1,588.00), (3,3,1,0,'2019-08-19 02:05:57.029411','2019-08-19 02:05:57.029440',180,'半年内有效',1,1000.00), (4,4,1,0,'2019-08-19 02:07:29.066617','2019-08-19 02:08:29.',3,'3天内有效',3,0.88), (5,3,1,0,'2019-08-19 02:07:46.','2019-08-19 02:08:18.',30,'1个月有效',3,188.00), (6,3,1,0,'2019-08-19 02:07:59.','2019-08-19 02:07:59.',60,'2个月有效',3,298.00);
注册到xadmin中,course/adminx.py,代码:
讯享网from .models import CourseExpire class CourseExpireModelAdmin(object): """商品有效期模型""" pass xadmin.site.register(CourseExpire, CourseExpireModelAdmin)
在course表中的price字段加一个提示文本
price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价", default=0,help_text='如果填写的价格为0,那么表示当前课程在购买的时候,没有永久有效的期限。')
course/models.py
讯享网... # 获取课程有效期 def get_expire(self): expire_list = self.course_expire.all() data = [] for expire in expire_list: data.append({
'id':expire.id, 'expire_text':expire.expire_text, 'price':expire.price, }) # 当价格为0时,没有永久有效这一项,其他的都有 if self.price > 0: data.append({
'id': 0, 'expire_text': '永久有效', 'price': self.price, }) return data ...
cart/views.py,新增expire_list,当前课程有效期的选项列表,代码:
... cart_data_list.append({
'course_id':course_obj.id, 'name':course_obj.name, 'course_img':contains.SERVER_ADDR + course_obj.course_img.url , 'price':course_obj.price, 'real_price':course_obj.real_price(), 'expire_id':expire_id, 'expire_list':course_obj.get_expire(), 'selected':False, # 默认没有勾选 }) ...
postman测试:
讯享网
客户端中获取到数据以后,展示有效期选项:
CartItem.vue
讯享网... <div class="cart_column column_3"> <el-select v-model="cart.expire_id" size="mini" placeholder="请选择购买有效期" class="my_el_select"> <el-option :label="expire.expire_text" :value="expire.id" :key="expire.id" v-for="(expire,expire_index) in cart.expire_list"></el-option> </el-select> </div> ...
... cart_data_list.append({
'course_id':course_obj.id, 'name':course_obj.name, 'course_img':contains.SERVER_ADDR + course_obj.course_img.url , 'price':course_obj.price, 'real_price':course_obj.real_price(), 'expire_id':int(expire_id), #不然它是个字符串,因为redis的问题,只能存为字符串,那么前端渲染下拉框的时候,会多一个选项0. 'expire_list':course_obj.get_expire(), 'selected':False, #默认没有勾选 }) ...
有了有效期选项列表以后,我们就可以实现,当用户切换有效期选项以后,同步更新redis中购物车商品的有效期值。
2.根据课程有效期调整价格
路由代码:cart/urls.py
讯享网from django.urls import path,re_path from . import views urlpatterns = [ path('add_cart/', views.AddCartView.as_view( {
'post':'add','get':'cart_list', 'patch':'change_select','put':'cancel_select', 'delete':'delete_course' })), path('expires/', views.AddCartView.as_view({
'put':'change_expire','get':'show_pay_info'})) ]
服务端提供切换有效期选项的接口,视图:cart/views.py
... # 切换有效期 def change_expire(self,request): user_id = request.user.id course_id = request.data.get('course_id') expire_id = request.data.get('expire_id') try: course_obj = models.Course.objects.get(id=course_id) except: return Response({
'msg':'课程不存在'},status=status.HTTP_400_BAD_REQUEST) try: if expire_id > 0: expire_object = models.CourseExpire.objects.get(id=expire_id) except: return Response({
'msg':'课程有效期不存在'},status=status.HTTP_400_BAD_REQUEST) real_price = course_obj.real_price(expire_id) conn = get_redis_connection('cart') conn.hset('cart_%s' % user_id, course_id, expire_id) return Response({
'msg':'切换成功!', 'real_price': real_price}) ...
客户端中,实现axios请求,CartItem.vue,代码:
讯享网... export default {
name: "CartItem", data(){
return {
// checked:false, } }, props:['cart', ], watch:{
'cart.selected':function (){
// 添加选中 let token = localStorage.token || sessionStorage.token; if (this.cart.selected){
this.$axios.patch(`${
this.$settings.Host}/cart/add_cart/`,{
course_id: this.cart.course_id, },{
headers:{
'Authorization':'jwt ' + token } }).then((res)=>{
this.$message.success(res.data.msg); this.$emit('cal_t_p') }).catch((error)=>{
this.$message.error(res.data.msg); }) } else {
// 取消选中 this.$axios.put(`${
this.$settings.Host}/cart/add_cart/`,{
course_id: this.cart.course_id, },{
headers:{
'Authorization':'jwt ' + token } }).then((res)=>{
this.$message.success(res.data.msg); this.$emit('cal_t_p') }).catch((error)=>{
this.$message.error(res.data.msg); }) } }, 'cart.expire_id':function (){
let token = localStorage.token || sessionStorage.token; this.$axios.put(`${
this.$settings.Host}/cart/expires/`,{
course_id: this.cart.course_id, expire_id:this.cart.expire_id, },{
headers:{
'Authorization':'jwt ' + token } } ).then((res)=>{
this.$message.success(res.data.msg); // console.log('>>>>', res.data.real_price) this.cart.real_price = res.data.real_price; this.$emit('change_expire_handler',) }).catch((error)=>{
this.$message.error(error.response.data.msg) }) } }, ...
接下来分2步进行:
- 在用户进入购物车页面时,根据不同的有效期选项,在获取购物车商品列表时,需要查询真实价格。
- 在用户点击切换不同有效期选项时,也要同步切换当前商品课程对应的真实价格
在用户进入页面时,根据不同有效期选项显示对应的真实价格
模型代码:course模型类models.py中的real_price方法需要改一下:
... # 真实价格计算 def real_price(self,expire_id=0): price = float(self.price) if expire_id > 0: expire_obj = self.course_expire.get(id=expire_id) price = float(expire_obj.price) r_price = price a = self.activity() if a: sale = a[0].discount.sale condition_price = a[0].discount.condition # 限时免费 if not sale.strip(): r_price = 0 # 限时折扣 *0.5 elif '*' in sale.strip(): if price >= condition_price: _, d = sale.split('*') r_price = price * float(d) # 限时减免 -100 elif sale.strip().startswith('-'): if price >= condition_price: _, d = sale.split('-') r_price = price - float(d) # 满减 # ''' # 满100-10 # 满300 - 50 # 满600 - 100 # 满200-25 # # ''' elif '满' in sale: if price >= condition_price: l1 = sale.split('\r\n') dis_list = [] #10 50 25 for i in l1: a, b = i[1:].split('-') #400 if price >= float(a): dis_list.append(float(b)) max_dis = max(dis_list) r_price = price - max_dis return r_price ...
讯享网... def cart_list(self,request): # requset.user # print('>>>>>>>>>>>>>>>', request.user) # user_id = 1 user_id = request.user.id conn = get_redis_connection('cart') conn.delete('selected_cart_%s' % user_id) ret = conn.hgetall('cart_%s' % user_id) #dict {b'1': b'0', b'2': b'0'} cart_data_list = [] # print(ret) try: for cid, eid in ret.items(): course_id = cid.decode('utf-8') # expire_id = int(eid.decode('utf-8')) conn.hset('cart_%s' % user_id, course_id, 0) expire_id = 0 course_obj = models.Course.objects.get(id=course_id) cart_data_list.append({
'course_id':course_obj.id, 'name':course_obj.name, 'course_img':contains.SERVER_ADDR + course_obj.course_img.url , 'price':course_obj.price, 'real_price':course_obj.real_price(), 'expire_id':expire_id, 'expire_list':course_obj.get_expire(), 'selected':False, #默认没有勾选 }) except Exception: logger.error('获取购物车数据失败') return Response({
'msg':'后台数据库出问题了,请联系管理员'},status=status.HTTP_507_INSUFFICIENT_STORAGE) # try: # models.Course.objects.filter() # print(cart_data_list) return Response({
'msg':'xxx','cart_data_list':cart_data_list}) ...
客户端点击切换不同有效期选项时,更新真实价格
在原来切换有效期选项时,直接增加获取真实价格的代码逻辑,cart/views.py,代码:
... # 切换有效期 def change_expire(self,request): user_id = request.user.id course_id = request.data.get('course_id') expire_id = request.data.get('expire_id') try: course_obj = models.Course.objects.get(id=course_id) except: return Response({
'msg':'课程不存在'},status=status.HTTP_400_BAD_REQUEST) try: if expire_id > 0: expire_object = models.CourseExpire.objects.get(id=expire_id) except: return Response({
'msg':'课程有效期不存在'},status=status.HTTP_400_BAD_REQUEST) real_price = course_obj.real_price(expire_id) conn = get_redis_connection('cart') conn.hset('cart_%s' % user_id, course_id, expire_id) return Response({
'msg':'切换成功!', 'real_price': real_price}) ...
客户端在ajax请求中,补充修改价格,CartItem.vue,代码:
讯享网<template> <div class="cart_item"> <div class="cart_column column_1"> <el-checkbox class="my_el_checkbox" v-model="cart.selected"></el-checkbox> </div> <div class="cart_column column_2"> <img :src="cart.course_img" alt=""> <span><router-link to="/course/detail/1">{
{
cart.name}}</router-link></span> </div> <div class="cart_column column_3"> <el-select v-model="cart.expire_id" size="mini" placeholder="请选择购买有效期" class="my_el_select"> <el-option :label="expire.expire_text" :value="expire.id" :key="expire.id" v-for="(expire,expire_index) in cart.expire_list"></el-option> </el-select> </div> <div class="cart_column column_4">¥{
{
cart.real_price}}</div> <div class="cart_column column_4" id="delete" @click="delete_course">删除</div> </div> </template> <script> export default {
name: "CartItem", data(){
return {
// checked:false, } }, props:['cart', ], watch:{
'cart.selected':function (){
// 添加选中 let token = localStorage.token || sessionStorage.token; if (this.cart.selected){
this.$axios.patch(`${
this.$settings.Host}/cart/add_cart/`,{
course_id: this.cart.course_id, },{
headers:{
'Authorization':'jwt ' + token } }).then((res)=>{
this.$message.success(res.data.msg); this.$emit('cal_t_p') }).catch((error)=>{
this.$message.error(res.data.msg); }) } else {
// 取消选中 this.$axios.put(`${
this.$settings.Host}/cart/add_cart/`,{
course_id: this.cart.course_id, },{
headers:{
'Authorization':'jwt ' + token } }).then((res)=>{
this.$message.success(res.data.msg); this.$emit('cal_t_p') }).catch((error)=>{
this.$message.error(res.data.msg); }) } }, 'cart.expire_id':function (){
let token = localStorage.token || sessionStorage.token; this.$axios.put(`${
this.$settings.Host}/cart/expires/`,{
course_id: this.cart.course_id, expire_id:this.cart.expire_id, },{
headers:{
'Authorization':'jwt ' + token } } ).then((res)=>{
this.$message.success(res.data.msg); // console.log('>>>>', res.data.real_price) this.cart.real_price = res.data.real_price; this.$emit('change_expire_handler',) }).catch((error)=>{
this.$message.error(error.response.data.msg) }) } }, methods:{
delete_course(){
let token = localStorage.token || sessionStorage.token; this.$axios.delete(`${
this.$settings.Host}/cart/add_cart/`,{
params:{
course_id: this.cart.course_id, // request.query_params.get('course_id') }, headers:{
'Authorization':'jwt ' + token } }) .then((res)=>{
this.$message.success(res.data.msg); this.$emit('delete_course_handler') }) .catch((error)=>{
this.$message.error(error.response.data.msg); }) } } } </script> ...
3.统计整个购物车中,勾选所有商品的总价格
有了购物车中各个商品课程的真实价格以后,那么我们就统计计算整个购物车中,勾选所有商品的总价格了。
先看一个foreach的用法
let a = [11,22,33]; undefined a.forEach(function(value,key){
console.log(value,key); }); 结果: 11 0 22 1 33 2
Cart.vue
讯享网<template> <div class="cart"> <Vheader></Vheader> <div class="cart_info"> <div class="cart_title"> <span class="text">我的购物车</span> <span class="total">共4门课程</span> </div> <div class="cart_table"> <div class="cart_head_row"> <span class="doing_row"></span> <span class="course_row">课程</span> <span class="expire_row">有效期</span> <span class="price_row">单价</span> <span class="do_more">操作</span> </div> <div class="cart_course_list"> <CartItem v-for="(value,index) in cart_data_list" :key="index" :cart="value" @cal_t_p="cal_total_price" @change_expire_handler="cal_total_price" @delete_course_handler="delete_c(index)"></CartItem> </div> <div class="cart_footer_row"> <span class="cart_select"><label> <el-checkbox v-model="checked"></el-checkbox><span>全选</span></label></span> <span class="cart_delete"><i class="el-icon-delete"></i> <span>删除</span></span> <span class="goto_pay"><router-link to="/order/">去结算</router-link></span> <span class="cart_total">总计:¥{
{
total_price}}</span> </div> </div> </div> <Footer></Footer> </div> </template> <script> import Vheader from "./common/Vheader" import Footer from "./common/Footer" import CartItem from "./common/CartItem" export default {
name: "Cart", data(){
return {
checked: false, total_price:0, cart_data_list:[], } }, methods:{
cal_total_price(){
// {
// "name": "flask框架", // "course_img": "http://www.lyapi.com:8001/media/course/course-cover.jpeg", // "price": 1110.0, // "real_price": 0, // "expire_id": "0", // 'selected':true, // } // {
// "name": "flask框架", // "course_img": "http://www.lyapi.com:8001/media/course/course-cover.jpeg", // "price": 1110.0, // "real_price": 0, // "expire_id": "0", // 'selected':false, // } let t_price = 0 this.cart_data_list.forEach((v,k)=>{
if (v.selected){
t_price += v.real_price } }) console.log('total_price>>>>',t_price) this.total_price = t_price }, delete_c(index){
this.cart_data_list.splice(index,1) this.cal_total_price() } }, created() {
let token = sessionStorage.token || localStorage.token; if (token){
this.$axios.get(`${
this.$settings.Host}/cart/add_cart/`,{
headers:{
'Authorization':'jwt ' + token } }) .then((res)=>{
this.cart_data_list = res.data.cart_data_list }) .catch((error)=>{
this.$message.error(error.response.data.msg); }) }else {
this.$router.push('/user/login'); } }, components:{
Vheader, Footer, CartItem, } } </script> <style scoped> .cart_info{
width: 1200px; margin: 0 auto 50px; } .cart_title{
margin: 25px 0; } .cart_title .text{
font-size: 18px; color: #666; } .cart_title .total{
font-size: 12px; color: #d0d0d0; } .cart_table{
width: 1170px; } .cart_table .cart_head_row{
background: #F7F7F7; width: 100%; height: 80px; line-height: 80px; padding-right: 30px; } .cart_table .cart_head_row::after{
content: ""; display: block; clear: both; } .cart_table .cart_head_row .doing_row, .cart_table .cart_head_row .course_row, .cart_table .cart_head_row .expire_row, .cart_table .cart_head_row .price_row, .cart_table .cart_head_row .do_more{
padding-left: 10px; height: 80px; float: left; } .cart_table .cart_head_row .doing_row{
width: 78px; } .cart_table .cart_head_row .course_row{
width: 530px; } .cart_table .cart_head_row .expire_row{
width: 188px; } .cart_table .cart_head_row .price_row{
width: 162px; } .cart_table .cart_head_row .do_more{
width: 162px; } .cart_footer_row{
padding-left: 36px; background: #F7F7F7; width: 100%; height: 80px; line-height: 80px; } .cart_footer_row .cart_select span{
margin-left: 14px; font-size: 18px; color: #666; } .cart_footer_row .cart_delete{
margin-left: 58px; } .cart_delete .el-icon-delete{
font-size: 18px; } .cart_delete span{
margin-left: 15px; cursor: pointer; font-size: 18px; color: #666; } .cart_total{
float: right; margin-right: 62px; font-size: 18px; color: #666; } .goto_pay{
float: right; width: 159px; height: 80px; outline: none; border: none; background: #ffc210; font-size: 18px; color: #fff; text-align: center; cursor: pointer; } </style>
4.切换课程勾选状态或有效期选项时,重新计算总价

... <div class="cart_course_list"> <CartItem v-for="(value,index) in cart_data_list" :key="index" :cart="value" @cal_t_p="cal_total_price" @change_expire_handler="cal_total_price" @delete_course_handler="delete_c(index)"></CartItem> </div> ...
在子组件中,当用户切换勾选状态和切换有效期时,通过this.$emit()通知父组件调用cal_total_price方法
CartItem.vue
讯享网... <script> export default {
name: "CartItem", data(){
return {
// checked:false, } }, props:['cart', ], watch:{
'cart.selected':function (){
// 添加选中 let token = localStorage.token || sessionStorage.token; if (this.cart.selected){
this.$axios.patch(`${
this.$settings.Host}/cart/add_cart/`,{
course_id: this.cart.course_id, },{
headers:{
'Authorization':'jwt ' + token } }).then((res)=>{
this.$message.success(res.data.msg); this.$emit('cal_t_p') }).catch((error)=>{
this.$message.error(res.data.msg); }) } else {
// 取消选中 this.$axios.put(`${
this.$settings.Host}/cart/add_cart/`,{
course_id: this.cart.course_id, },{
headers:{
'Authorization':'jwt ' + token } }).then((res)=>{
this.$message.success(res.data.msg); this.$emit('cal_t_p') }).catch((error)=>{
this.$message.error(res.data.msg); }) } }, 'cart.expire_id':function (){
let token = localStorage.token || sessionStorage.token; this.$axios.put(`${
this.$settings.Host}/cart/expires/`,{
course_id: this.cart.course_id, expire_id:this.cart.expire_id, },{
headers:{
'Authorization':'jwt ' + token } } ).then((res)=>{
this.$message.success(res.data.msg); // console.log('>>>>', res.data.real_price) this.cart.real_price = res.data.real_price; this.$emit('change_expire_handler',) }).catch((error)=>{
this.$message.error(error.response.data.msg) }) } }, methods:{
delete_course(){
let token = localStorage.token || sessionStorage.token; this.$axios.delete(`${
this.$settings.Host}/cart/add_cart/`,{
params:{
course_id: this.cart.course_id, // request.query_params.get('course_id') }, headers:{
'Authorization':'jwt ' + token } }) .then((res)=>{
this.$message.success(res.data.msg); this.$emit('delete_course_handler') }) .catch((error)=>{
this.$message.error(error.response.data.msg); }) } } } </script> ...
5.每次刷新有效期都为永久有效
cart/views.py。hset
def cart_list(self,request): # requset.user # print('>>>>>>>>>>>>>>>', request.user) # user_id = 1 user_id = request.user.id conn = get_redis_connection('cart') conn.delete('selected_cart_%s' % user_id) ret = conn.hgetall('cart_%s' % user_id) #dict {b'1': b'0', b'2': b'0'} cart_data_list = [] # print(ret) try: for cid, eid in ret.items(): course_id = cid.decode('utf-8') # expire_id = int(eid.decode('utf-8')) conn.hset('cart_%s' % user_id, course_id, 0) expire_id = 0 course_obj = models.Course.objects.get(id=course_id) cart_data_list.append({
'course_id':course_obj.id, 'name':course_obj.name, 'course_img':contains.SERVER_ADDR + course_obj.course_img.url , 'price':course_obj.price, 'real_price':course_obj.real_price(), 'expire_id':expire_id, 'expire_list':course_obj.get_expire(), 'selected':False, #默认没有勾选 }) except Exception: logger.error('获取购物车数据失败') return Response({
'msg':'后台数据库出问题了,请联系管理员'},status=status.HTTP_507_INSUFFICIENT_STORAGE) # try: # models.Course.objects.filter() # print(cart_data_list) return Response({
'msg':'xxx','cart_data_list':cart_data_list})
二、购物车商品的删除操作
CartItem.vue
讯享网<template> <div class="cart_item"> <div class="cart_column column_1"> <el-checkbox class="my_el_checkbox" v-model="cart.selected"></el-checkbox> </div> <div class="cart_column column_2"> <img :src="cart.course_img" alt=""> <span><router-link to="/course/detail/1">{
{
cart.name}}</router-link></span> </div> <div class="cart_column column_3"> <el-select v-model="cart.expire_id" size="mini" placeholder="请选择购买有效期" class="my_el_select"> <el-option :label="expire.expire_text" :value="expire.id" :key="expire.id" v-for="(expire,expire_index) in cart.expire_list"></el-option> </el-select> </div> <div class="cart_column column_4">¥{
{
cart.real_price}}</div> <div class="cart_column column_4" id="delete" @click="delete_course">删除</div> </div> </template> <script> export default {
name: "CartItem", data(){
return {
// checked:false, } }, props:['cart', ], watch:{
'cart.selected':function (){
// 添加选中 let token = localStorage.token || sessionStorage.token; if (this.cart.selected){
this.$axios.patch(`${
this.$settings.Host}/cart/add_cart/`,{
course_id: this.cart.course_id, },{
headers:{
'Authorization':'jwt ' + token } }).then((res)=>{
this.$message.success(res.data.msg); this.$emit('cal_t_p') }).catch((error)=>{
this.$message.error(res.data.msg); }) } else {
// 取消选中 this.$axios.put(`${
this.$settings.Host}/cart/add_cart/`,{
course_id: this.cart.course_id, },{
headers:{
'Authorization':'jwt ' + token } }).then((res)=>{
this.$message.success(res.data.msg); this.$emit('cal_t_p') }).catch((error)=>{
this.$message.error(res.data.msg); }) } }, 'cart.expire_id':function (){
let token = localStorage.token || sessionStorage.token; this.$axios.put(`${
this.$settings.Host}/cart/expires/`,{
course_id: this.cart.course_id, expire_id:this.cart.expire_id, },{
headers:{
'Authorization':'jwt ' + token } } ).then((res)=>{
this.$message.success(res.data.msg); // console.log('>>>>', res.data.real_price) this.cart.real_price = res.data.real_price; this.$emit('change_expire_handler',) }).catch((error)=>{
this.$message.error(error.response.data.msg) }) } }, methods:{
delete_course(){
let token = localStorage.token || sessionStorage.token; this.$axios.delete(`${
this.$settings.Host}/cart/add_cart/`,{
params:{
course_id: this.cart.course_id, // request.query_params.get('course_id') }, headers:{
'Authorization':'jwt ' + token } }) .then((res)=>{
this.$message.success(res.data.msg); this.$emit('delete_course_handler') }) .catch((error)=>{
this.$message.error(error.response.data.msg); }) } } } </script> <style scoped> #delete{
cursor: pointer; } /*.cart_item{*/ /* height: 100px;*/ /*}*/ .cart_item::after{
content: ""; display: block; clear: both; } .cart_column{
float: left; height: 150px; display: flex; align-items: center; } .cart_item .column_1{
width: 88px; position: relative; } .my_el_checkbox{
position: absolute; left: 0; right: 0; bottom: 0; top: 0; margin: auto; width: 16px; height: 16px; } .cart_item .column_2 {
/*padding: 67px 10px;*/ width: 520px; /*height: 116px;*/ } .cart_item .column_2 img{
width: 175px; /*height: 115px;*/ margin-right: 35px; /*vertical-align: middle;*/ } .cart_item .column_3{
width: 197px; position: relative; padding-left: 10px; } .my_el_select{
width: 117px; height: 28px; position: absolute; top: 0; bottom: 0; margin: auto; } .cart_item .column_4{
/*padding: 67px 10px;*/ /*height: 116px;*/ width: 142px; /*line-height: 116px;*/ } </style>
cart/urls.py
from django.urls import path,re_path from . import views urlpatterns = [ path('add_cart/', views.AddCartView.as_view( {
'post':'add','get':'cart_list', 'patch':'change_select','put':'cancel_select', 'delete':'delete_course' })), path('expires/', views.AddCartView.as_view({
'put':'change_expire','get':'show_pay_info'})) ]
cart/views.py
讯享网... # 删除购物车数据 def delete_course(self,request): user_id = request.user.id course_id = request.query_params.get('course_id') print('course_id>>>',course_id) conn = get_redis_connection('cart') pipe = conn.pipeline() pipe.hdel('cart_%s' % user_id, course_id) pipe.srem('selected_cart_%s' % user_id, course_id) pipe.execute() return Response({
'msg':'删除成功'}) ...
Cart.vue
<template> <div class="cart"> <Vheader></Vheader> <div class="cart_info"> <div class="cart_title"> <span class="text">我的购物车</span> <span class="total">共4门课程</span> </div> <div class="cart_table"> <div class="cart_head_row"> <span class="doing_row"></span> <span class="course_row">课程</span> <span class="expire_row">有效期</span> <span class="price_row">单价</span> <span class="do_more">操作</span> </div> <div class="cart_course_list"> <CartItem v-for="(value,index) in cart_data_list" :key="index" :cart="value" @cal_t_p="cal_total_price" @change_expire_handler="cal_total_price" @delete_course_handler="delete_c(index)"></CartItem> </div> <div class="cart_footer_row"> <span class="cart_select"><label> <el-checkbox v-model="checked"></el-checkbox><span>全选</span></label></span> <span class="cart_delete"><i class="el-icon-delete"></i> <span>删除</span></span> <span class="goto_pay"><router-link to="/order/">去结算</router-link></span> <span class="cart_total">总计:¥{
{
total_price}}</span> </div> </div> </div> <Footer></Footer> </div> </template> <script> import Vheader from "./common/Vheader" import Footer from "./common/Footer" import CartItem from "./common/CartItem" export default {
name: "Cart", data(){
return {
checked: false, total_price:0, cart_data_list:[], } }, methods:{
cal_total_price(){
let t_price = 0 this.cart_data_list.forEach((v,k)=>{
if (v.selected){
t_price += v.real_price } }) console.log('total_price>>>>',t_price) this.total_price = t_price }, delete_c(index){
this.cart_data_list.splice(index,1) this.cal_total_price() } }, created() {
let token = sessionStorage.token || localStorage.token; if (token){
this.$axios.get(`${
this.$settings.Host}/cart/add_cart/`,{
headers:{
'Authorization':'jwt ' + token } }) .then((res)=>{
this.cart_data_list = res.data.cart_data_list }) .catch((error)=>{
this.$message.error(error.response.data.msg); }) }else {
this.$router.push('/user/login'); } }, components:{
Vheader, Footer, CartItem, } } </script> <style scoped> .cart_info{
width: 1200px; margin: 0 auto 50px; } .cart_title{
margin: 25px 0; } .cart_title .text{
font-size: 18px; color: #666; } .cart_title .total{
font-size: 12px; color: #d0d0d0; } .cart_table{
width: 1170px; } .cart_table .cart_head_row{
background: #F7F7F7; width: 100%; height: 80px; line-height: 80px; padding-right: 30px; } .cart_table .cart_head_row::after{
content: ""; display: block; clear: both; } .cart_table .cart_head_row .doing_row, .cart_table .cart_head_row .course_row, .cart_table .cart_head_row .expire_row, .cart_table .cart_head_row .price_row, .cart_table .cart_head_row .do_more{
padding-left: 10px; height: 80px; float: left; } .cart_table .cart_head_row .doing_row{
width: 78px; } .cart_table .cart_head_row .course_row{
width: 530px; } .cart_table .cart_head_row .expire_row{
width: 188px; } .cart_table .cart_head_row .price_row{
width: 162px; } .cart_table .cart_head_row .do_more{
width: 162px; } .cart_footer_row{
padding-left: 36px; background: #F7F7F7; width: 100%; height: 80px; line-height: 80px; } .cart_footer_row .cart_select span{
margin-left: 14px; font-size: 18px; color: #666; } .cart_footer_row .cart_delete{
margin-left: 58px; } .cart_delete .el-icon-delete{
font-size: 18px; } .cart_delete span{
margin-left: 15px; cursor: pointer; font-size: 18px; color: #666; } .cart_total{
float: right; margin-right: 62px; font-size: 18px; color: #666; } .goto_pay{
float: right; width: 159px; height: 80px; outline: none; border: none; background: #ffc210; font-size: 18px; color: #fff; text-align: center; cursor: pointer; } </style>
结算页面
总Order.vue
讯享网<template> <div class="cart"> <Vheader/> <div class="cart-info"> <h3 class="cart-top">购物车结算 <span>共1门课程</span></h3> <div class="cart-title"> <el-row> <el-col :span="2"> </el-col> <el-col :span="10">课程</el-col> <el-col :span="8">有效期</el-col> <el-col :span="4">价格</el-col> </el-row> </div> <div class="cart-item" v-for="(course,index) in course_list"> <el-row> <el-col :span="2" class="checkbox"> </el-col> <el-col :span="10" class="course-info"> <img :src="course.course_img" alt=""> <span>{
{
course.name}}</span> </el-col> <el-col :span="8"><span>{
{
course.expire_text}}</span></el-col> <el-col :span="4" class="course-price">¥{
{
course.real_price}}</el-col> </el-row> </div> <div class="discount"> <div id="accordion"> <div class="coupon-box"> <div class="icon-box"> <span class="select-coupon">使用优惠劵:</span> <a class="select-icon unselect" :class="use_coupon?'is_selected':''" @click="use_coupon=!use_coupon"><img class="sign is_show_select" src="../../static/img/12.png" alt=""></a> <span class="coupon-num">有{
{
coupon_list.length}}张可用</span> </div> <p class="sum-price-wrap">商品总金额:<span class="sum-price">0.00元</span></p> </div> <div id="collapseOne" v-if="use_coupon"> <ul class="coupon-list" v-if="coupon_list.length>0"> <li class="coupon-item" :class="select_coupon(index,coupon.id)" v-for="(coupon,index) in coupon_list" @click="change_coupon(index,coupon.id)"> <p class="coupon-name">{
{
coupon.coupon.name}}</p> <p class="coupon-condition">满{
{
coupon.coupon.condition}}元可以使用</p> <p class="coupon-time start_time">开始时间:{
{
coupon.start_time.replace('T',' ')}}</p> <p class="coupon-time end_time">过期时间:{
{
coupon.end_time.replace('T',' ')}}</p> </li> </ul> <div class="no-coupon" v-if="coupon_list.length<1"> <span class="no-coupon-tips">暂无可用优惠券</span> </div> </div> </div> <div class="credit-box"> <label class="my_el_check_box"><el-checkbox class="my_el_checkbox" v-model="use_credit"></el-checkbox></label> <p class="discount-num1" v-if="!use_credit">使用我的贝里</p> <p class="discount-num2" v-else><span>总积分:{
{
credit}}, <el-input-number v-model="num" @change="handleChange" :min="0" :max="max_credit()" label="描述文字"></el-input-number>,已抵扣 ¥{
{
num / credit_to_money}},本次花费{
{
num}}积分</span></p> </div> <p class="sun-coupon-num">优惠券抵扣:<span>0.00元</span></p> </div> <div class="calc"> <el-row class="pay-row"> <el-col :span="4" class="pay-col"><span class="pay-text">支付方式:</span></el-col> <el-col :span="8"> <span class="alipay" v-if="pay_type===0"><img src="../../static/img/alipay2.png" alt=""></span> <span class="alipay" @click="pay_type=0" v-else><img src="../../static/img/alipay.png" alt=""></span> <span class="alipay wechat" v-if="pay_type===1"><img src="../../static/img/wechat2.png" alt="" ></span> <span class="alipay wechat" @click="pay_type=1" v-else><img src="../../static/img/wechat.png" alt=""></span> </el-col> <el-col :span="8" class="count">实付款: <span>¥{
{
(total_price - num/credit_to_money).toFixed(2)}}</span></el-col> <el-col :span="4" class="cart-pay"><span @click="payhander">支付</span></el-col> </el-row> </div> </div> <Footer/> </div> </template> <script> import Vheader from "./common/Vheader" import Footer from "./common/Footer" export default {
name:"Order", data(){
return {
id: localStorage.id || sessionStorage.id, order_id:sessionStorage.order_id || null, course_list: [], coupon_list: [], total_price: 0, //加上优惠券和积分计算之后的真实总价 total_real_price: 0, pay_type:0, num:0, current_coupon:0, // 当前选中的优惠券id use_coupon:false, use_credit:false, coupon_obj:{
}, // 当前coupon对象 {} credit:0, // 用户剩余积分 credit_to_money:0, //积分兑换比例 } }, components:{
Vheader, Footer, }, watch:{
use_coupon(){
if (this.use_coupon === false){
this.current_coupon = 0; } }, //当选中的优惠券发生变化时,重新计算总价 current_coupon(){
this.cal_total_price(); } } , created(){
this.get_order_data(); this.get_user_coupon(); this.get_credit(); }, methods: {
max_credit(){
let a = parseFloat(this.total_price) * parseFloat(this.credit_to_money); if (this.credit >= a){
return a }else {
return parseFloat(this.credit) } }, get_credit(){
this.credit = localStorage.getItem('credit') || sessionStorage.getItem('credit') this.credit_to_money = localStorage.getItem('credit_to_money') || sessionStorage.getItem('credit_to_money') }, handleChange(value){
if (!value){
this.num = 0 } console.log(value); }, cal_total_price(){
if (this.current_coupon !== 0){
let tt = this.total_real_price; let sales = this.coupon_obj.coupon.sale; let d = parseFloat(this.coupon_obj.coupon.sale.substr(1)); if (sales[0] === '-'){
tt = this.total_real_price - d // this.total_real_price -= d }else if (sales[0] === '*'){
// this.total_real_price *= d tt = this.total_real_price * d } this.total_price = tt; } }, // 记录切换的couponid change_coupon(index,coupon_id){
let current_c = this.coupon_list[index] if (this.total_real_price < current_c.coupon.condition){
return false } let current_time = new Date() / 1000; let s_time = new Date(current_c.start_time) / 1000 let e_time = new Date(current_c.end_time) / 1000 if (current_time < s_time || current_time > e_time){
return false } this.current_coupon = coupon_id; this.coupon_obj = current_c; }, select_coupon(index,coupon_id){
// {
// "id": 2, // "start_time": "2020-11-10T09:03:00", // "coupon": {
// "name": "五十元优惠券", // "coupon_type": 1, // "timer": 30, // "condition": 50, // "sale": "-50" // }, // "end_time": "2020-12-10T09:03:00" // }, let current_c = this.coupon_list[index] if (this.total_real_price < current_c.coupon.condition){
return 'disable' } let current_time = new Date() / 1000; let s_time = new Date(current_c.start_time) / 1000 let e_time = new Date(current_c.end_time) / 1000 if (current_time < s_time || current_time > e_time){
return 'disable' } if (this.current_coupon === coupon_id){
return 'active' } return '' }, get_order_data(){
let token = localStorage.token || sessionStorage.token; this.$axios.get(`${
this.$settings.Host}/cart/expires/`,{
headers:{
'Authorization':'jwt ' + token } }).then((res)=>{
this.course_list = res.data.data; this.total_real_price = res.data.total_real_price; this.total_price = res.data.total_real_price; }) }, get_user_coupon(){
let token = localStorage.token || sessionStorage.token; this.$axios.get(`${
this.$settings.Host}/coupon/list/`,{
headers:{
'Authorization':'jwt ' + token } }).then((res)=>{
this.coupon_list = res.data; }).catch((error)=>{
this.$message.error('优惠券获取错误') }) }, // 支付 payhander(){
let token = localStorage.token || sessionStorage.token; this.$axios.post(`${
this.$settings.Host}/order/add_money/`,{
"pay_type":this.pay_type, "coupon":this.current_coupon, "credit":this.num, },{
headers:{
'Authorization':'jwt ' + token } }).then((res)=>{
this.$message.success('订单已经生成,马上跳转支付页面') let order_number = res.data.order_number let token = localStorage.token || sessionStorage.token; this.$axios.get(`${
this.$settings.Host}/payment/alipay/?order_number=${
order_number}`,{
headers:{
'Authorization':'jwt ' + token } }) .then((res)=>{
// res.data : alipay.trade...?a=1&b=2.... location.href = res.data.url; }) }).catch((error)=>{
this.$message.error(error.response.data.msg); }) } } } </script> <style scoped> .coupon-box{
text-align: left; padding-bottom: 22px; padding-left:30px; border-bottom: 1px solid #e8e8e8; } .coupon-box::after{
content: ""; display: block; clear: both; } .icon-box{
float: left; } .icon-box .select-coupon{
float: left; color: #666; font-size: 16px; } .icon-box::after{
content:""; clear:both; display: block; } .select-icon{
width: 20px; height: 20px; float: left; } .select-icon img{
max-height:100%; max-width: 100%; margin-top: 2px; transform: rotate(-90deg); transition: transform .5s; } .is_show_select{
transform: rotate(0deg)!important; } .coupon-num{
height: 22px; line-height: 22px; padding: 0 5px; text-align: center; font-size: 12px; float: left; color: #fff; letter-spacing: .27px; background: #fa6240; border-radius: 2px; margin-left: 20px; } .sum-price-wrap{
float: right; font-size: 16px; color: #4a4a4a; margin-right: 45px; } .sum-price-wrap .sum-price{
font-size: 18px; color: #fa6240; } .no-coupon{
text-align: center; width: 100%; padding: 50px 0px; align-items: center; justify-content: center; /* 文本两端对其 */ border-bottom: 1px solid rgb(232, 232, 232); } .no-coupon-tips{
font-size: 16px; color: #9b9b9b; } .credit-box{
height: 30px; margin-top: 40px; display: flex; align-items: center; justify-content: flex-end } .my_el_check_box{
position: relative; } .my_el_checkbox{
margin-right: 10px; width: 16px; height: 16px; } .discount{
overflow: hidden; } .discount-num1{
color: #9b9b9b; font-size: 16px; margin-right: 45px; } .discount-num2{
margin-right: 45px; font-size: 16px; color: #4a4a4a; } .sun-coupon-num{
margin-right: 45px; margin-bottom:43px; margin-top: 40px; font-size: 16px; color: #4a4a4a; display: inline-block; float: right; } .sun-coupon-num span{
font-size: 18px; color: #fa6240; } .coupon-list{
margin: 20px 0; } .coupon-list::after{
display: block; content:""; clear: both; } .coupon-item{
float: left; margin: 15px 8px; width: 180px; height: 100px; padding: 5px; background-color: #fa3030; cursor: pointer; } .coupon-list .active{
background-color: #fa9000; } .coupon-list .disable{
cursor: not-allowed; background-color: #fa6060; } .coupon-condition{
font-size: 12px; text-align: center; color: #fff; } .coupon-name{
color: #fff; font-size: 24px; text-align: center; } .coupon-time{
text-align: left; color: #fff; font-size: 12px; } .unselect{
margin-left: 0px; transform: rotate(-90deg); } .is_selected{
transform: rotate(-1turn)!important; } .coupon-item p{
margin: 0; padding: 0; } .cart{
margin-top: 80px; } .cart-info{
overflow: hidden; width: 1200px; margin: auto; } .cart-top{
font-size: 18px; color: #666; margin: 25px 0; font-weight: normal; } .cart-top span{
font-size: 12px; color: #d0d0d0; display: inline-block; } .cart-title{
background: #F7F7F7; height: 70px; } .calc{
margin-top: 25px; margin-bottom: 40px; } .calc .count{
text-align: right; margin-right: 10px; vertical-align: middle; } .calc .count span{
font-size: 36px; color: #333; } .calc .cart-pay{
margin-top: 5px; width: 110px; height: 38px; outline: none; border: none; color: #fff; line-height: 38px; background: #ffc210; border-radius: 4px; font-size: 16px; text-align: center; cursor: pointer; } .cart-item{
height: 120px; line-height: 120px; margin-bottom: 30px; } .course-info img{
width: 175px; height: 115px; margin-right: 35px; vertical-align: middle; } .alipay{
display: inline-block; height: 48px; } .alipay img{
height: 100%; width:auto; } .pay-text{
display: block; text-align: right; height: 100%; line-height: 100%; vertical-align: middle; margin-top: 20px; } </style>
分几部分来写: 结算页面商品展示 优惠券使用 积分使用 支付
index.js
讯享网... import Order from '@/components/Order' ... {
path: '/order/', component: Order }, ...
Cart.vue
... <span class="goto_pay"><router-link to="/order/">去结算</router-link></span> ...
一、订单的生成
讯享网cd luffyapi/apps python ../../manage.py startapp order
注册子应用,settings/dev.py,代码:
INSTALLED_APPS = [ # 子应用 。。。 'order', ]
order/urls.py
讯享网from django.urls import path,re_path from . import views urlpatterns = [ path('add_money/',views.OrderView.as_view(),) ]
总urls.py
... path(r'order/',include('order.urls')), ...
order/views.py
讯享网from django.shortcuts import render from rest_framework.generics import CreateAPIView # Create your views here. from . import models from .serializers import OrderModelSerializer from rest_framework.permissions import IsAuthenticated class OrderView(CreateAPIView): queryset = models.Order.objects.filter(is_deleted=False,is_show=True) serializer_class = OrderModelSerializer permission_classes = [IsAuthenticated, ]
cart/views.py
... # 结算页面数据 def show_pay_info(self,request): user_id = request.user.id conn = get_redis_connection('cart') select_list = conn.smembers('selected_cart_%s' % user_id) data = [] ret = conn.hgetall('cart_%s' % user_id) # dict {b'1': b'0', b'2': b'0'} # print(ret) total_price = 0 total_real_price = 0 for cid, eid in ret.items(): expire_id = int(eid.decode('utf-8')) if cid in select_list: course_id = int(cid.decode('utf-8')) course_obj = models.Course.objects.get(id=course_id) if expire_id > 0: expire_obj = models.CourseExpire.objects.get(id=expire_id) course_real_price = course_obj.real_price(expire_id) total_real_price += course_real_price data.append({
'course_id':course_obj.id, 'name':course_obj.name, 'course_img':contains.SERVER_ADDR + course_obj.course_img.url , 'real_price':course_real_price, 'expire_text':expire_obj.expire_text, }) else: course_real_price = course_obj.real_price(expire_id) total_real_price += course_real_price data.append({
'course_id': course_obj.id, 'name': course_obj.name, 'course_img': contains.SERVER_ADDR + course_obj.course_img.url, 'real_price': course_real_price, 'expire_text': '永久有效', }) return Response({
'data':data,'total_real_price':total_real_price}) ...
cart/urls.py
讯享网... path('expires/', views.AddCartView.as_view({
'put':'change_expire','get':'show_pay_info'})) ...
1.订单模型
订单模型分析:
订单模型: 优惠券ID,积分兑换数量,订单总价格,订单标题,订单支付时间,用户ID,订单状态,订单有效时间,订单号,支付方式, 订单详情模型: 商品ID,商品原价,商品实价,商品有效期,商品优惠方式 商品购买记录: 优惠券模型: 积分流水模型:
订单号
讯享网支付平台需要记录每一个商家的资金流水,所以需要我们这边提供一个足够复杂的流水号和支付平台保持一致。 所以订单号是支付平台那边强制要求在支付时提供给平台的。 订单号,因为第三方支付平台依靠这个订单号来识别当前订单是否已经支付过的,所以必须唯一。
from django.db import models # Create your models here. from lyapi.settings import contains from lyapi.utils.models import BaseModel from users.models import User from course.models import Course,CourseExpire class Order(BaseModel): """订单模型""" status_choices = ( (0, '未支付'), (1, '已支付'), (2, '已取消'), (3, '超时取消'), ) pay_choices = ( (0, '支付宝'), (1, '微信支付'), ) order_title = models.CharField(max_length=150,verbose_name="订单标题") total_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="订单总价", default=0) real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="实付金额", default=0) order_number = models.CharField(max_length=64,verbose_name="订单号") order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="订单状态") pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式") credit = models.IntegerField(default=0, verbose_name="使用的积分数量") coupon = models.IntegerField(null=True, verbose_name="用户优惠券ID") order_desc = models.TextField(max_length=500, verbose_name="订单描述",null=True,blank=True) pay_time = models.DateTimeField(null=True, verbose_name="支付时间") user = models.ForeignKey(User, related_name='user_orders', on_delete=models.DO_NOTHING,verbose_name="下单用户") class Meta: db_table="ly_order" verbose_name= "订单记录" verbose_name_plural= "订单记录" def __str__(self): return "%s,总价: %s,实付: %s" % (self.order_title, self.total_price, self.real_price) def order_detail_data(self): order_detail_objs = self.order_courses.all() data_list = [ ] for order_detail in order_detail_objs: expire_id = order_detail.expire if expire_id > 0: expire_obj = CourseExpire.objects.get(id=expire_id) expire_text = expire_obj.expire_text else: expire_text = '永久有效' order_dict = {
'course_img':contains.SERVER_ADDR + order_detail.course.course_img.url, 'course_name':order_detail.course.name, 'expire_text':expire_text, 'price':order_detail.price, 'real_price': self.real_price, 'discount_name':order_detail.discount_name, } data_list.append(order_dict) return data_list # ''' # order_dict:[ # {
# 订单id:'xxx', # 订单号:'xxxx', # course_list:[ # {
# 'course_id':1, # 'course_name':'flask框架' # }, # {
# 'course_id':1, # 'course_name':'flask框架' # }, # ] # }, # {
# 订单id:'xxx', # 订单号:'xxxx', # course_list:[ # {
# 'course_id':1, # 'course_name':'flask框架' # }, # {
# 'course_id':1, # 'course_name':'flask框架' # }, # ] # } # # # ] # # ''' class OrderDetail(BaseModel): """ 订单详情 """ order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, verbose_name="订单ID") course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.CASCADE, verbose_name="课程ID") expire = models.IntegerField(default='0', verbose_name="有效期周期",help_text="0表示永久有效") price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价") real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程实价") discount_name = models.CharField(max_length=120,default="",verbose_name="优惠类型") class Meta: db_table="ly_order_detail" verbose_name= "订单详情" verbose_name_plural= "订单详情" def __str__(self): return "%s" % (self.course.name)
数据迁移:
讯享网python manage.py makemigrations python manage.py migrate
把当前子应用注册到xadmin中,order/adminx.py
import xadmin from .models import Order class OrderModelAdmin(object): """订单模型管理类""" pass xadmin.site.register(Order, OrderModelAdmin) from .models import OrderDetail class OrderDetailModelAdmin(object): """订单详情模型管理类""" pass xadmin.site.register(OrderDetail, OrderDetailModelAdmin)
order/__init__.py,
讯享网default_app_config = "order.apps.OrderConfig"
order/apps.py
from django.apps import AppConfig class OrderConfig(AppConfig): name = 'order' verbose_name = '订单管理'
2.后端实现生成订单的api接口
通过postman 访问成功以后,有出现空单的生成情况,原因一次性生成多条数据记录或者一次性操作多个模型,都有可能产生中途报错的情况,所以我们需要在生成订单时保证多个数据操作的原子性。
讯享网事务: 在完成一个整体功能时,操作到了多个表数据,或者同一个表的多条记录,如果要保证这些SQL语句操作作为一个整体保存到数据库中,那么可以使用事务(transation), 事务具有4个特性,5个隔离等级 四个特性:一致性,原子性,隔离性,持久性 # 隔离性: 两个事务的隔离性,隔离性的修改可以通过数据库的配置文件进行修改 五个隔离级别: 串行隔离,可重复读,已提交读,未提交读,没有隔离级别 原子性(Atomicity) 一致性(Consistency) 隔离性(Isolation)[事务隔离级别->幻读,脏读] 持久性(Durability) 在mysql中有专门的SQl语句来完成事务的操作,事务操作一般有3个步骤: 设置事务开始 transation start 事务的处理[增删改] 设置事务的回滚或者提交 rollback / commit 在 django等web框架中,只要ORM模型,一般都会实现了事务操作封装 所以在django中我们可以直接使用ORM模型提供的事务操作方法即可完成事务的操作
django框架本身就提供了2种事务操作的用法。
django的事务操作方法主要通过 django.db.transation模块完成的。
启用事务用法1:
from django.db import transaction from rest_framework.views import APIView class OrderAPIView(APIView): @transaction.atomic # 开启事务,当方法执行完成以后,自动提交事务 def post(self,request): ....
启用事务用法2:
讯享网from django.db import transaction from rest_framework.views import APIView class OrderAPIView(APIView): def post(self,request): .... with transation.atomic(): # 开启事务,当with语句执行完成以后,自动提交事务 # 数据库操作
import datetime from rest_framework import serializers from . import models from django_redis import get_redis_connection from users.models import User from course.models import Course from course.models import CourseExpire from coupon.models import UserCoupon, Coupon from django.db import transaction from lyapi.settings import contains class OrderModelSerializer(serializers.ModelSerializer): class Meta: model = models.Order fields = ['id', 'order_number', 'pay_type', 'coupon', 'credit'] extra_kwargs = {
'id':{
'read_only':True}, 'order_number':{
'read_only':True}, 'pay_type':{
'write_only':True}, 'coupon':{
'write_only':True}, 'credit':{
'write_only':True}, } def validate(self, attrs): # 支付方式 pay_type = int(attrs.get('pay_type',0)) # if pay_type not in [i[0] for i in models.Order.pay_choices]: raise serializers.ValidationError('支付方式不对!') coupon_id = attrs.get('coupon', 0) if coupon_id > 0: try: user_conpon_obj = UserCoupon.objects.get(id=coupon_id) except: raise serializers.ValidationError('订单创建失败,优惠券id不对') # condition = user_conpon_obj.coupon.condition now = datetime.datetime.now().timestamp() start_time = user_conpon_obj.start_time.timestamp() end_time = user_conpon_obj.end_time.timestamp() if now < start_time or now > end_time: raise serializers.ValidationError('订单创建失败,优惠券不在使用范围内,滚犊子') credit = attrs.get('credit') user_credit = self.context['request'].user.credit if credit > user_credit: raise serializers.ValidationError('积分超上限了,别乱搞') return attrs def create(self, validated_data): try: # 生成订单号 [日期,用户id,自增数据] current_time = datetime.datetime.now() now = current_time.strftime('%Y%m%d%H%M%S') user_id = self.context['request'].user.id conn = get_redis_connection('cart') num = conn.incr('num') order_number = now + "%06d" % user_id + "%06d" % num total_price = 0 #总原价 total_real_price = 0 # 总真实价格 with transaction.atomic(): # 添加事务 sid = transaction.savepoint() #创建事务保存点 # 生成订单 order_obj = models.Order.objects.create({
'order_title': '31期订单', 'total_price': 0, 'real_price': 0, 'order_number': order_number, 'order_status': 0, 'pay_type': validated_data.get('pay_type', 0), 'credit': 0, 'coupon': 0, 'order_desc': '女朋友', 'pay_time': current_time, 'user_id': user_id, # 'user':user_obj, }) select_list = conn.smembers('selected_cart_%s' % user_id) ret = conn.hgetall('cart_%s' % user_id) # dict {b'1': b'0', b'2': b'0'} for cid, eid in ret.items(): expire_id = int(eid.decode('utf-8')) if cid in select_list: course_id = int(cid.decode('utf-8')) course_obj = Course.objects.get(id=course_id) # expire_text = '永久有效' if expire_id > 0: expire_text = CourseExpire.objects.get(id=expire_id).expire_text # 生成订单详情 models.OrderDetail.objects.create({
'order': order_obj, 'course': course_obj, 'expire': expire_id, 'price': course_obj.price, 'real_price': course_obj.real_price(expire_id), 'discount_name': course_obj.discount_name(), }) total_price += course_obj.price total_real_price += course_obj.real_price(expire_id) coupon_id = validated_data.get('coupon') # condition = 100 # if coupon_id > 0: try: user_conpon_obj = UserCoupon.objects.get(id=coupon_id) except: transaction.savepoint_rollback(sid) raise serializers.ValidationError('订单创建失败,优惠券id不对') condition = user_conpon_obj.coupon.condition if total_real_price < condition: transaction.savepoint_rollback(sid) raise serializers.ValidationError('订单创建失败,优惠券不满足使用条件') sale = user_conpon_obj.coupon.sale if sale[0] == '-': total_real_price -= float(sale[1:]) elif sale[0] == '*': total_real_price *= float(sale[1:]) order_obj.coupon = coupon_id # 积分判断 credit = float(validated_data.get('credit',0)) if credit > contains.CREDIT_MONEY * total_real_price: transaction.savepoint_rollback(sid) raise serializers.ValidationError('使用积分超过了上线,别高事情') # 积分计算 total_real_price -= credit / contains.CREDIT_MONEY order_obj.credit = credit order_obj.total_price = total_price order_obj.real_price = total_real_price order_obj.save() # 结算成功之后,再清除 # conn = get_redis_connection('cart') # conn.delete('selected_cart_%s' % user_id) # print('xxxxx') except Exception: raise models.Order.DoesNotExist # order_obj.order_number = order_number return order_obj
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/16299.html