props.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. from django.db import transaction
  2. from django.http import HttpResponse,JsonResponse
  3. from . import aidog_tools,aidog_request_tools,puppyStateUpdate #引入请求声纹API方法
  4. from datetime import datetime, timedelta
  5. from django.utils import timezone
  6. import random
  7. import json
  8. import time
  9. import os
  10. import jwt
  11. from .models import InventoryInfo
  12. os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aiDogProject.settings")
  13. import django
  14. django.setup()
  15. from aiDogApp.models import *
  16. from django.views.decorators.csrf import csrf_exempt
  17. import hashlib
  18. from django.core import serializers
  19. from django.core.cache import cache
  20. # 使用道具接口:客户端上传access_token,用户id、item_id,dog_list,date_time
  21. # 2025/03/12
  22. # author Feng
  23. # 使用道具后, 所有狗的数据全部更新
  24. @csrf_exempt
  25. def use_props(request):
  26. try:
  27. # 获取请求参数
  28. params = aidog_request_tools.handle_post_request_parameters(request,
  29. ["access_token", "user_id", "item_id", "dog_list", "date_time"])
  30. access_token, user_id, item_id, dog_list, interaction_type = params["access_token"], params["user_id"], params[
  31. "item_id"], params["dog_list"], params["date_time"]
  32. time_now = timezone.make_aware(datetime.now())
  33. # 处理dog_list参数 - 它可能是字符串形式的列表或JSON字符串
  34. # 验证旧的access_token是否有效
  35. try:
  36. decoded_token = jwt.decode(access_token, aidog_tools.SECRET_KEY, algorithms=['HS256'])
  37. user_id = decoded_token['user_id']
  38. # 取出数据库的uid:
  39. try:
  40. user = User.objects.get(uid=user_id)
  41. except User.DoesNotExist:
  42. return {"status": "error", "message": f"No user found with uid={user_id}"}
  43. except jwt.ExpiredSignatureError:
  44. # 旧access_token已过期,返回失败信息
  45. return JsonResponse({
  46. "status": "error",
  47. "code": 400,
  48. "message": "access_token expired, please refresh again"
  49. })
  50. # 确保dog_list是列表类型
  51. # if not isinstance(dog_list, list):
  52. # dog_list = [dog_list]
  53. # 处理dog_list参数 - 它可能是字符串形式的列表或JSON字符串
  54. if isinstance(dog_list, str):
  55. try:
  56. # 尝试解析为JSON
  57. dog_list = json.loads(dog_list)
  58. except json.JSONDecodeError:
  59. try:
  60. # 如果不是有效的JSON,尝试使用字面量评估
  61. # 这种方法可以处理Python列表字符串表示
  62. import ast
  63. dog_list = ast.literal_eval(dog_list)
  64. except (SyntaxError, ValueError):
  65. # 如果仍然失败,保持原样
  66. pass
  67. # 在Doginfo表中过滤出dog_list中存在的d_id对应的数据
  68. dog_info_list = Doginfo.objects.filter(owner_id=user_id, d_id__in=dog_list)
  69. # 检查是否有找到狗
  70. if not dog_info_list.exists():
  71. # 尝试不使用owner_id过滤,仅检查是否存在这些dog_id
  72. all_dogs = Doginfo.objects.filter(d_id__in=dog_list)
  73. if all_dogs.exists():
  74. # 狗存在,但不属于这个用户
  75. wrong_owner_dogs = [dog.d_id for dog in all_dogs]
  76. return JsonResponse({
  77. "status": "error",
  78. "message": f"Dogs found but don't belong to user {user_id}: {wrong_owner_dogs}"
  79. })
  80. else:
  81. # 狗完全不存在
  82. return JsonResponse({
  83. "status": "error",
  84. "message": f"No dogs found with the provided IDs: {dog_list}"
  85. })
  86. # 获取用户道具库存
  87. try:
  88. user_inventory = UserInventory.objects.get(owner_id=user_id, item_id=item_id)
  89. # 检查用户是否有足够的道具数量
  90. # if user_inventory.quantity < len(dog_info_list):
  91. if user_inventory.quantity < 1:
  92. return JsonResponse({
  93. "status": "error",
  94. "message": f"Insufficient item quantity. Required: {1}, Available: {user_inventory.quantity}"
  95. })
  96. except UserInventory.DoesNotExist:
  97. return JsonResponse({
  98. "status": "error",
  99. "message": f"User doesn't have this item in inventory: {item_id}"
  100. })
  101. # 更新每只狗的状态
  102. # 获取用户所有的狗
  103. dog_all_list = Doginfo.objects.filter(owner_id=user_id)
  104. dogs = []
  105. successful_updates = 0
  106. # 提取需要更新的狗的ID列表
  107. dog_info_d_ids = [dog.d_id for dog in dog_info_list]
  108. # 遍历所有狗数据
  109. for dog in dog_all_list:
  110. dog_id = dog.d_id
  111. # 获取狗的信息并转换为字典
  112. dog_serialized = serializers.serialize('json', [dog])
  113. dog_data = json.loads(dog_serialized)[0]['fields']
  114. # 如果当前狗在需要更新的列表中,调用更新接口
  115. if dog_id in dog_info_d_ids:
  116. update_response = puppyStateUpdate.props_gain_state(user_id, dog_id, item_id, time_now)
  117. if update_response["status"] == "error":
  118. # 如果更新某只狗时出错,记录错误但继续处理其他狗
  119. pass
  120. else:
  121. successful_updates += 1
  122. # 获取狗的相关信息(无论是否更新,都获取最新状态)
  123. new_dog_status = aidog_tools.model_to_dict_without_id(DogStatus.objects.filter(d_id=dog_id).first())
  124. new_dog_training = aidog_tools.model_to_dict_without_id(DogTrainingNew.objects.filter(d_id=dog_id).first())
  125. new_dog_body_attributes = aidog_tools.model_to_dict_without_id(
  126. DogBodyAttributes.objects.filter(d_id=dog_id).first())
  127. new_dog_personality_relationship = aidog_tools.model_to_dict_without_id(
  128. DogPersonalityRelationship.objects.filter(d_id=dog_id).first())
  129. # 合并所有相关信息
  130. dog_info_combined = {
  131. **dog_data,
  132. **new_dog_status,
  133. **new_dog_training,
  134. **new_dog_body_attributes,
  135. **new_dog_personality_relationship,
  136. }
  137. # 只保留字段而不是数据库内部的元数据(如 '_state')
  138. dog_info_clean = {key: value for key, value in dog_info_combined.items() if not key.startswith('_')}
  139. dogs.append(dog_info_clean)
  140. # 更新道具数量 (只扣除成功更新的狗的数量)
  141. if successful_updates > 0 :
  142. # user_inventory.quantity -= successful_updates #---todo==========按狗的数量扣除多个道具
  143. if not item_id.startswith('toy_'):
  144. user_inventory.quantity -= 1 #---todo==========无论几只狗每次只扣除一个道具
  145. # if user_inventory.quantity <= 0:
  146. # 如果道具数量为0或负数,删除库存记录
  147. # user_inventory.delete()
  148. # else:
  149. user_inventory.save()
  150. # 获取用户所有道具库存
  151. user_inventory_all = UserInventory.objects.filter(owner_id=user_id)
  152. props_dict = {
  153. "food": {},
  154. "toy": {},
  155. "other": {}
  156. }
  157. for prop in user_inventory_all:
  158. prop_serialized = serializers.serialize('json', [prop])
  159. prop_data = json.loads(prop_serialized)[0]['fields']
  160. if prop_data['quantity'] > 0:
  161. # Determine the category based on item_id prefix
  162. if prop_data['item_id'].startswith('food_') or prop_data['item_id'].startswith('water_'):
  163. category = 'food'
  164. elif prop_data['item_id'].startswith('toy_'):
  165. category = 'toy'
  166. else:
  167. category = 'other'
  168. # Add to the corresponding category dictionary
  169. props_dict[category][prop_data['item_id']] = prop_data['quantity']
  170. # 处理isRegUser的布尔值转换
  171. isRegUser = True if getattr(user, 'isRegUser', 0) == 1 else False
  172. # 成功返回结果
  173. res = {
  174. "status": "success",
  175. "message": f"item:{str(item_id)} is used",
  176. "user_info": {
  177. "user_name": user.user_name,
  178. "coin": user.coin,
  179. "isRegUser": isRegUser,
  180. "mobile": user.mobile,
  181. "email": user.email,
  182. "level": user.level,
  183. "uuid": user.uuid,
  184. "registration_time": user.registration_time
  185. },
  186. "status": "success",
  187. "dogs":dogs,
  188. "props": props_dict,
  189. }
  190. return JsonResponse(res)
  191. except Exception as e:
  192. return aidog_tools.handle_error(str(e))
  193. # 购买道具接口:客户端上传access_token,用户id、item_id,qty(默认为1),date_time
  194. # 2025/05/23
  195. # author Feng
  196. # 返回购物成功信息,用户道具表,用户信息表
  197. @csrf_exempt
  198. def purchase_props(request):
  199. # 记录请求时间
  200. current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  201. try:
  202. # 获取请求参数
  203. params = aidog_request_tools.handle_post_request_parameters(
  204. request,
  205. ["access_token", "user_id", "item_id", "qty"]
  206. )
  207. date_time = request.POST.get("date_time", '')
  208. access_token = params["access_token"]
  209. item_id = params["item_id"]
  210. qty = int(params.get("qty", 1)) # 默认数量为1,确保是整数
  211. # 验证数量参数
  212. if qty <= 0:
  213. return JsonResponse({
  214. "status": "error",
  215. "code": 400,
  216. "date": current_time,
  217. "message": "Quantity must be greater than 0"
  218. })
  219. # 验证数量上限(防止恶意购买)
  220. if qty > 999:
  221. return JsonResponse({
  222. "status": "error",
  223. "code": 400,
  224. "date": current_time,
  225. "message": "Quantity cannot exceed 999"
  226. })
  227. # 验证访问令牌
  228. token_validation = aidog_tools.validate_access_token(access_token)
  229. if isinstance(token_validation, JsonResponse):
  230. return token_validation
  231. user_id = token_validation['user_id']
  232. # 验证道具信息
  233. try:
  234. # 先从缓存获取
  235. # cache_key = f"item_info_{item_id}"
  236. # item_info = cache.get(cache_key)
  237. #
  238. # if item_info is None:
  239. # # 缓存未命中,从数据库查询
  240. # item_info = InventoryInfo.objects.get(item_id=item_id)
  241. # # 缓存5分钟
  242. # cache.set(cache_key, item_info, 300)
  243. item_info = InventoryInfo.objects.get(item_id=item_id)
  244. # 检查道具是否可购买
  245. if not item_info.is_active:
  246. return JsonResponse({
  247. "status": "error",
  248. "code": 400,
  249. "date": current_time,
  250. "message": f"Item {item_id} is not available for purchase"
  251. })
  252. except InventoryInfo.DoesNotExist:
  253. return JsonResponse({
  254. "status": "error",
  255. "code": 404,
  256. "date": current_time,
  257. "message": f"Item not found: {item_id}"
  258. })
  259. # 使用数据库事务确保数据一致性
  260. with transaction.atomic():
  261. # 获取用户信息
  262. try:
  263. user = User.objects.select_for_update().get(uid=user_id)
  264. except User.DoesNotExist:
  265. return JsonResponse({
  266. "status": "error",
  267. "code": 404,
  268. "date": current_time,
  269. "message": f"User not found with uid={user_id}"
  270. })
  271. # 计算总价格
  272. total_cost = item_info.price * qty
  273. # 检查用户余额
  274. if user.coin < total_cost:
  275. return JsonResponse({
  276. "status": "error",
  277. "code": 400,
  278. "date": current_time,
  279. "message": f"Insufficient coins. Need {total_cost} coins, but only have {user.coin} coins"
  280. })
  281. # 获取或创建用户道具库存
  282. user_inventory, created = UserInventory.objects.select_for_update().get_or_create(
  283. owner_id=user.uid,
  284. item_id=item_id,
  285. defaults={'quantity': 0}
  286. )
  287. # 根据道具类型检查库存上限
  288. current_quantity = user_inventory.quantity
  289. # 定义需要检查库存上限的道具
  290. limit_999_items = ['food_00001', 'food_00002', 'food_00003',
  291. 'water_00001', 'water_00002', 'other_00001']
  292. limit_1_items = ['toy_00001']
  293. if item_id in limit_999_items:
  294. # 检查是否超过999的上限
  295. if current_quantity >= 999:
  296. return JsonResponse({
  297. "status": "error",
  298. "code": 400,
  299. "date": current_time,
  300. "message": f"Cannot purchase {item_id}. Inventory limit of 999 has been reached (current: {current_quantity})"
  301. })
  302. # 检查购买后是否会超过上限
  303. if current_quantity + qty > 999:
  304. max_can_buy = 999 - current_quantity
  305. return JsonResponse({
  306. "status": "error",
  307. "code": 400,
  308. "date": current_time,
  309. "message": f"Cannot purchase {qty} x {item_id}. Would exceed inventory limit of 999. You can only purchase {max_can_buy} more items"
  310. })
  311. elif item_id in limit_1_items:
  312. # 检查是否超过1的上限
  313. if current_quantity >= 1:
  314. return JsonResponse({
  315. "status": "error",
  316. "code": 400,
  317. "date": current_time,
  318. "message": f"Cannot purchase {item_id}. Inventory limit of 1 has been reached (current: {current_quantity})"
  319. })
  320. # 检查购买数量是否会超过上限
  321. if qty > 1:
  322. return JsonResponse({
  323. "status": "error",
  324. "code": 400,
  325. "date": current_time,
  326. "message": f"Cannot purchase {qty} x {item_id}. Maximum allowed quantity is 1"
  327. })
  328. # 扣除金币并增加道具数量
  329. user.coin -= total_cost
  330. user_inventory.quantity += qty
  331. # 保存更改
  332. user.save(update_fields=['coin'])
  333. user_inventory.save(update_fields=['quantity'])
  334. # 获取用户所有道具库存
  335. props_dict = _get_user_props_dict(user_id)
  336. # 构建用户信息
  337. user_info = aidog_tools._build_user_info(user)
  338. # 成功返回结果
  339. res = {
  340. "status": "success",
  341. "message": f"Successfully purchased {qty} x {item_id}",
  342. "user_info": user_info,
  343. "props": props_dict,
  344. }
  345. return JsonResponse(res)
  346. except Exception as e:
  347. return aidog_tools.handle_error(str(e))
  348. # 获取用户道具字典
  349. def _get_user_props_dict(user_id):
  350. # 先尝试从缓存获取
  351. # cache_key = f"user_props_{user_id}"
  352. # props_dict = cache.get(cache_key)
  353. #
  354. # if props_dict is not None:
  355. # return props_dict
  356. #
  357. # # 缓存未命中,查询数据库
  358. # 使用JOIN查询获取道具信息和库存信息
  359. user_inventory_data = UserInventory.objects.filter(
  360. owner_id=user_id,
  361. quantity__gt=0
  362. ).select_related().values(
  363. 'item_id', 'quantity'
  364. )
  365. # 批量获取道具信息
  366. item_ids = [item['item_id'] for item in user_inventory_data]
  367. item_infos = {
  368. item.item_id: item.item_type
  369. for item in InventoryInfo.objects.filter(item_id__in=item_ids)
  370. }
  371. props_dict = {
  372. "food": {},
  373. "toy": {},
  374. "other": {}
  375. }
  376. for inventory_item in user_inventory_data:
  377. item_id = inventory_item['item_id']
  378. quantity = inventory_item['quantity']
  379. # 从数据库获取道具类型
  380. item_type = item_infos.get(item_id, 'other')
  381. props_dict[item_type][item_id] = quantity
  382. # 缓存5分钟
  383. # cache.set(cache_key, props_dict, 300)
  384. return props_dict