mahjong_group/server/CoreCms.Net.Web.WebApi/Controllers/SQController.cs
2026-01-02 15:29:46 +08:00

2200 lines
74 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AutoMapper;
using CoreCms.Net.Auth.HttpContextUser;
using CoreCms.Net.IRepository.UnitOfWork;
using CoreCms.Net.IServices;
using CoreCms.Net.Model.Entities;
using CoreCms.Net.Model.Entities.Expression;
using CoreCms.Net.Model.ViewModels.Basics;
using CoreCms.Net.Model.ViewModels.SQ;
using CoreCms.Net.Model.ViewModels.UI;
using CoreCms.Net.Services;
using CoreCms.Net.Services.SQ;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NPOI.SS.Formula.Functions;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CoreCms.Net.Utility.Extensions;
using ToolGood.Words.internals;
using NPOI.SS.Formula.PTG;
using NPOI.OpenXmlFormats.Dml;
using Humanizer;
using Newtonsoft.Json;
using CoreCms.Net.Configuration;
using CoreCms.Net.Utility.Helper;
namespace CoreCms.Net.Web.WebApi.Controllers;
/// <summary>
/// 预约接口
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
public class SQController : ControllerBase
{
private readonly IWebHostEnvironment _webHostEnvironment;
private readonly ISQReservationsServices _SQReservationsServices;
private readonly ISQRoomsServices _SQRoomsServices;
private readonly ISysDictionaryDataServices _sysDictionaryDataServices;
private readonly ISysDictionaryServices _sysDictionaryServices;
private readonly ISQReservationParticipantsServices _SQReservationParticipantsServices;
private readonly IMapper _mapper;
private readonly ICoreCmsUserServices _userServices;
private readonly IHttpContextUser _user;
private readonly SqlSugarScope _dbBase;
private readonly ICoreCmsUserBlacklistServices _coreCmsUserBlacklistServices;
private readonly ISQReservationReputationServices _sQReservationReputationServices;
private readonly ISQReservationEvaluateServices _sQReservationEvaluateServices;
private readonly ISQRoomUnavailableTimesServices _sQRoomUnavailableTimesServices;
private readonly ISQRoomPricingServices _sQRoomPricingServices;
private readonly ISQMessageServices _sQMessageServices;
private readonly ISQEarningsServices _sQEarningsServices;
private readonly ICoreCmsSettingServices _settingServices;
// 营业时间常量配置
private const string BUSINESS_OPEN_TIME = "09:00";
private const string BUSINESS_CLOSE_TIME = "23:00";
/// <summary>
/// 构造函数
///</summary>
public SQController(IWebHostEnvironment webHostEnvironment
, ISQReservationsServices SQReservationsServices
, ISQRoomsServices SQRoomsServices
, ISysDictionaryServices sysDictionaryServices
, ISysDictionaryDataServices sysDictionaryDataServices
, ISQReservationParticipantsServices sQReservationParticipantsServices
, IMapper mapper
, ICoreCmsUserServices userServices
, IHttpContextUser user
, IUnitOfWork unitOfWork
, ICoreCmsUserBlacklistServices coreCmsUserBlacklistServices
, ISQReservationEvaluateServices sQReservationEvaluateServices
, ISQReservationReputationServices sQReservationReputationServices
, ISQRoomUnavailableTimesServices sQRoomUnavailableTimesServices
, ISQRoomPricingServices sQRoomPricingServices
, ISQMessageServices sQMessageServices
, ISQEarningsServices sQEarningsServices
, ICoreCmsSettingServices settingServices
)
{
_webHostEnvironment = webHostEnvironment;
_SQReservationsServices = SQReservationsServices;
_SQRoomsServices = SQRoomsServices;
_sysDictionaryServices = sysDictionaryServices;
_sysDictionaryDataServices = sysDictionaryDataServices;
_SQReservationParticipantsServices = sQReservationParticipantsServices;
_mapper = mapper;
_userServices = userServices;
_user = user;
_dbBase = unitOfWork.GetDbClient();
_coreCmsUserBlacklistServices = coreCmsUserBlacklistServices;
_sQReservationEvaluateServices = sQReservationEvaluateServices;
_sQReservationReputationServices = sQReservationReputationServices;
_sQRoomUnavailableTimesServices = sQRoomUnavailableTimesServices;
_sQRoomPricingServices = sQRoomPricingServices;
_sQMessageServices = sQMessageServices;
_sQEarningsServices = sQEarningsServices;
_settingServices = settingServices;
}
/// <summary>
/// 我的预约记录
/// </summary>
/// <param name="type">0 参与者1发起者</param>
/// <param name="index">起始页</param>
/// <param name="size">页大小</param>
/// <returns></returns>
[HttpGet]
[Authorize]
public async Task<WebApiDto> GetMyReservation([FromQuery] int type = 0, [FromQuery] int index = 1, [FromQuery] int size = 10)
{
var userId = _user.ID;
var list = await _dbBase.Ado.SqlQueryAsync<SQReservationsMyDto>($"select r.*,p.id pid, p.status pstatus,p.join_time, p.quit_time ,p.is_refund from SQReservationParticipants p inner join SQReservations r on p.reservation_id=r.id where p.user_id={userId} and p.role={type} order by p.id desc OFFSET {(index - 1) * size} ROWS FETCH NEXT {size} ROWS ONLY ");
if (list != null && list.Count > 0)
{
var roomList = await _SQRoomsServices.GetRoomList();
// 转为基类列表
var baseList = list.Cast<SQReservationsBaseDto>().ToList();
await baseList.LoadSQReservationParticipantsApiDto(userId, _dbBase, _coreCmsUserBlacklistServices, roomList, _mapper);
}
return new WebApiDto()
{
Data = list,
Code = 0,
};
}
/// <summary>
/// 获取正在进行的预约
/// </summary>
/// <returns></returns>
[HttpGet]
[Authorize]
public async Task<WebApiDto> GetMyUseReservation()
{
var userId = _user.ID;
var list = await _dbBase.Ado.SqlQueryAsync<SQReservationsApiDto>($@"
SELECT r.*, p.role AS Role, p.is_arrive,
CASE WHEN r.status = 1 THEN 0 WHEN r.status = 2 THEN 1 WHEN r.status = 0 THEN 2 WHEN r.status = 3 THEN 3 END AS orderid
FROM SQReservations r
LEFT JOIN SQReservationParticipants p ON r.id = p.reservation_id
WHERE r.status < 4
AND p.user_id = {userId}
AND p.status = 0
AND DATEADD(day,2, end_time) > GETDATE()
AND NOT EXISTS (
SELECT 1 FROM SQReservationEvaluate e
WHERE e.reservation_id = r.id AND e.user_id = {userId}
)
AND (
r.status < 3
OR (
r.status = 3
AND (SELECT COUNT(1) FROM SQReservationParticipants p2
WHERE p2.reservation_id = r.id
AND p2.status = 0
AND p2.is_arrive = 1
AND p2.user_id != {userId}) > 0
)
)
ORDER BY orderid asc, r.start_time Desc
");
if (list != null && list.Count > 0)
{
var roomList = await _SQRoomsServices.GetRoomList();
// 转为基类列表
var baseList = list.Cast<SQReservationsBaseDto>().ToList();
await baseList.LoadSQReservationParticipantsApiDto(userId, _dbBase, _coreCmsUserBlacklistServices, roomList, _mapper);
}
return new WebApiDto()
{
Data = list,
Code = 0,
};
}
/// <summary>
/// 首页预约列表
/// </summary>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <returns></returns>
[HttpGet]
public async Task<WebApiDto> GetReservationList([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20)
{
var userId = _user.ID;
var now = DateTime.Now;
var roomList = await _SQRoomsServices.GetRoomList();
var where = PredicateBuilder.True<SQReservations>();
where = where.And(it => it.end_time > now);
where = where.And(it => it.status < 3);
//
List<int> userBlacklist = new List<int>();
if (userId > 0)
{
//_sQRoomUnavailableTimesServices.get
userBlacklist = await _coreCmsUserBlacklistServices.GetUserBlacklists(userId);
if (userBlacklist.Count > 0)
{
string sqlWhere = string.Join(",", userBlacklist);
var participants = await _dbBase.Ado.SqlQueryAsync<int>($"select sq.id from SQReservations sq left join SQReservationParticipants p on sq.id=p.reservation_id and p.role=1 where sq.status<3 and p.user_id in ({sqlWhere})");
if (participants.Count > 0)
{
where = where.And(it => !participants.Contains(it.id));
}
}
}
var list = await _SQReservationsServices.QueryPageAsync(where, it => it.start_time, OrderByType.Asc, pageIndex, pageSize, true);
var pageList = _mapper.Map<List<SQReservationsApiDto>>(list);
if (pageList != null && pageList.Count > 0)
{
// 转为基类列表
var baseList = pageList.Cast<SQReservationsBaseDto>().ToList();
await baseList.LoadSQReservationParticipantsApiDto(userId, _dbBase, _coreCmsUserBlacklistServices, roomList, _mapper);
}
return new WebApiDto()
{
Data = pageList,
Code = 0,
Msg = "ok",
};
}
/// <summary>
/// 根据预约ID获取详情结构同首页预约列表单项
/// </summary>
/// <param name="id">预约ID</param>
/// <returns></returns>
[HttpGet]
public async Task<WebApiDto> GetReservationDetail([FromQuery] int id)
{
var userId = _user.ID;
var model = await _SQReservationsServices.QueryByIdAsync(id);
if (model == null)
{
return new WebApiDto
{
Code = -1,
Msg = "预约不存在",
};
}
var dto = _mapper.Map<SQReservationsApiDto>(model);
var roomList = await _SQRoomsServices.GetRoomList();
var baseList = new List<SQReservationsBaseDto> { dto };
await baseList.LoadSQReservationParticipantsApiDto(userId, _dbBase, _coreCmsUserBlacklistServices, roomList, _mapper);
return new WebApiDto
{
Data = dto,
Code = 0,
Msg = "ok",
};
}
/// <summary>
/// 获取预约评价
/// </summary>
/// <param name="reId"></param>
/// <returns></returns>
[HttpGet]
[Authorize]
public async Task<WebApiDto> GetEvaluateServices([FromQuery] int reId)
{
var userId = _user.ID;
var list = await _sQReservationEvaluateServices.QueryListByClauseAsync(it => it.reservation_id == reId && it.user_id == userId);
var participants = await _dbBase.Ado.SqlQueryAsync<SQReservationParticipantsApiEavDto>($"select p.id,p.reservation_id,p.status,p.join_time,p.user_id, p.role,u.nickName UserName,u.avatarImage AvatarImage,ISNULL(u.play_level,4) play_level,ISNULL(u.skills_level,4) skills_level from SQReservationParticipants p inner join CoreCmsUser u on p.user_id=u.id where p.status=0 and p.reservation_id ={reId} and p.is_arrive=1 ");
// 过滤掉当前用户自己,只保留可评价的其他用户
var evaluableParticipants = participants?.Where(p => p.user_id != userId).ToList();
if (evaluableParticipants == null || evaluableParticipants.Count == 0)
{
return new WebApiDto()
{
Code = 0,
Data = null,
Msg = "暂无可评价的牌友"
};
}
var userBlack = await _coreCmsUserBlacklistServices.GetUserBlacklists(userId);
List<object> obj = new List<object>();
foreach (var item in evaluableParticipants)
{
var is_evaluate = false;
decimal play_level = 0;
decimal skills_level = 0;
decimal user_play_level = 4;
decimal user_skills_level = 4;
DateTime? now = null;
var userBlackStatus = false;
if (list.Count > 0)
{
var e = list.Find(it => it.to_user_id == item.user_id);
if (e != null)
{
is_evaluate = true;
play_level = e.play_level; // 这是用户给出的评分
skills_level = e.skills_level; // 这是用户给出的评分
now = e.created_at;
// 修复:显示被评价用户的当前平均分,而不是评价记录中的分数
user_play_level = item.play_level; // 使用用户表中的当前平均分
user_skills_level = item.skills_level; // 使用用户表中的当前平均分
}
}
if (userBlack.Count > 0)
{
if (userBlack.Any(it => it == item.user_id))
{
userBlackStatus = true;
}
}
var o = new
{
item.UserName,
item.user_id,
item.AvatarImage,
userBlackStatus,
user_play_level,
user_skills_level,
is_evaluate,
play_level,
skills_level,
evaluate_at = now
};
obj.Add(o);
}
return new WebApiDto()
{
Code = 0,
Data = obj
};
}
/// <summary>
/// 评价用户
/// </summary>
/// <param name="evaluate"></param>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<WebApiDto> AddEvaluateServices([FromBody] SQReservationEvaluateDto evaluate)
{
var userId = _user.ID;
var reId = evaluate.reservation_id;
var list = await _sQReservationEvaluateServices.QueryListByClauseAsync(it => it.reservation_id == reId && it.user_id == userId);
var participants = await _dbBase.Ado.SqlQueryAsync<SQReservationParticipantsApiEavDto>($"select p.id,p.reservation_id,p.status,p.join_time,p.user_id, p.role,u.nickName UserName,u.avatarImage AvatarImage,ISNULL(u.play_level,4) play_level,ISNULL(u.skills_level,4) skills_level from SQReservationParticipants p inner join CoreCmsUser u on p.user_id=u.id where p.status=0 and p.reservation_id ={reId} and p.is_arrive=1 ");
if (participants == null)
{
return new WebApiDto()
{
Code = 0,
Data = null
};
}
List<object> obj = new List<object>();
var p = participants.Find(it => it.user_id == evaluate.to_user_id);
if (p == null)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "未找到对局记录"
};
}
//_sQReservationReputationServices.
SQReservationEvaluate sQReservationEvaluate = new SQReservationEvaluate()
{
created_at = DateTime.Now,
play_level = evaluate.play_level,
reservation_id = evaluate.reservation_id,
role = p.role,
skills_level = evaluate.skills_level,
to_user_id = evaluate.to_user_id,
user_id = userId,
};
await _sQReservationEvaluateServices.InsertAsync(sQReservationEvaluate);
var play_level = _dbBase.Ado.SqlQuerySingle<SQReservationEvaluateFDto>($"select ISNULL(sum(play_level),0) level,count(1) rs_count from SQReservationEvaluate where to_user_id={evaluate.to_user_id} ");
var skills_level = _dbBase.Ado.SqlQuerySingle<SQReservationEvaluateFDto>($"select ISNULL(sum(skills_level),0) level,count(1) rs_count from SQReservationEvaluate where to_user_id={evaluate.to_user_id} ");
//先加4
play_level.level += 4;
if (play_level.rs_count > 0)
{
if (play_level.rs_count == 1)
{
play_level.rs_count++;
}
play_level.level = play_level.level / play_level.rs_count;
}
//先加4
skills_level.level += 4;
if (skills_level.rs_count > 0)
{
if (skills_level.rs_count == 1)
{
skills_level.rs_count++;
}
skills_level.level = skills_level.level / skills_level.rs_count;
}
if (play_level.rs_count > 0 || skills_level.rs_count > 0)
{
await _userServices.UpdateAsync(it => new CoreCmsUser
{
play_level = play_level.level,
skills_level = skills_level.level
}, it => it.id == evaluate.to_user_id);
}
return new WebApiDto()
{
Code = 0,
Data = null,
Msg = "评价成功"
};
}
/// <summary>
/// 获取我的声誉记录
/// </summary>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <returns></returns>
[HttpGet]
[Authorize]
public async Task<WebApiDto> GetReputationByUser([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20)
{
var list = await _sQReservationReputationServices.QueryPageAsync(it => it.user_id == _user.ID, it => it.created_at, OrderByType.Desc, pageIndex, pageSize, true);
var o = new List<object>();
foreach (var item in list)
{
var c = new
{
item.created_at,
reputation_value = item.reputation_value.ToString("#.#"),
title = item.remark
};
o.Add(c);
}
return new WebApiDto()
{
Code = 0,
Data = o,
Msg = "ok",
};
}
/// <summary>
/// 获取评价我的记录
/// </summary>
/// <param name="pageIndex">页码</param>
/// <param name="pageSize">每页数量</param>
/// <returns></returns>
[HttpGet]
[Authorize]
public async Task<WebApiDto> GetEvaluateToMe([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20)
{
var userId = _user.ID;
var list = await _sQReservationEvaluateServices.QueryPageAsync(
it => it.to_user_id == userId,
it => it.created_at,
OrderByType.Desc,
pageIndex,
pageSize,
true
);
return new WebApiDto()
{
Code = 0,
Data = list,
Msg = "ok"
};
}
/// <summary>
/// 获取可预约的房间列表
/// </summary>
/// <param name="startTime">开始时间 时间戳</param>
/// <param name="endTime">结束时间 时间戳</param>
/// <returns></returns>
[HttpGet]
public async Task<WebApiDto> GetReservationRoomList([FromQuery] long startTime, [FromQuery] long endTime)
{
if (startTime == 0 || endTime == 0)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "参数错误"
};
}
// 时间戳转DateTime
var start = DateTimeOffset.FromUnixTimeSeconds(startTime).DateTime;
var end = DateTimeOffset.FromUnixTimeSeconds(endTime).DateTime;
// 1. 查询所有可用房间
var allRooms = await _SQRoomsServices.GetRoomList();
// 2. 查询有不可用时间段冲突的房间
var unavailableRoomIds = (await _sQRoomUnavailableTimesServices.QueryListByClauseAsync(
t => t.start_time < end && t.end_time > start
)).Select(t => t.room_id).Distinct().ToList();
// 3. 查询已被预约的房间(未取消的预约,时间有重叠)
var reservedRoomIds = (await _SQReservationsServices.QueryListByClauseAsync(
r => r.status < 3 && r.start_time < end && r.end_time > start
)).Select(r => r.room_id).Distinct().ToList();
// 4. 可预约房间 = 所有可用房间 - 不可用房间 - 已预约房间
var availableRooms = allRooms
.Where(r => r.status == true && !unavailableRoomIds.Contains(r.id) && !reservedRoomIds.Contains(r.id))
.OrderBy(r => r.name)
.Select(it => new { name = it.name + $" {it.price_per_hour.ToString("#.##")}/小时", it.id, it.capacity })
.ToList();
return new WebApiDto()
{
Code = 0,
Data = availableRooms,
Msg = "ok"
};
}
/// <summary>
/// 用户预约接口
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<WebApiDto> AddSQReservation([FromBody] SQReservationsAddDto dto)
{
var userId = _user.ID;
var start_time = DateTimeOffset.FromUnixTimeSeconds(dto.start_time).AddHours(8).DateTime;
var end_time = DateTimeOffset.FromUnixTimeSeconds(dto.end_time).AddHours(8).DateTime;
if (dto?.LatestDateTime > 0)
{
dto.latest_arrival_time = DateTimeOffset.FromUnixTimeSeconds(dto.LatestDateTime ?? 0).AddHours(8).DateTime;
}
// 1. 参数校验
if (dto == null || dto.room_id <= 0 || start_time >= end_time)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "参数错误"
};
}
// 1.1 验证预约时长最短1小时最长12小时
var duration = (end_time - start_time).TotalHours;
if (duration < 1)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "预约时长不能少于1小时"
};
}
if (duration > 12)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "预约时长不能超过12小时"
};
}
// 2. 检查房间是否存在
var room = await _SQRoomsServices.QueryByClauseAsync(r => r.id == dto.room_id, r => r.id, OrderByType.Asc);
if (room == null)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "房间不存在"
};
}
// 3. 检查房间是否可预约(不可用时间段)
var hasUnavailable = await _sQRoomUnavailableTimesServices.QueryListByClauseAsync(
t => t.room_id == dto.room_id && t.start_time < end_time && t.end_time > start_time,
t => t.id, OrderByType.Asc);
if (hasUnavailable != null && hasUnavailable.Count > 0)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "该时间段房间不可预约"
};
}
// 2.1 校验用户是否有其它预约时间冲突
// 2.1 校验用户是否有其它预约时间冲突(只查未开始或正在进行中的预约)
var now = DateTime.Now;
var hasConflict = await _SQReservationParticipantsServices.QueryMuchAsync<SQReservationParticipants, SQReservations, int>(
(p, r) => new object[]
{
JoinType.Inner, p.reservation_id == r.id
},
(p, r) => r.id,
(p, r) =>
p.user_id == userId &&
p.status == 0 && // 只查未退出
r.status < 3 && // 只查未取消/未结束
r.end_time > now && // 只查未结束
r.start_time < end_time && r.end_time > start_time // 时间有重叠
);
if (hasConflict != null && hasConflict.Count > 0)
{
return new WebApiDto
{
Code = 402,
Data = null,
Msg = "您有其它预约时间冲突,无法创建该预约!"
};
}
// 4. 检查房间是否已被预约(未取消的预约,时间有重叠)
var hasReserved = await _SQReservationsServices.QueryListByClauseAsync(
r => r.room_id == dto.room_id && r.status < 2 && r.start_time < end_time && r.end_time > start_time,
r => r.id, OrderByType.Asc);
if (hasReserved != null && hasReserved.Count > 0)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "该时间段房间已被预约"
};
}
string paymentId = "";
if (dto.deposit_fee > 0)
{
var imdata = JsonConvert.DeserializeObject<Dictionary<string, object>>(dto.important_data);
if (imdata == null || !imdata.ContainsKey("paymentId") || string.IsNullOrEmpty(imdata["paymentId"].ToString()))
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "押金支付信息错误!"
};
}
//订单编号
paymentId = imdata["paymentId"].ToString();
if (string.IsNullOrEmpty(paymentId))
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "生成订单失败!"
};
}
}
// 5. 创建预约记录
var reservation = new SQReservations
{
room_id = dto.room_id,
room_name = room.name,
start_time = start_time,
end_time = end_time,
game_type = dto.game_type,
created_at = DateTime.Now,
updated_at = DateTime.Now,
credit_limit = dto.credit_limit,
player_count = dto.player_count,
latest_arrival_time = dto.latest_arrival_time,
deposit_fee = dto.deposit_fee,
duration_minutes = (int)(end_time - start_time).TotalMinutes,
extra_info = dto.extra_info,
game_rule = dto.game_rule,
gender_limit = dto.gender_limit,
is_smoking = dto.is_smoking,
max_age = dto.max_age,
min_age = dto.min_age,
remarks = "",
status = 0,
title = dto.title,
is_solo_mode = dto.player_count == 1,
// 其他字段可根据需要补充
};
var reservationId = await _dbBase.Insertable(reservation).ExecuteReturnIdentityAsync();
if (reservationId <= 0)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "预约失败"
};
}
// 6. 创建预约参与人记录发起者role=1
var participant = new SQReservationParticipants
{
reservation_id = reservationId,
user_id = userId,
join_time = DateTime.Now,
role = 1,
status = 0,
is_refund = dto.deposit_fee > 0 ? 1 : 0,
important_data = dto.important_data,
paymentId = paymentId,
};
await _SQReservationParticipantsServices.InsertAsync(participant);
return new WebApiDto()
{
Code = 0,
Data = new { reservation_id = reservationId },
Msg = "预约成功"
};
}
/// <summary>
/// 用户是否可以创建预约(仅校验,不创建)
/// </summary>
/// <param name="dto">创建预约所需参数</param>
/// <returns>返回可否创建及原因</returns>
[HttpPost]
[Authorize]
public async Task<WebApiDto> CanCreateSQReservation([FromBody] SQReservationsAddDto dto)
{
var userId = _user.ID;
var start_time = DateTimeOffset.FromUnixTimeSeconds(dto.start_time).AddHours(8).DateTime;
var end_time = DateTimeOffset.FromUnixTimeSeconds(dto.end_time).AddHours(8).DateTime;
// 1. 参数校验
if (dto == null || dto.room_id <= 0 || start_time >= end_time)
{
return new WebApiDto()
{
Code = 500,
Data = new { canCreate = false },
Msg = "参数错误"
};
}
// 1.0 验证预约时间不能是过去的时间
var now = DateTime.Now;
if (start_time <= now)
{
return new WebApiDto()
{
Code = 500,
Data = new { canCreate = false },
Msg = "不能预约过去的时间"
};
}
// 1.1 验证预约时长最短1小时最长12小时
var duration = (end_time - start_time).TotalHours;
if (duration < 1)
{
return new WebApiDto()
{
Code = 500,
Data = new { canCreate = false },
Msg = "预约时长不能少于1小时"
};
}
if (duration > 12)
{
return new WebApiDto()
{
Code = 500,
Data = new { canCreate = false },
Msg = "预约时长不能超过12小时"
};
}
// 2. 检查房间是否存在
var room = await _SQRoomsServices.QueryByClauseAsync(r => r.id == dto.room_id, r => r.id, OrderByType.Asc);
if (room == null)
{
return new WebApiDto()
{
Code = 500,
Data = new { canCreate = false },
Msg = "房间不存在"
};
}
// 3. 检查房间是否可预约(不可用时间段)
var hasUnavailable = await _sQRoomUnavailableTimesServices.QueryListByClauseAsync(
t => t.room_id == dto.room_id && t.start_time < end_time && t.end_time > start_time,
t => t.id, OrderByType.Asc);
if (hasUnavailable != null && hasUnavailable.Count > 0)
{
return new WebApiDto()
{
Code = 500,
Data = new { canCreate = false },
Msg = "该时间段房间不可预约"
};
}
// 2.1 校验用户是否有其它预约时间冲突(只查未开始或正在进行中的预约)
now = DateTime.Now;
var hasConflict = await _SQReservationParticipantsServices.QueryMuchAsync<SQReservationParticipants, SQReservations, int>(
(p, r) => new object[]
{
JoinType.Inner, p.reservation_id == r.id
},
(p, r) => r.id,
(p, r) =>
p.user_id == userId &&
p.status == 0 &&
r.status < 3 &&
r.end_time > now &&
r.start_time < end_time && r.end_time > start_time
);
if (hasConflict != null && hasConflict.Count > 0)
{
return new WebApiDto
{
Code = 402,
Data = new { canCreate = false },
Msg = "您有其它预约时间冲突,无法创建该预约!"
};
}
// 4. 检查房间是否已被预约(未取消的预约,时间有重叠)
var hasReserved = await _SQReservationsServices.QueryListByClauseAsync(
r => r.room_id == dto.room_id && r.status < 2 && r.start_time < end_time && r.end_time > start_time,
r => r.id, OrderByType.Asc);
if (hasReserved != null && hasReserved.Count > 0)
{
return new WebApiDto()
{
Code = 500,
Data = new { canCreate = false },
Msg = "该时间段房间已被预约"
};
}
// 全部通过
return new WebApiDto()
{
Code = 0,
Data = new { canCreate = true },
Msg = "ok"
};
}
/// <summary>
/// 用户加入预约接口
/// </summary>
/// <param name="dto">预约</param>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<WebApiDto> JoinReservation([FromBody] SQReservationParticipantsAddDto dto)
{
var userId = _user.ID;
var reservationId = dto.ReservationsId;
// 1. 校验预约是否存在且未结束
var reservation = await _SQReservationsServices.QueryByClauseAsync(
r => r.id == reservationId && r.end_time > DateTime.Now && r.status < 2,
r => r.id, OrderByType.Asc);
if (reservation == null)
{
return new WebApiDto
{
Code = 404,
Data = null,
Msg = "预约不存在或已结束"
};
}
// 2. 校验用户是否已加入该预约
var exists = await _SQReservationParticipantsServices.QueryByClauseAsync(
p => p.reservation_id == reservationId && p.user_id == userId && p.status == 0,
p => p.id, OrderByType.Asc);
if (exists != null)
{
return new WebApiDto
{
Code = 400,
Data = null,
Msg = "您已加入该预约"
};
}
// 2.0.2 校验是否为"无需组局"模式
if (reservation.is_solo_mode)
{
return new WebApiDto
{
Code = 403,
Data = null,
Msg = "该预约为独享模式,不接受其他人加入"
};
}
var user = _userServices.QueryById(userId);
// 2.0.1 校验用户条件 是否符合要求 如 性别user.sex 1男2女 年龄user.birthday这个是生日 用生日去动态计算年龄) 信用分credit_score
//reservation.credit_limit 最低信誉分 同 用户表的的 信用分
// reservation.gender_limit 0 不限 1男 2 女
// 最大 和最小 年龄 0 不限制 reservation.max_age reservation.min_age
if (user == null)
{
return new WebApiDto
{
Code = 404,
Data = null,
Msg = "用户不存在"
};
}
// 信誉分校验
if (reservation.credit_limit > 0 && user.credit_score < reservation.credit_limit)
{
return new WebApiDto
{
Code = 400,
Data = null,
Msg = $"您的信誉分不足,最低要求为{reservation.credit_limit}"
};
}
// 性别限制0 不限1 男2 女
if (reservation.gender_limit == 1 || reservation.gender_limit == 2)
{
if (user.sex != reservation.gender_limit)
{
return new WebApiDto
{
Code = 400,
Data = null,
Msg = "您的性别不符合该预约要求"
};
}
}
// 年龄限制(按生日计算实际年龄)
if (reservation.min_age > 0 || reservation.max_age > 0)
{
if (!user.birthday.HasValue)
{
return new WebApiDto
{
Code = 400,
Data = null,
Msg = "请先完善生日信息以校验年龄"
};
}
var today = DateTime.Today;
var birth = user.birthday.Value.Date;
var age = today.Year - birth.Year;
if (birth > today.AddYears(-age)) age--;
if (reservation.min_age > 0 && age < reservation.min_age)
{
return new WebApiDto
{
Code = 400,
Data = null,
Msg = $"年龄小于最小限制:{reservation.min_age}岁"
};
}
if (reservation.max_age > 0 && age > reservation.max_age)
{
return new WebApiDto
{
Code = 400,
Data = null,
Msg = $"年龄超过最大限制:{reservation.max_age}岁"
};
}
}
// 2.1 校验用户是否有其它预约时间冲突
// 2.1 校验用户是否有其它预约时间冲突(只查未开始或正在进行中的预约)
var now = DateTime.Now;
var hasConflict = await _SQReservationParticipantsServices.QueryMuchAsync<SQReservationParticipants, SQReservations, int>(
(p, r) => new object[]
{
JoinType.Inner, p.reservation_id == r.id
},
(p, r) => r.id,
(p, r) =>
p.user_id == userId &&
p.status == 0 && // 只查未退出
r.status < 3 && // 只查未取消/未结束
r.end_time > now && // 只查未结束
r.start_time < reservation.end_time && r.end_time > reservation.start_time // 时间有重叠
);
if (hasConflict != null && hasConflict.Count > 0)
{
return new WebApiDto
{
Code = 402,
Data = null,
Msg = "您有其它预约时间冲突,无法加入该预约"
};
}
//// 查询用户所有未结束的预约参与记录
//var userReservations = await _SQReservationParticipantsServices.QueryListByClauseAsync(
// p => p.user_id == userId && p.status == 0, // 只查未退出的
// p => p.id, OrderByType.Asc);
//if (userReservations != null && userReservations.Count > 0)
//{
// // 获取这些预约的ID
// var reservationIds = userReservations.Select(p => p.reservation_id).ToList();
// // 查询这些预约的时间段
// var reservations = await _SQReservationsServices.QueryListByClauseAsync(
// r => reservationIds.Contains(r.id) && r.status < 2 && r.end_time > DateTime.Now,
// r => r.id, OrderByType.Asc);
// // 检查时间是否有重叠
// foreach (var r in reservations)
// {
// if (r.start_time < reservation.end_time && r.end_time > reservation.start_time)
// {
// return new WebApiDto
// {
// Code = 402,
// Data = null,
// Msg = "您有其它预约时间冲突,无法加入该预约"
// };
// }
// }
//}
//3.校验预约是否已满(如有容量限制,可补充)
var participantsCount = await _SQReservationParticipantsServices.QueryListByClauseAsync(
p => p.reservation_id == reservationId && p.status == 0, p => p.id, OrderByType.Asc);
if (participantsCount.Count >= reservation.player_count)
{
return new WebApiDto()
{
Code = 401,
Data = null,
Msg = "预约已满"
};
}
string paymentId = "";
if (reservation.deposit_fee > 0)
{
var imdata = JsonConvert.DeserializeObject<Dictionary<string, object>>(dto.important_data);
if (imdata == null || !imdata.ContainsKey("paymentId") || string.IsNullOrEmpty(imdata["paymentId"].ToString()))
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "押金支付信息错误!"
};
}
//订单编号
paymentId = imdata["paymentId"].ToString();
}
// 4. 插入参与人记录
var participant = new SQReservationParticipants
{
reservation_id = reservationId,
user_id = userId,
join_time = DateTime.Now,
role = 0, // 0为参与者
status = 0,
is_refund = reservation.deposit_fee > 0 ? 1 : 0,
important_data = dto.important_data,
paymentId = paymentId,
};
await _SQReservationParticipantsServices.InsertAsync(participant);
return new WebApiDto
{
Code = 0,
Data = new { reservation_id = reservationId },
Msg = "加入预约成功"
};
}
/// <summary>
/// 取消预约接口
/// </summary>
/// <param name="dto">取消预约参数</param>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<WebApiDto> CancelReservation([FromBody] SQReservationCancelDto dto)
{
var userId = _user.ID;
// 1. 参数校验
if (dto == null || dto.reservation_id <= 0)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "参数错误"
};
}
// 2. 查询预约是否存在
var reservation = await _SQReservationsServices.QueryByClauseAsync(
r => r.id == dto.reservation_id,
r => r.id, OrderByType.Asc);
if (reservation == null)
{
return new WebApiDto()
{
Code = 404,
Data = null,
Msg = "预约不存在"
};
}
if (reservation.start_time.AddMinutes(-30) < DateTime.Now)
{
return new WebApiDto()
{
Code = 400,
Data = null,
Msg = "预约开始前30分钟无法取消"
};
}
if (reservation.status == 1) //
{
return new WebApiDto()
{
Code = 400,
Data = null,
Msg = "该预约已组局成功锁定,无法取消"
};
}
// 3. 检查预约状态是否允许取消
if (reservation.status >= 3) // 3=已结束4=取消
{
return new WebApiDto()
{
Code = 400,
Data = null,
Msg = "该预约已结束或已取消,无法再次取消"
};
}
// 4. 检查用户是否有权限取消该预约
var participant = await _SQReservationParticipantsServices.QueryByClauseAsync(
p => p.reservation_id == dto.reservation_id && p.user_id == userId,
p => p.id, OrderByType.Asc);
if (participant == null)
{
return new WebApiDto()
{
Code = 403,
Data = null,
Msg = "您没有权限取消该预约"
};
}
// 5. 检查是否已经开始(如果已经开始,只有发起者可以取消)
var now = DateTime.Now;
if (reservation.start_time <= now && participant.role != 1)
{
return new WebApiDto()
{
Code = 400,
Data = null,
Msg = "预约已开始,只有发起者可以取消"
};
}
// 7. 更新参与人状态为已退出(如果是发起者取消整个预约,所有参与人都标记为退出)
if (participant.role == 1) // 发起者取消
{
// 7.1.如果是发起者取消,则当前对局全部取消
var updateResult = await _SQReservationsServices.UpdateAsync(
it => new SQReservations
{
status = 4, // 用户取消
updated_at = DateTime.Now,
remarks = string.IsNullOrEmpty(dto.cancel_reason) ? "未支付鸽子费" : dto.cancel_reason
},
it => it.id == dto.reservation_id);
if (!updateResult)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "取消预约失败"
};
}
// 7.2 有押金时,已支付押金(is_refund=2)的参与者为发起退款(3)
if (reservation.deposit_fee.HasValue && reservation.deposit_fee.Value > 0)
{
await _SQReservationParticipantsServices.UpdateAsync(
it => new SQReservationParticipants
{
is_refund = 3
},
it => it.reservation_id == dto.reservation_id && it.status == 0 && it.is_refund == 2 && it.paymentId != null && it.paymentId != "");
}
// 7.3发起者取消整个预约,所有参与人都标记为退出
await _SQReservationParticipantsServices.UpdateAsync(
it => new SQReservationParticipants
{
status = 1, // 已退出
quit_time = DateTime.Now
},
it => it.reservation_id == dto.reservation_id);
//判断是否有参与者
var list = await _SQReservationParticipantsServices.QueryListByClauseAsync(it => it.reservation_id == dto.reservation_id && it.role == 0 && it.status == 0);
if (list.Count > 0)
{
await _SQReservationsServices.NotifyReservationFailedAsync(reservation, list, "发起者解散组局!");
}
}
else // 参与者取消
{
// 参与者只退出自己
await _SQReservationParticipantsServices.UpdateAsync(
it => new SQReservationParticipants
{
status = 1, // 已退出
quit_time = DateTime.Now
},
it => it.reservation_id == dto.reservation_id && it.user_id == userId);
// 有押金且已支付押金时,标记该参与者为发起退款(3)
if (reservation.deposit_fee.HasValue && reservation.deposit_fee.Value > 0)
{
await _SQReservationParticipantsServices.UpdateAsync(
it => new SQReservationParticipants
{
is_refund = 3
},
it => it.reservation_id == dto.reservation_id && it.user_id == userId && it.is_refund == 2 && it.paymentId != null && it.paymentId != "");
}
}
return new WebApiDto()
{
Code = 0,
Data = null,
Msg = "取消预约成功"
};
}
/// <summary>
/// 预约签到(仅发起者可操作,且只能签到一次)
/// </summary>
/// <param name="dto">签到参数:不包含发起者的参会名单</param>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<WebApiDto> CheckInReservation([FromBody] SQReservationCheckInDto dto)
{
var userId = _user.ID;
if (dto == null || dto.reservation_id <= 0)
{
return new WebApiDto
{
Code = 500,
Data = null,
Msg = "参数错误"
};
}
// 查询预约
var reservation = await _SQReservationsServices.QueryByClauseAsync(
r => r.id == dto.reservation_id,
r => r.id, OrderByType.Asc);
if (reservation == null)
{
return new WebApiDto
{
Code = 404,
Data = null,
Msg = "预约不存在"
};
}
// 已结束或已取消
if (reservation.status >= 3)
{
return new WebApiDto
{
Code = 400,
Data = null,
Msg = "预约已结束或已取消,无法签到"
};
}
// 已进行中(已签到),禁止重复
if (reservation.status == 2)
{
return new WebApiDto
{
Code = 400,
Data = null,
Msg = "已签到,无法重复签到"
};
}
// 权限校验:仅发起者可签到
var initiator = await _SQReservationParticipantsServices.QueryByClauseAsync(
p => p.reservation_id == dto.reservation_id && p.user_id == userId && p.role == 1 && p.status == 0,
p => p.id, OrderByType.Asc);
if (initiator == null)
{
return new WebApiDto
{
Code = 403,
Data = null,
Msg = "仅发起者可进行签到"
};
}
dto.attendeds ??= new List<SQReservationCheckInAttendeeDto>();
var falseUserIds = dto.attendeds
.Where(a => a != null && a.isAttended == false)
.Select(a => a.user_id)
.Distinct()
.ToList();
var qiandaoUserIds = dto.attendeds
.Where(a => a != null && a.isAttended)
.Select(a => a.user_id)
.Distinct()
.ToList();
try
{
_dbBase.Ado.BeginTran();
// 1) 预约置为进行中
var updatedReservation = await _SQReservationsServices.UpdateAsync(
it => new SQReservations
{
status = 2,
updated_at = DateTime.Now
},
it => it.id == dto.reservation_id);
if (!updatedReservation)
{
_dbBase.Ado.RollbackTran();
return new WebApiDto
{
Code = 500,
Data = null,
Msg = "签到失败:更新预约状态异常"
};
}
// 2) 默认将所有未退出的参与者标记为已赴约(1)
await _SQReservationParticipantsServices.UpdateAsync(
it => new SQReservationParticipants
{
is_arrive = 1
},
it => it.reservation_id == dto.reservation_id && it.status == 0);
// 3) 对于传入的未出席名单,标记为未赴约(2),同时将其踢出
if (falseUserIds.Count > 0)
{
await _SQReservationParticipantsServices.UpdateAsync(
it => new SQReservationParticipants
{
is_arrive = 2,
status = 1
},
it => it.reservation_id == dto.reservation_id && it.status == 0 && falseUserIds.Contains(it.user_id));
//如果有鸽子费,需要将鸽子费平分到 已到的用户,添加到收益中
//_sQReservationReputationServices.
//AddReputationAsync(falseUserIds, -5, $"未按时赴约扣除5点信誉分预约ID:{dto.reservation_id}");
//添加鸽子次数
foreach (var user_id in falseUserIds)
{
var user = await _userServices.QueryByIdAsync(user_id);
if (user != null)
{
//扣除信誉分
await _sQReservationReputationServices.InsertAsync(new SQReservationReputation()
{
created_at = DateTime.Now,
remark = $"{reservation.title}对局未签到",
reputation_value = (decimal)-0.5,
reservation_id = dto.reservation_id,
updated_at = DateTime.Now,
user_id = user_id
});
user.dove_count++;
if (user.credit_score > 0)
{
user.credit_score = (user.credit_score - (decimal)0.5);
}
if (user.credit_score <= 0)
{
user.credit_score = 0;
}
}
await _userServices.UpdateAsync(user);
}
}
//确保发起者始终为已赴约(1)
if (!qiandaoUserIds.Contains(userId))
{
qiandaoUserIds.Add(userId);
}
if (qiandaoUserIds.Count > 0)
{
foreach (var user_id in qiandaoUserIds)
{
var user = await _userServices.QueryByIdAsync(user_id);
if (user != null)
{
//user.dove_count++;
if (user.credit_score < 5)
{
user.credit_score = (user.credit_score + (decimal)0.2);
if (user.credit_score > 5)
{
user.credit_score = 5;
}
//增加信记录
await _sQReservationReputationServices.InsertAsync(new SQReservationReputation()
{
created_at = DateTime.Now,
remark = $"{reservation.title} 完成组局",
reputation_value = (decimal)0.2,
reservation_id = dto.reservation_id,
updated_at = DateTime.Now,
user_id = user_id
});
await _userServices.UpdateAsync(user);
}
}
}
}
// 4) 确保发起者始终为已赴约(1)
await _SQReservationParticipantsServices.UpdateAsync(
it => new SQReservationParticipants
{
is_arrive = 1,
check_reservation = DateTime.Now
},
it => it.reservation_id == dto.reservation_id && it.status == 0 && it.user_id == userId);
if (reservation.deposit_fee > 0)
{
// 有押金,标记到场人员为“发起退款(3)”,由定时任务统一处理实际退款
await _SQReservationParticipantsServices.UpdateAsync(
it => new SQReservationParticipants
{
is_refund = 3
},
it => it.reservation_id == dto.reservation_id && it.status == 0 && it.is_arrive == 1 && it.is_refund == 2 && it.paymentId != null && it.paymentId != "");
}
_dbBase.Ado.CommitTran();
return new WebApiDto
{
Code = 0,
Data = null,
Msg = "签到成功"
};
}
catch (Exception)
{
_dbBase.Ado.RollbackTran();
return new WebApiDto
{
Code = 500,
Data = null,
Msg = "签到失败"
};
}
}
/// <summary>
/// 订单支付记录(分页)
/// </summary>
[HttpGet]
[Authorize]
public async Task<WebApiDto> GetPaymentRecords([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20)
{
var userId = _user.ID;
if (pageIndex < 1) pageIndex = 1;
if (pageSize <= 0) pageSize = 20;
var sql = $@"SELECT sq.title, p.paymentId, p.is_refund, p.join_time, p.status,
CASE p.is_refund WHEN 1 THEN N'待支付' WHEN 2 THEN N'已支付' WHEN 3 THEN N'待退款' WHEN 4 THEN N'已退款' ELSE N'退款异常' END AS is_refund_text
FROM SQReservationParticipants p
LEFT JOIN SQReservations sq ON p.reservation_id = sq.id
WHERE p.user_id = {userId} AND p.is_refund > 0 AND p.paymentId IS NOT NULL AND p.paymentId <> ''
ORDER BY p.id DESC
OFFSET {(pageIndex - 1) * pageSize} ROWS FETCH NEXT {pageSize} ROWS ONLY";
var list = await _dbBase.Ado.SqlQueryAsync<SQPaymentRecordDto>(sql);
return new WebApiDto()
{
Code = 0,
Data = list,
Msg = "ok"
};
}
#region
/// <summary>
/// 获取房间详情
/// </summary>
/// <param name="roomId">房间ID</param>
/// <param name="date">查询日期Unix时间戳-秒级),默认今天</param>
/// <returns></returns>
[HttpGet]
public async Task<WebApiDto> GetRoomDetail([FromQuery] int roomId, [FromQuery] long date = 0)
{
try
{
if (roomId <= 0)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "参数错误房间ID无效"
};
}
var queryDate = DateTime.Now;
if (date > 0)
{
// 反序列化时明确作为 UTC 时间戳处理
var targetDate = DateTimeOffset.FromUnixTimeSeconds(date).UtcDateTime;
// 或者如果需要北京时间
var chinaTz = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
queryDate = TimeZoneInfo.ConvertTimeFromUtc(targetDate, chinaTz);
}
// 调用Service层方法
var result = await _SQRoomsServices.GetRoomDetailAsync(roomId, queryDate);
if (result == null)
{
return new WebApiDto()
{
Code = 404,
Data = null,
Msg = "房间不存在"
};
}
return new WebApiDto()
{
Code = 0,
Data = result,
Msg = "ok"
};
}
catch (Exception ex)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = $"查询失败:{ex.Message}"
};
}
}
#endregion
#region
/// <summary>
/// 获取未来7天日期列表
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<WebApiDto> GetAvailableDates()
{
var dates = new List<SQAvailableDateDto>();
var today = DateTime.Today;
// 明确指定使用中国时区
var chinaTimeZone = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
for (int i = 0; i < 7; i++) // 今天+未来6天
{
var date = today.AddDays(i);
var weekDay = date.ToString("dddd", new System.Globalization.CultureInfo("zh-CN"));
var dateText = i == 0 ? "今天" : i == 1 ? "明天" : i == 2 ? "后天" : date.ToString("ddd", new System.Globalization.CultureInfo("zh-CN"));
// 转换为 DateTimeOffset 并指定为北京时间
var beijingOffset = new DateTimeOffset(date, chinaTimeZone.GetUtcOffset(date));
dates.Add(new SQAvailableDateDto
{
date = (beijingOffset).ToUnixTimeSeconds(),
dateText = dateText,
dateDisplay = $"{date.ToString("MM月dd")}"
});
}
return new WebApiDto()
{
Code = 0,
Data = dates,
Msg = "ok"
};
}
/// <summary>
/// 获取房间列表及时段状态(新版)
/// </summary>
/// <param name="date">查询日期Unix时间戳-秒级)</param>
/// <param name="showOnlyAvailable">是否只显示当前时段可用的房间</param>
/// <param name="currentTimeSlot">当前时段类型配合showOnlyAvailable使用</param>
/// <returns></returns>
[HttpGet]
public async Task<WebApiDto> GetRoomListWithSlotsNew([FromQuery] long date, [FromQuery] bool showOnlyAvailable = false, [FromQuery] int? currentTimeSlot = null)
{
try
{
if (date == 0)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = "参数错误:请提供日期"
};
}
// 反序列化时明确作为 UTC 时间戳处理
var targetDate = DateTimeOffset.FromUnixTimeSeconds(date).UtcDateTime;
// 或者如果需要北京时间
var chinaTz = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
var beijingDate = TimeZoneInfo.ConvertTimeFromUtc(targetDate, chinaTz);
// 调用Service层方法
var result = await _SQRoomsServices.GetRoomListWithSlotsAsync(beijingDate, showOnlyAvailable, currentTimeSlot);
return new WebApiDto()
{
Code = 0,
Data = result,
Msg = "ok"
};
}
catch (Exception ex)
{
return new WebApiDto()
{
Code = 500,
Data = null,
Msg = $"查询失败:{ex.Message}"
};
}
}
/// <summary>
/// 校验是否可以创建预约(新版,按时段)
/// </summary>
/// <param name="dto">创建预约DTO</param>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<WebApiDto> ValidateReservationBySlot([FromBody] SQReservationsAddBySlotDto dto)
{
var userId = _user.ID;
try
{
var (canCreate, reason) = await _SQReservationsServices.ValidateReservationBySlotAsync(dto, userId);
return new WebApiDto()
{
Code = canCreate ? 0 : 500,
Data = new { canCreate, reason },
Msg = reason
};
}
catch (Exception ex)
{
return new WebApiDto()
{
Code = 500,
Data = new { canCreate = false, reason = ex.Message },
Msg = $"校验失败:{ex.Message}"
};
}
}
/// <summary>
/// 获取营业时间配置(已存在,保持不变)
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<WebApiDto> GetBusinessHours()
{
var businessHours = new SQBusinessHoursDto
{
open_time = BUSINESS_OPEN_TIME,
close_time = BUSINESS_CLOSE_TIME,
is_24_hours = false,
description = $"早{BUSINESS_OPEN_TIME.Split(':')[0]}点 至 晚{BUSINESS_CLOSE_TIME.Split(':')[0]}点"
};
return new WebApiDto()
{
Code = 0,
Data = businessHours,
Msg = "ok"
};
}
#endregion
#region
/// <summary>
/// 计算房间时段占用情况
/// </summary>
/// <param name="reservations">预约列表</param>
/// <param name="dayStart">查询日期开始</param>
/// <param name="dayEnd">查询日期结束</param>
/// <returns></returns>
private SQRoomTimeSlotsDto CalculateTimeSlots(List<SQReservations> reservations, DateTime dayStart, DateTime dayEnd)
{
var timeSlots = new SQRoomTimeSlotsDto
{
dawn = new SQRoomTimeSlotDto(),
morning = new SQRoomTimeSlotDto(),
afternoon = new SQRoomTimeSlotDto(),
evening = new SQRoomTimeSlotDto()
};
if (reservations == null || reservations.Count == 0)
{
return timeSlots;
}
// 定义时段范围
var dawnStart = dayStart.AddHours(0);
var dawnEnd = dayStart.AddHours(6);
var morningStart = dayStart.AddHours(6);
var morningEnd = dayStart.AddHours(12);
var afternoonStart = dayStart.AddHours(12);
var afternoonEnd = dayStart.AddHours(18);
var eveningStart = dayStart.AddHours(18);
var eveningEnd = dayStart.AddHours(24);
foreach (var reservation in reservations)
{
// 检查凌晨时段
if (reservation.start_time < dawnEnd && reservation.end_time > dawnStart)
{
timeSlots.dawn.is_occupied = true;
timeSlots.dawn.reservations.Add(new SQRoomTimeSlotReservationDto
{
start_time = reservation.start_time.ToString("yyyy-MM-dd HH:mm:ss"),
end_time = reservation.end_time.ToString("yyyy-MM-dd HH:mm:ss")
});
}
// 检查上午时段
if (reservation.start_time < morningEnd && reservation.end_time > morningStart)
{
timeSlots.morning.is_occupied = true;
timeSlots.morning.reservations.Add(new SQRoomTimeSlotReservationDto
{
start_time = reservation.start_time.ToString("yyyy-MM-dd HH:mm:ss"),
end_time = reservation.end_time.ToString("yyyy-MM-dd HH:mm:ss")
});
}
// 检查下午时段
if (reservation.start_time < afternoonEnd && reservation.end_time > afternoonStart)
{
timeSlots.afternoon.is_occupied = true;
timeSlots.afternoon.reservations.Add(new SQRoomTimeSlotReservationDto
{
start_time = reservation.start_time.ToString("yyyy-MM-dd HH:mm:ss"),
end_time = reservation.end_time.ToString("yyyy-MM-dd HH:mm:ss")
});
}
// 检查晚上时段
if (reservation.start_time < eveningEnd && reservation.end_time > eveningStart)
{
timeSlots.evening.is_occupied = true;
timeSlots.evening.reservations.Add(new SQRoomTimeSlotReservationDto
{
start_time = reservation.start_time.ToString("yyyy-MM-dd HH:mm:ss"),
end_time = reservation.end_time.ToString("yyyy-MM-dd HH:mm:ss")
});
}
}
return timeSlots;
}
#endregion
#region
/// <summary>
/// 获取用户消息列表
/// </summary>
/// <param name="pageIndex">页码从1开始</param>
/// <param name="pageSize">每页数量</param>
/// <param name="messageType">消息类型0=全部1=私信</param>
/// <returns></returns>
[HttpGet]
[Authorize]
public async Task<JsonResult> GetMessageList(int pageIndex = 1, int pageSize = 20, int messageType = 0)
{
var jm = new WebApiCallBack();
try
{
var userId = _user.ID;
if (userId <= 0)
{
jm.code = 401;
jm.msg = "请先登录";
return new JsonResult(jm);
}
var list = await _sQMessageServices.GetUserMessageListAsync(userId, messageType, pageIndex, pageSize);
jm.code = 0;
jm.msg = "获取成功";
jm.data = list;
}
catch (Exception ex)
{
jm.code = 1;
jm.msg = "获取消息列表失败";
jm.data = ex.Message;
}
return new JsonResult(jm);
}
/// <summary>
/// 获取未读消息数量
/// </summary>
/// <returns></returns>
[HttpGet]
[Authorize]
public async Task<JsonResult> GetUnreadCount()
{
var jm = new WebApiCallBack();
try
{
var userId = _user.ID;
if (userId <= 0)
{
jm.code = 401;
jm.msg = "请先登录";
return new JsonResult(jm);
}
var count = await _sQMessageServices.GetUnreadCountAsync(userId);
jm.code = 0;
jm.msg = "获取成功";
jm.data = new { count };
}
catch (Exception ex)
{
jm.code = 1;
jm.msg = "获取未读数量失败";
jm.data = ex.Message;
}
return new JsonResult(jm);
}
/// <summary>
/// 标记所有消息为已读
/// </summary>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<JsonResult> MarkAllAsRead()
{
var jm = new WebApiCallBack();
try
{
var userId = _user.ID;
if (userId <= 0)
{
jm.code = 401;
jm.msg = "请先登录";
return new JsonResult(jm);
}
var result = await _sQMessageServices.MarkAllAsReadAsync(userId);
if (result)
{
jm.code = 0;
jm.msg = "标记成功";
}
else
{
jm.code = 1;
jm.msg = "标记失败";
}
}
catch (Exception ex)
{
jm.code = 1;
jm.msg = "操作失败";
jm.data = ex.Message;
}
return new JsonResult(jm);
}
#endregion
#region
/// <summary>
/// 获取收益统计信息
/// </summary>
/// <returns></returns>
[HttpGet]
[Authorize]
public async Task<JsonResult> GetEarningsSummary()
{
var jm = new WebApiCallBack();
try
{
var userId = _user.ID;
if (userId <= 0)
{
jm.code = 401;
jm.msg = "请先登录";
return new JsonResult(jm);
}
var result = await _sQEarningsServices.GetEarningsSummaryAsync(userId);
jm.code = 0;
jm.msg = "ok";
jm.data = result;
}
catch (Exception ex)
{
jm.code = 1;
jm.msg = "获取失败";
jm.data = ex.Message;
}
return new JsonResult(jm);
}
/// <summary>
/// 获取收益规则说明
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<JsonResult> GetEarningsRule()
{
var jm = new WebApiCallBack();
try
{
// 从配置中获取收益规则说明
var allConfigs = await _settingServices.GetConfigDictionaries();
var content = CommonHelper.GetConfigDictionary(allConfigs, SystemSettingConstVars.EarningsRule);
// 如果配置为空,返回默认内容
if (string.IsNullOrWhiteSpace(content))
{
content = "收益规则说明:\n1. 收益来源于房间预约成功后的分成\n2. 收益可随时提现\n3. 提现将在3-5个工作日内到账\n4. 最低提现金额为0.01元";
}
jm.code = 0;
jm.msg = "ok";
jm.data = new { content };
}
catch (Exception ex)
{
jm.code = 1;
jm.msg = "获取失败";
jm.data = ex.Message;
}
return new JsonResult(jm);
}
/// <summary>
/// 获取收益记录列表
/// </summary>
/// <param name="pageIndex">页码从1开始</param>
/// <param name="pageSize">每页数量</param>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<JsonResult> GetEarningsRecordList([FromBody] PageRequest request)
{
var jm = new WebApiCallBack();
try
{
var userId = _user.ID;
if (userId <= 0)
{
jm.code = 401;
jm.msg = "请先登录";
return new JsonResult(jm);
}
var pageIndex = request?.pageIndex ?? 1;
var pageSize = request?.pageSize ?? 20;
var result = await _sQEarningsServices.GetEarningsRecordListAsync(userId, pageIndex, pageSize);
jm.code = 0;
jm.msg = "ok";
jm.data = result;
}
catch (Exception ex)
{
jm.code = 1;
jm.msg = "获取失败";
jm.data = ex.Message;
}
return new JsonResult(jm);
}
/// <summary>
/// 获取提现记录列表
/// </summary>
/// <param name="request">分页请求</param>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<JsonResult> GetWithdrawRecordList([FromBody] PageRequest request)
{
var jm = new WebApiCallBack();
try
{
var userId = _user.ID;
if (userId <= 0)
{
jm.code = 401;
jm.msg = "请先登录";
return new JsonResult(jm);
}
var pageIndex = request?.pageIndex ?? 1;
var pageSize = request?.pageSize ?? 20;
var result = await _sQEarningsServices.GetWithdrawRecordListAsync(userId, pageIndex, pageSize);
jm.code = 0;
jm.msg = "ok";
jm.data = result;
}
catch (Exception ex)
{
jm.code = 1;
jm.msg = "获取失败";
jm.data = ex.Message;
}
return new JsonResult(jm);
}
/// <summary>
/// 申请提现
/// </summary>
/// <param name="request">提现请求</param>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<JsonResult> ApplyWithdraw([FromBody] ApplyWithdrawRequest request)
{
var jm = new WebApiCallBack();
try
{
var userId = _user.ID;
if (userId <= 0)
{
jm.code = 401;
jm.msg = "请先登录";
return new JsonResult(jm);
}
if (request == null || request.amount <= 0)
{
jm.code = 1;
jm.msg = "请输入正确的提现金额";
return new JsonResult(jm);
}
var result = await _sQEarningsServices.ApplyWithdrawAsync(userId, request.amount);
jm.code = result.status ? 0 : 1;
jm.msg = result.msg;
jm.data = result.data;
}
catch (Exception ex)
{
jm.code = 1;
jm.msg = "提现申请失败";
jm.data = ex.Message;
}
return new JsonResult(jm);
}
#endregion
}
/// <summary>
/// 分页请求
/// </summary>
public class PageRequest
{
/// <summary>
/// 页码从1开始
/// </summary>
public int pageIndex { get; set; } = 1;
/// <summary>
/// 每页数量
/// </summary>
public int pageSize { get; set; } = 20;
}
/// <summary>
/// 申请提现请求
/// </summary>
public class ApplyWithdrawRequest
{
/// <summary>
/// 提现金额
/// </summary>
public decimal amount { get; set; }
}