签到与活动配置:后台配置接口、用户签到流程、UserEntity 扩展

This commit is contained in:
2026-10-19 00:24:22 +08:00
parent d7a672cdf4
commit ff2b779a56
13 changed files with 569 additions and 425 deletions
@@ -79,10 +79,10 @@ namespace AdminApi.GameConfigs
}
[HttpPost("UpdateSign")]
public async Task<BaseResponse<string>> UpdateSign(List<SignConfig> configs)
public async Task<BaseResponse<string>> UpdateSign(SignActivityConfig config)
{
var activityConfig = await _activityConfigService.Get() ?? new ActivityConfig();
activityConfig.SignConfigs = configs;
activityConfig.SignActivityConfig = config;
var res = await _activityConfigService.Update(activityConfig);
if (res.isok)
return this.ToResult(res.msg);
@@ -3144,22 +3144,21 @@ namespace AdminApi.GameConfigs
var config = new ActivityConfig();
config.SignConfigs.Add(new SignConfig() { Order = 70000, Gold = 2000, Name = "2千", Icon = "/uploads/70000.png", Probability = 0.01 });
config.SignConfigs.Add(new SignConfig() { Order = 70001, Gold = 800, Name = "8佰", Icon = "/uploads/70001.png", Probability = 0.09 });
config.SignConfigs.Add(new SignConfig() { Order = 70002, Gold = 400, Name = "4佰", Icon = "/uploads/70002.png", Probability = 0.09 });
config.SignConfigs.Add(new SignConfig() { Order = 70003, Gold = 200, Name = "2佰", Icon = "/uploads/70003.png", Probability = 0.05 });
config.SignConfigs.Add(new SignConfig() { Order = 70004, Gold = 1000, Name = "1千", Icon = "/uploads/70004.png", Probability = 0.05 });
config.SignConfigs.Add(new SignConfig() { Order = 70005, Gold = 800, Name = "8佰", Icon = "/uploads/70005.png", Probability = 0.09 });
config.SignConfigs.Add(new SignConfig() { Order = 70006, Gold = 400, Name = "4佰", Icon = "/uploads/70006.png", Probability = 0.09 });
config.SignConfigs.Add(new SignConfig() { Order = 70007, Gold = 200, Name = "2佰", Icon = "/uploads/70007.png", Probability = 0.05 });
config.SignConfigs.Add(new SignConfig() { Order = 70008, Gold = 2000, Name = "2千", Icon = "/uploads/70008.png", Probability = 0.01 });
config.SignConfigs.Add(new SignConfig() { Order = 70009, Gold = 1000, Name = "1千", Icon = "/uploads/70009.png", Probability = 0.05 });
config.SignConfigs.Add(new SignConfig() { Order = 70010, Gold = 400, Name = "4佰", Icon = "/uploads/70010.png", Probability = 0.09 });
config.SignConfigs.Add(new SignConfig() { Order = 70011, Gold = 200, Name = "2佰", Icon = "/uploads/70011.png", Probability = 0.05 });
config.SignConfigs.Add(new SignConfig() { Order = 70012, Gold = 800, Name = "8佰", Icon = "/uploads/70012.png", Probability = 0.09 });
config.SignConfigs.Add(new SignConfig() { Order = 70013, Gold = 1000, Name = "1千", Icon = "/uploads/70013.png", Probability = 0.05 });
config.SignConfigs.Add(new SignConfig() { Order = 70014, Gold = 400, Name = "4佰", Icon = "/uploads/70014.png", Probability = 0.09 });
config.SignConfigs.Add(new SignConfig() { Order = 70015, Gold = 200, Name = "2佰", Icon = "/uploads/70015.png", Probability = 0.05 });
config.SignActivityConfig = new SignActivityConfig()
{
IsOpen = true,
CannotInterrupt = false,
Days = new List<SignConfig>()
{
new SignConfig() { Day = 1, Name = "第1天", Icon = "/uploads/70000.png", GiftList = new List<GiftConfig>() { new GiftConfig() { Type = 1, Count = 200 } } },
new SignConfig() { Day = 2, Name = "第2天", Icon = "/uploads/70001.png", GiftList = new List<GiftConfig>() { new GiftConfig() { Type = 1, Count = 400 } } },
new SignConfig() { Day = 3, Name = "第3天", Icon = "/uploads/70002.png", GiftList = new List<GiftConfig>() { new GiftConfig() { Type = 1, Count = 800 } } },
new SignConfig() { Day = 4, Name = "第4天", Icon = "/uploads/70003.png", GiftList = new List<GiftConfig>() { new GiftConfig() { Type = 1, Count = 1000 } } },
new SignConfig() { Day = 5, Name = "第5天", Icon = "/uploads/70004.png", GiftList = new List<GiftConfig>() { new GiftConfig() { Type = 1, Count = 1500 } } },
new SignConfig() { Day = 6, Name = "第6天", Icon = "/uploads/70005.png", GiftList = new List<GiftConfig>() { new GiftConfig() { Type = 1, Count = 2000 } } },
new SignConfig() { Day = 7, Name = "第7天", Icon = "/uploads/70006.png", GiftList = new List<GiftConfig>() { new GiftConfig() { Type = 1, Count = 5000 }, new GiftConfig() { Type = 2, Count = 100 }, new GiftConfig() { Type = 3, OtherId = "40003", Count = 1 } } }
}
};
config.ReliefConfig = new ReliefConfig() { IsOpen = true, Count = 3, Gold = 100000, Limit = 10000 };
+64 -114
View File
@@ -3694,120 +3694,70 @@
"Count": 3,
"Limit": 10000
},
"SignConfigs": [
{
"Order": 70000,
"Gold": 2000,
"Name": "2千",
"Icon": "/uploads/70000.png",
"Probability": 0.01
},
{
"Order": 70001,
"Gold": 800,
"Name": "8佰",
"Icon": "/uploads/70001.png",
"Probability": 0.09
},
{
"Order": 70002,
"Gold": 400,
"Name": "4佰",
"Icon": "/uploads/70002.png",
"Probability": 0.09
},
{
"Order": 70003,
"Gold": 200,
"Name": "2佰",
"Icon": "/uploads/70003.png",
"Probability": 0.05
},
{
"Order": 70004,
"Gold": 1000,
"Name": "1千",
"Icon": "/uploads/70004.png",
"Probability": 0.05
},
{
"Order": 70005,
"Gold": 800,
"Name": "8佰",
"Icon": "/uploads/70005.png",
"Probability": 0.09
},
{
"Order": 70006,
"Gold": 400,
"Name": "4佰",
"Icon": "/uploads/70006.png",
"Probability": 0.09
},
{
"Order": 70007,
"Gold": 200,
"Name": "2佰",
"Icon": "/uploads/70007.png",
"Probability": 0.05
},
{
"Order": 70008,
"Gold": 2000,
"Name": "2千",
"Icon": "/uploads/70008.png",
"Probability": 0.01
},
{
"Order": 70009,
"Gold": 1000,
"Name": "1千",
"Icon": "/uploads/70009.png",
"Probability": 0.05
},
{
"Order": 70010,
"Gold": 400,
"Name": "4佰",
"Icon": "/uploads/70010.png",
"Probability": 0.09
},
{
"Order": 70011,
"Gold": 200,
"Name": "2佰",
"Icon": "/uploads/70011.png?timespan=1784483834",
"Probability": 0.05
},
{
"Order": 70012,
"Gold": 800,
"Name": "8佰",
"Icon": "/uploads/70012.png?timespan=1784483840",
"Probability": 0.09
},
{
"Order": 70013,
"Gold": 1000,
"Name": "1千",
"Icon": "/uploads/70013.png?timespan=1784483847",
"Probability": 0.05
},
{
"Order": 70014,
"Gold": 400,
"Name": "4佰",
"Icon": "/uploads/70014.png?timespan=1784483852",
"Probability": 0.09
},
{
"Order": 70015,
"Gold": 200,
"Name": "2佰",
"Icon": "/uploads/70015.png?timespan=1784483858",
"Probability": 0.05
}
],
"SignActivityConfig": {
"IsOpen": true,
"CannotInterrupt": false,
"Days": [
{
"Day": 1,
"Name": "第1天",
"Icon": "/uploads/70000.png",
"GiftList": [
{ "Type": 1, "Count": 200 }
]
},
{
"Day": 2,
"Name": "第2天",
"Icon": "/uploads/70001.png",
"GiftList": [
{ "Type": 1, "Count": 400 }
]
},
{
"Day": 3,
"Name": "第3天",
"Icon": "/uploads/70002.png",
"GiftList": [
{ "Type": 1, "Count": 800 }
]
},
{
"Day": 4,
"Name": "第4天",
"Icon": "/uploads/70003.png",
"GiftList": [
{ "Type": 1, "Count": 1000 }
]
},
{
"Day": 5,
"Name": "第5天",
"Icon": "/uploads/70004.png",
"GiftList": [
{ "Type": 1, "Count": 1500 }
]
},
{
"Day": 6,
"Name": "第6天",
"Icon": "/uploads/70005.png",
"GiftList": [
{ "Type": 1, "Count": 2000 }
]
},
{
"Day": 7,
"Name": "第7天",
"Icon": "/uploads/70006.png",
"GiftList": [
{ "Type": 1, "Count": 5000 },
{ "Type": 2, "Count": 100 },
{ "Type": 3, "OtherId": "40003", "Count": 1 }
]
}
]
},
"GoldShopConfigs": [
{
"Number": "70001",
+236 -198
View File
@@ -1,4 +1,4 @@
<title>签到配置</title>
<div class="layui-card layadmin-header">
@@ -11,6 +11,33 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">基础配置</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="sign-form">
<div class="layui-form-item">
<label class="layui-form-label">是否开启</label>
<div class="layui-input-inline">
<input type="checkbox" name="isOpen" lay-skin="switch" lay-text="开启|关闭">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">不可中断</label>
<div class="layui-input-inline" style="line-height:38px;">
<input type="checkbox" name="cannotInterrupt" lay-skin="switch" lay-text="勾选|未勾选">
</div>
<div class="layui-form-mid layui-word-aux">勾选=漏签不回退(按累计次数循环1~7);未勾选=漏签重置回第1天</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="save-base">保存基础配置</button>
</div>
</div>
</form>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">7 日签到奖励</div>
<div class="layui-card-body">
<table id="table-sign" lay-filter="table-sign"></table>
</div>
@@ -20,17 +47,16 @@
<script type="text/html" id="toolbar-sign">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="init-sign"><i class="layui-icon layui-icon-refresh"></i> 初始化配置</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-sign"><i class="layui-icon layui-icon-add-1"></i> 添加天数</button>
<button class="layui-btn layui-btn-sm" lay-filter="save-sign"><i class="layui-icon layui-icon-ok"></i> 保存配置</button>
<button class="layui-btn layui-btn-sm layui-btn-warm" lay-filter="reload-sign"><i class="layui-icon layui-icon-refresh"></i> 刷新</button>
<button class="layui-btn layui-btn-sm" lay-filter="add-sign">添加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger" lay-filter="batchdel-sign">批量删除</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveAct('SignConfigs')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
<button class="layui-btn layui-btn-sm layui-btn-normal" onclick="saveAct('SignActivityConfig')"><i class="layui-icon layui-icon-export"></i> 保存到初始化文件</button>
</div>
</script>
<script type="text/html" id="table-operate">
<script type="text/html" id="bar-sign">
<a class="layui-btn layui-btn-xs" lay-event="edit"><i class="layui-icon layui-icon-edit"></i>编辑</a>
<a class="layui-btn layui-btn-xs" lay-event="preview"><i class="layui-icon layui-icon-search"></i>预览</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del"><i class="layui-icon layui-icon-delete"></i>删除</a>
</script>
@@ -49,6 +75,10 @@
.icon-upload-preview:hover { border-color: #009688; }
.icon-upload-preview img { max-width: 100%; max-height: 100%; }
.icon-upload-preview .placeholder { color: #999; font-size: 12px; text-align: center; }
.gift-row { display: flex; align-items: center; margin-bottom: 6px; }
.gift-row .layui-input, .gift-row select { height: 32px; }
.gift-row .gr-count { width: 110px; margin: 0 6px; }
.gift-row .gr-oid { width: 150px; margin-right: 6px; }
</style>
<script>
@@ -61,6 +91,7 @@
setter = layui.setter;
var activityConfig = null;
var typeMap = { 1: '金币', 2: '钻石', 3: '道具' };
setTimeout(function() {
if (window.parent && window.parent.$ && window.parent.layer) {
@@ -71,43 +102,104 @@
layer.closeAll();
}, 100);
function sign() {
if (!activityConfig) activityConfig = {};
if (!activityConfig.signActivityConfig) activityConfig.signActivityConfig = { isOpen: true, cannotInterrupt: false, days: [] };
if (!activityConfig.signActivityConfig.days) activityConfig.signActivityConfig.days = [];
return activityConfig.signActivityConfig;
}
function loadConfig() {
admin.req({
method: "get",
url: setter.baseurl + "adminapi/ActivityConfig/Get",
done: function (res) {
if (res.code === 0 && res.data) {
activityConfig = res.data;
renderTable();
activityConfig = (res.code === 0 && res.data) ? res.data : { signActivityConfig: {} };
initForm();
}
});
}
function initForm() {
var s = sign();
$('input[name="isOpen"]').prop('checked', !!s.isOpen);
$('input[name="cannotInterrupt"]').prop('checked', !!s.cannotInterrupt);
form.render();
renderTable();
}
function save(msg) {
var s = sign();
s.isOpen = $('input[name="isOpen"]')[0].checked;
s.cannotInterrupt = $('input[name="cannotInterrupt"]')[0].checked;
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/ActivityConfig/UpdateSign",
data: JSON.stringify(s),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg(msg || '保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
}
function giftText(list) {
if (!list || !list.length) return '<span style="color:#999;">未配置</span>';
return list.map(function (g) {
var t = typeMap[g.type] || '金币';
var oid = (g.type === 3 && g.otherId) ? ('(' + g.otherId + ')') : '';
return t + '×' + (g.count || 0) + oid;
}).join('、');
}
function renderTable() {
table.render({
elem: '#table-sign',
data: activityConfig ? activityConfig.signConfigs || [] : [],
data: sign().days || [],
page: false,
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: "order", title: "排序", width: 70, edit: 'text' },
{ field: "name", title: "名称", width: 100, edit: 'text' },
{ field: "gold", title: "金币", width: 100, edit: 'text' },
{ field: "probability", title: "概率", width: 100, edit: 'text' },
{ field: "icon", title: "图标", width: 100, templet: '#tpl-icon' },
{ title: "操作", width: 180, fixed: 'right', toolbar: '#table-operate' }
{ field: "day", title: "第几天", width: 80, sort: true },
{ field: "name", title: "名称", width: 140 },
{ field: "icon", title: "图标", width: 90, templet: '#tpl-icon' },
{ title: "奖励", templet: function (d) { return giftText(d.giftList); } },
{ title: "操作", width: 160, fixed: 'right', toolbar: '#bar-sign' }
]],
toolbar: '#toolbar-sign',
defaultToolbar: ['filter', 'exports', 'print']
});
}
function initConfig() {
layer.confirm('确定要从配置文件还原签到配置吗?', function(idx) {
form.on('submit(save-base)', function () { save('保存成功'); return false; });
$(document).on('click', '[lay-filter="reload-sign"]', function () {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-sign"]', function () { save('保存成功'); });
$(document).on('click', '[lay-filter="add-sign"]', function () {
var days = sign().days || [];
var max = days.length ? Math.max.apply(null, days.map(function (d) { return d.day || 0; })) : 0;
showDayEdit({ day: max + 1, name: '第' + (max + 1) + '天', icon: '', giftList: [] }, true, -1);
});
$(document).on('click', '[lay-filter="init-sign"]', function () {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
layer.confirm('确定要从配置文件还原签到配置吗?', function (idx) {
layer.close(idx);
admin.req({
method: "get",
url: setter.baseurl + "adminapi/GameConfig/InitActivitySection/SignConfigs",
url: setter.baseurl + "adminapi/GameConfig/InitActivitySection/SignActivityConfig",
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('初始化成功');
@@ -116,48 +208,14 @@
layer.msg(res.info || '初始化失败');
}
},
fail: function(res) {
fail: function (res) {
layer.msg(res.info || '初始化失败');
}
});
});
}
$(document).on('click', '[lay-filter="init-sign"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
initConfig();
});
$(document).on('click', '[lay-filter="reload-sign"]', function() {
loadConfig();
layer.msg('刷新成功');
});
$(document).on('click', '[lay-filter="save-sign"]', function() {
if (window.parent && window.parent.$ && window.parent.layer) {
window.parent.$('.layui-layer-shade').remove();
window.parent.layer.closeAll();
}
admin.req({
method: "post",
contentType: "application/json;charset=UTF-8",
url: setter.baseurl + "adminapi/ActivityConfig/UpdateSign",
data: JSON.stringify(activityConfig.signConfigs || []),
done: function (res) {
if (res.code === 0 || res.code === 200) {
layer.msg('保存成功');
loadConfig();
} else {
layer.msg(res.info || '保存失败');
}
}
});
});
window.previewImage = function(url) {
window.previewImage = function (url) {
layer.open({
type: 1,
title: '图片预览',
@@ -167,75 +225,87 @@
});
};
function updateDataByTable(obj) {
var field = obj.field;
var value = obj.value;
table.on('tool(table-sign)', function (obj) {
var data = obj.data;
if (field === 'gold') {
data[field] = parseInt(value) || 0;
} else if (field === 'probability') {
data[field] = parseFloat(value) || 0;
} else {
data[field] = value;
}
}
table.on('edit(table-sign)', function(obj){
updateDataByTable(obj);
});
table.on('tool(table-sign)', function(obj) {
var data = obj.data;
var trIndex = activityConfig && activityConfig.signConfigs
? activityConfig.signConfigs.findIndex(function(x) { return x.order === data.order; }) : -1;
var days = sign().days || [];
var idx = days.findIndex(function (x) { return x.day === data.day; });
if (idx < 0) return;
if (obj.event === 'edit') {
showEditDialog(trIndex > -1 ? activityConfig.signConfigs[trIndex] : data, false, trIndex);
showDayEdit(days[idx], false, idx);
} else if (obj.event === 'del') {
layer.confirm('确定删除该配置吗?', function(idx) {
if (trIndex >= 0 && activityConfig && activityConfig.signConfigs) {
activityConfig.signConfigs.splice(trIndex, 1);
}
obj.del();
layer.close(idx);
layer.confirm('确定删除第' + data.day + '天配置吗?', function (i) {
days.splice(idx, 1);
layer.close(i);
save('删除成功');
});
} else if (obj.event === 'preview') {
if (data.icon) {
previewImage(data.icon);
} else {
layer.msg('暂无图片');
}
}
});
function showAddDialog() {
var newData = {
order: 0,
name: '',
gold: 0,
probability: 0,
icon: ''
};
showEditDialog(newData, true);
}
$(document).on('click', '[lay-filter="add-sign"]', function() {
showAddDialog();
$(document).on('click', '[lay-filter="batchdel-sign"]', function () {
var checkData = table.checkStatus('table-sign').data;
if (checkData.length === 0) return layer.msg('请选择要删除的数据');
layer.confirm('确定删除选中 ' + checkData.length + ' 条数据吗?', function (idx) {
var daySet = checkData.map(function (item) { return item.day; });
var s = sign();
s.days = (s.days || []).filter(function (item) { return daySet.indexOf(item.day) < 0; });
layer.close(idx);
save('删除成功');
});
});
function showEditDialog(data, isAdd, rowIndex) {
var dialogTitle = isAdd ? '添加签到配置' : '编辑签到配置';
// ==================== 天数编辑弹窗(含奖励编辑) ====================
function giftRowHtml(g) {
g = g || { type: 1, count: 0, otherId: '' };
var oidStyle = g.type === 3 ? '' : 'display:none;';
return '<div class="gift-row">' +
'<select class="gr-type" style="width:90px;">' +
'<option value="1">金币</option><option value="2">钻石</option><option value="3">道具</option>' +
'</select>' +
'<input type="number" class="layui-input gr-count" placeholder="数量" value="' + (g.count || 0) + '">' +
'<input type="text" class="layui-input gr-oid" style="' + oidStyle + '" placeholder="道具编号" value="' + (g.otherId || '') + '">' +
'<button type="button" class="layui-btn layui-btn-sm layui-btn-danger gr-del">删除</button>' +
'</div>';
}
function bindGiftRows(container) {
container.off('change', '.gr-type').on('change', '.gr-type', function () {
var isProp = $(this).val() === '3';
$(this).closest('.gift-row').find('.gr-oid').toggle(isProp);
});
container.off('click', '.gr-del').on('click', '.gr-del', function () {
$(this).closest('.gift-row').remove();
});
container.off('click', '.gr-add').on('click', '.gr-add', function () {
container.find('#giftRows').append(giftRowHtml({ type: 1, count: 0, otherId: '' }));
});
}
function collectGiftRows(container) {
var list = [];
container.find('.gift-row').each(function () {
var t = parseInt($(this).find('.gr-type').val()) || 1;
var c = parseInt($(this).find('.gr-count').val()) || 0;
var oid = t === 3 ? ($(this).find('.gr-oid').val() || '') : '';
list.push({ type: t, count: c, otherId: oid });
});
return list;
}
function showDayEdit(data, isAdd, rowIndex) {
var imagePreviewHtml = data.icon
? '<img src="' + data.icon + '" style="max-width:100%;max-height:100%;">'
: '<div class="placeholder">点击上传</div>';
var rowsHtml = '';
(data.giftList || []).forEach(function (g) { rowsHtml += giftRowHtml(g); });
var html = '<div style="padding:20px;">' +
'<form class="layui-form">' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">排序</label>' +
'<label class="layui-form-label">第几天</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="order" value="' + (data.order || 0) + '" class="layui-input">' +
'<input type="number" name="day" value="' + (data.day || 1) + '" class="layui-input" ' + (isAdd ? '' : 'readonly') + '>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
@@ -244,27 +314,23 @@
'<input type="text" name="name" value="' + (data.name || '') + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">金币</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="gold" value="' + (data.gold || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">概率</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="probability" value="' + (data.probability || 0) + '" class="layui-input">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">图标</label>' +
'<div class="layui-input-block">' +
'<div class="icon-upload-box">' +
'<div class="icon-upload-preview" id="iconPreview">' + imagePreviewHtml + '</div>' +
'<input type="file" class="upload-btn" accept="image/png" id="iconUpload">' +
'<input type="file" class="upload-btn" accept="image/*" id="iconUpload">' +
'</div>' +
'<input type="hidden" name="icon" id="iconInput" value="' + (data.icon || '') + '">' +
'<div style="margin-top:8px;color:#999;font-size:12px;">点击图片可预览,仅支持 PNG 格式</div>' +
'<div style="margin-top:8px;color:#999;font-size:12px;">点击图片可预览</div>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">奖励</label>' +
'<div class="layui-input-block">' +
'<div id="giftRows">' + rowsHtml + '</div>' +
'<button type="button" class="layui-btn layui-btn-sm layui-btn-normal gr-add" style="margin-top:6px;"><i class="layui-icon layui-icon-add-1"></i> 新增奖励</button>' +
'<div style="color:#999;font-size:12px;margin-top:4px;">类型选“道具”时需填写道具编号</div>' +
'</div>' +
'</div>' +
'</form>' +
@@ -272,106 +338,78 @@
layer.open({
type: 1,
title: dialogTitle,
area: ['600px', '500px'],
title: isAdd ? '添加天数配置' : '编辑天数配置',
area: ['620px', '560px'],
content: html,
btn: ['保存', '取消'],
shadeClose: true,
success: function(layero) {
success: function (layero) {
// 初始化每个奖励行的类型下拉
(data.giftList || []).forEach(function (g, i) {
var row = layero.find('.gift-row').eq(i);
row.find('.gr-type').val(g.type || 1);
row.find('.gr-oid').toggle((g.type || 1) === 3);
});
bindGiftRows(layero);
var $preview = layero.find('#iconPreview');
var $input = layero.find('#iconInput');
var $fileInput = layero.find('#iconUpload');
$preview.on('click', function() {
$preview.on('click', function () {
var url = $input.val();
if (url) {
previewImage(url);
}
if (url) previewImage(url);
});
$fileInput.on('change', function() {
layero.find('#iconUpload').on('change', function () {
var file = this.files[0];
if (file) {
var order = layero.find('input[name="order"]').val();
if (!order && isAdd) {
layer.msg('请先填写排序编号');
$(this).val('');
return;
}
var formData = new FormData();
formData.append('file', file);
formData.append('number', order);
$.ajax({
url: setter.baseurl + 'adminapi/ImageUpload/upload/config',
type: 'POST',
data: formData,
processData: false,
contentType: false,
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
success: function(res) {
if (res.code === 0 || res.code === 200) {
var path = res.data.path;
$input.val(path);
$preview.html('<img src="' + path + '" style="max-width:100%;max-height:100%;">');
layer.msg('上传成功');
} else {
layer.msg(res.info || '上传失败');
}
},
error: function() {
layer.msg('上传失败');
if (!file) return;
var formData = new FormData();
formData.append('file', file);
$.ajax({
url: setter.baseurl + 'adminapi/ImageUpload/upload/temp',
type: 'POST',
data: formData,
processData: false,
contentType: false,
headers: { 'token': layui.data(setter.tableName)[setter.request.tokenName] },
success: function (res) {
if (res.code === 0 || res.code === 200) {
var path = res.data.path;
$input.val(path);
$preview.html('<img src="' + path + '" style="max-width:100%;max-height:100%;">');
layer.msg('上传成功');
} else {
layer.msg(res.info || '上传失败');
}
});
}
},
error: function () { layer.msg('上传失败'); }
});
});
},
yes: function(index, layero) {
yes: function (index, layero) {
var day = parseInt(layero.find('input[name="day"]').val()) || data.day;
if (day <= 0) { layer.msg('请填写正确的天数'); return; }
var item = {
day: day,
name: layero.find('input[name="name"]').val() || ('第' + day + '天'),
icon: layero.find('input[name="icon"]').val(),
giftList: collectGiftRows(layero)
};
var s = sign();
if (!s.days) s.days = [];
if (isAdd) {
var newItem = {
order: parseInt(layero.find('input[name="order"]').val()) || 0,
name: layero.find('input[name="name"]').val(),
gold: parseInt(layero.find('input[name="gold"]').val()) || 0,
probability: parseFloat(layero.find('input[name="probability"]').val()) || 0,
icon: layero.find('input[name="icon"]').val()
};
if (!activityConfig.signConfigs) activityConfig.signConfigs = [];
activityConfig.signConfigs.unshift(newItem);
if (s.days.some(function (x) { return x.day === item.day; })) { layer.msg('该天数已存在'); return; }
s.days.push(item);
} else if (rowIndex > -1) {
var original = activityConfig.signConfigs[rowIndex];
original.order = parseInt(layero.find('input[name="order"]').val()) || 0;
original.name = layero.find('input[name="name"]').val();
original.gold = parseInt(layero.find('input[name="gold"]').val()) || 0;
original.probability = parseFloat(layero.find('input[name="probability"]').val()) || 0;
original.icon = layero.find('input[name="icon"]').val();
s.days[rowIndex] = item;
}
layer.close(index);
table.reload('table-sign', {
data: activityConfig.signConfigs || []
});
save(isAdd ? '添加成功' : '保存成功');
}
});
}
$(document).on('click', '[lay-filter="batchdel-sign"]', function() {
var checkStatus = table.checkStatus('table-sign');
var checkData = checkStatus.data;
if (checkData.length === 0) {
return layer.msg('请选择要删除的数据');
}
layer.confirm('确定删除选中 ' + checkData.length + ' 条数据吗?', function(idx) {
var orders = checkData.map(function(item) { return item.order; });
if (activityConfig && activityConfig.signConfigs) {
activityConfig.signConfigs = activityConfig.signConfigs.filter(function(item) {
return !orders.includes(item.order);
});
}
renderTable();
layer.close(idx);
});
});
window.saveAct=function(n){layui.admin.req({method:'get',url:(layui.setter.baseurl||'/')+'adminapi/ActivityConfig/SaveSection/'+n,done:function(r){layui.layer.msg(r.info||(r.code===0?'保存成功':'保存失败'))}})}
window.saveAct = function (n) { layui.admin.req({ method: 'get', url: (layui.setter.baseurl || '/') + 'adminapi/ActivityConfig/SaveSection/' + n, done: function (r) { layui.layer.msg(r.info || (r.code === 0 ? '保存成功' : '保存失败')) } }) }
loadConfig();
});
+14 -4
View File
@@ -15,7 +15,7 @@ namespace CommonDomain
public ReliefConfig ReliefConfig { get; set; } = new ReliefConfig();
public List<SignConfig> SignConfigs { get; set; } = new List<SignConfig>();
public SignActivityConfig SignActivityConfig { get; set; } = new SignActivityConfig();
public List<GoldShopConfig> GoldShopConfigs { get; set; } = new List<GoldShopConfig>();
@@ -42,6 +42,17 @@ namespace CommonDomain
}
}
[BsonIgnoreExtraElements]
public class SignActivityConfig
{
public bool IsOpen { get; set; } = true;
/// <summary>勾选=不可中断(漏签不回退,按累计次数循环);未勾选=可中断(漏签重置回第1天)</summary>
public bool CannotInterrupt { get; set; } = false;
public List<SignConfig> Days { get; set; } = new List<SignConfig>();
}
public class RankingRewardConfig
{
public bool IsEnabled { get; set; }
@@ -72,11 +83,10 @@ namespace CommonDomain.BaseModel
[BsonIgnoreExtraElements]
public class SignConfig
{
public int Order { get; set; } = 0;
public long Gold { get; set; } = 0;
public int Day { get; set; } = 1;
public string Name { get; set; } = string.Empty;
public string Icon { get; set; } = string.Empty;
public double Probability { get; set; } = 0;
public List<GiftConfig> GiftList { get; set; } = new List<GiftConfig>();
}
[BsonIgnoreExtraElements]
@@ -1186,6 +1186,32 @@ namespace FishGameService.GameService
}
}
/// <summary>
/// 机器人击杀累计经验并在游戏内升级(按 UserLevelConfigs 判定,等级上限与经验上限封顶)。
/// </summary>
private void AddBotExp(RedisGamePlayer bot, long gainExp)
{
if (bot == null || gainExp <= 0)
return;
bot.Exp = bot.Exp > long.MaxValue - gainExp ? long.MaxValue : bot.Exp + gainExp;
var configs = _gameConfig?.UserLevelConfigs;
if (configs == null || configs.Count == 0)
return;
var levelCfg = configs.FirstOrDefault(c => bot.Exp >= c.ExpMin && bot.Exp < c.ExpMax)
?? configs.OrderBy(c => c.Level).LastOrDefault();
if (levelCfg == null)
return;
bot.Level = levelCfg.Level;
bot.MaxExp = levelCfg.ExpMax;
if (bot.Exp >= levelCfg.ExpMax)
bot.Exp = levelCfg.ExpMax - 1;
}
private void AddBullets(string roomId, string userId, List<string> bulletIds)
{
_bulletTracker.Add(roomId, userId, bulletIds);
@@ -1848,7 +1874,11 @@ namespace FishGameService.GameService
skillGold = res.gold;
bot.Gold = skillGold > long.MaxValue - bot.Gold ? long.MaxValue : bot.Gold + skillGold;
_sendResult.RoomKillNotice(roomId, fishNumber, botUserId, skillGold, fishId, DateTime.Now.ToTimeStamp(), res.beishu, gold, fishConfig.FishScore, res.res);
var botExpGain = fishConfig.FishType is FishTypeEnum.Boss鱼 or FishTypeEnum.特殊鱼 or FishTypeEnum.特殊Boss
? 1000 : fishConfig.FishScore;
AddBotExp(bot, botExpGain);
_sendResult.RoomKillNotice(roomId, fishNumber, botUserId, skillGold, fishId, DateTime.Now.ToTimeStamp(), res.beishu, gold, fishConfig.FishScore, res.res, gainExp: botExpGain, exp: bot.Exp, level: bot.Level, expMax: bot.MaxExp);
await FishKillControl(cid, room, bot, fishNumber, skillGold, fishId, gold, DateTime.Now.ToTimeStamp(), killConfig);
}
@@ -1959,10 +1989,6 @@ namespace FishGameService.GameService
var cannonMultiple = roomConfig.CannonMultipleList[System.Random.Shared.Next(roomConfig.CannonMultipleList.Count)];
var maxMultiple = roomConfig.CannonMultipleList.Max();
var gold = maxMultiple * System.Random.Shared.Next(100, 501);
var props = _gameConfig?.PropConfig ?? new List<PropInfConfig>();
var cannons = props.Where(p => p.Type == ProductTypeEnum.炮台 && p.IsEnable && !p.Number.StartsWith("401")).ToList();
var wings = props.Where(p => p.Type == ProductTypeEnum.翅膀 && p.IsEnable && p.IsSale).ToList();
var warGods = props.Where(p => p.Type == ProductTypeEnum.战神 && p.IsEnable && p.IsSale).ToList();
var replacementId = Guid.NewGuid().ToString("N");
var replacementNumber = Common.Utils.RandomHelper.RandomNumbers(8);
@@ -1972,16 +1998,10 @@ namespace FishGameService.GameService
UserNumber = replacementNumber,
UserName = profile.Name,
Avatar = profile.Avatar,
Level = System.Random.Shared.Next(1, 101),
IsBot = true,
Gold = gold,
Diamond = System.Random.Shared.Next(100, 5001),
Position = position,
CannonId = cannons.Count > 0 ? cannons[System.Random.Shared.Next(cannons.Count)].Number : string.Empty,
WingsId = wings.Count > 0 ? wings[System.Random.Shared.Next(wings.Count)].Number : string.Empty,
WarGodId = warGods.Count > 0 && System.Random.Shared.Next(5) == 0
? warGods[System.Random.Shared.Next(warGods.Count)].Number
: string.Empty,
CannonMultiple = cannonMultiple,
BeginCoin = gold,
BeginTime = DateTime.Now.ToTimeStamp(),
@@ -1989,6 +2009,7 @@ namespace FishGameService.GameService
State = 0,
PlayerFactor = new PlayerFactor()
};
RoomBotFactory.ApplyBotBaseValues(replacement, _gameConfig);
room.Players.Add(replacement);
NormalizeRoomPlayerCounts(room);
@@ -2038,10 +2059,6 @@ namespace FishGameService.GameService
int botsToCreate = Math.Min(room.RobotCount, availablePositions.Count);
var cannons = _gameConfig.PropConfig.Where(p => p.Type == ProductTypeEnum.炮台 && p.IsEnable && !p.Number.StartsWith("401")).ToList();
var wings = _gameConfig.PropConfig.Where(p => p.Type == ProductTypeEnum.翅膀 && p.IsEnable && p.IsSale).ToList();
var warGods = _gameConfig.PropConfig.Where(p => p.Type == ProductTypeEnum.战神 && p.IsEnable && p.IsSale).ToList();
for (int i = 0; i < botsToCreate; i++)
{
var idx = System.Random.Shared.Next(botProfiles.Count);
@@ -2058,16 +2075,10 @@ namespace FishGameService.GameService
UserNumber = botNumber,
UserName = profile.Name,
Avatar = profile.Avatar,
Level = System.Random.Shared.Next(1, 101),
IsBot = true,
Gold = gold,
Diamond = System.Random.Shared.Next(100, 1001),
Position = position,
CannonId = cannons.Count > 0 ? cannons[System.Random.Shared.Next(cannons.Count)].Number : string.Empty,
WingsId = wings.Count > 0 ? wings[System.Random.Shared.Next(wings.Count)].Number : string.Empty,
WarGodId = warGods.Count > 0 && System.Random.Shared.Next(5) == 0
? warGods[System.Random.Shared.Next(warGods.Count)].Number
: string.Empty,
CannonMultiple = roomConfig.CannonMultipleList[cannonIndex],
BeginCoin = gold,
BeginTime = DateTime.Now.ToTimeStamp(),
@@ -2075,6 +2086,7 @@ namespace FishGameService.GameService
State = 0,
PlayerFactor = new PlayerFactor()
};
RoomBotFactory.ApplyBotBaseValues(bot, _gameConfig);
room.Players.Add(bot);
+59 -11
View File
@@ -14,6 +14,17 @@ namespace RedisService.Param
public static bool FixedBotSeatsEnabled => FixedBotSeats.Length > 0;
/// <summary>基础默认炮台(与 UserService.UnloadCannon 中一致)</summary>
public const string DefaultCannonNumber = "11001";
/// <summary>机器人初始等级范围</summary>
public const int MinBotLevel = 1;
public const int MaxBotLevel = 15;
/// <summary>机器人初始 VIP 等级范围</summary>
public const int MinBotVipLevel = 0;
public const int MaxBotVipLevel = 5;
public static RedisGamePlayer CreateBot(int position, RoomConfig roomConfig, GameConfig gameConfig, IReadOnlyList<BotProfileEntity> botProfiles)
{
if (roomConfig?.CannonMultipleList == null || roomConfig.CannonMultipleList.Count == 0
@@ -23,29 +34,19 @@ namespace RedisService.Param
var profile = botProfiles[System.Random.Shared.Next(botProfiles.Count)];
var maxMultiple = roomConfig.CannonMultipleList.Max();
var gold = maxMultiple * System.Random.Shared.Next(100, 501);
var props = gameConfig?.PropConfig ?? new List<PropInfConfig>();
var cannons = props.Where(p => p.Type == ProductTypeEnum.炮台 && p.IsEnable && !p.Number.StartsWith("401")).ToList();
var wings = props.Where(p => p.Type == ProductTypeEnum.翅膀 && p.IsEnable && p.IsSale).ToList();
var warGods = props.Where(p => p.Type == ProductTypeEnum.战神 && p.IsEnable && p.IsSale).ToList();
var botId = Guid.NewGuid().ToString("N");
var botNumber = Common.Utils.RandomHelper.RandomNumbers(8);
return new RedisGamePlayer
var bot = new RedisGamePlayer
{
UserId = botId,
UserNumber = botNumber,
UserName = profile.Name,
Avatar = profile.Avatar,
Level = System.Random.Shared.Next(1, 101),
IsBot = true,
Gold = gold,
Diamond = System.Random.Shared.Next(100, 5001),
Position = position,
CannonId = cannons.Count > 0 ? cannons[System.Random.Shared.Next(cannons.Count)].Number : string.Empty,
WingsId = wings.Count > 0 ? wings[System.Random.Shared.Next(wings.Count)].Number : string.Empty,
WarGodId = warGods.Count > 0 && System.Random.Shared.Next(5) == 0
? warGods[System.Random.Shared.Next(warGods.Count)].Number
: string.Empty,
CannonMultiple = roomConfig.CannonMultipleList[System.Random.Shared.Next(roomConfig.CannonMultipleList.Count)],
BeginCoin = gold,
BeginTime = new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds(),
@@ -53,6 +54,53 @@ namespace RedisService.Param
State = 0,
PlayerFactor = new PlayerFactor()
};
ApplyBotBaseValues(bot, gameConfig);
return bot;
}
/// <summary>
/// 设置机器人基础值:等级 1~15、VIP 0~5、经验与等级对应、炮台 30% 非默认 / 70% 默认、
/// 翅膀 15%、战神 5%。三处创建机器人的入口共用,避免取值不一致。
/// </summary>
public static void ApplyBotBaseValues(RedisGamePlayer bot, GameConfig gameConfig)
{
if (bot == null)
return;
var levelConfigs = gameConfig?.UserLevelConfigs ?? new List<UserLevelConfig>();
var inRangeLevels = levelConfigs
.Where(c => c != null && c.Level >= MinBotLevel && c.Level <= MaxBotLevel)
.ToList();
var levelCfg = inRangeLevels.Count > 0
? inRangeLevels[System.Random.Shared.Next(inRangeLevels.Count)]
: null;
bot.Level = levelCfg?.Level ?? MinBotLevel;
bot.MaxExp = levelCfg?.ExpMax ?? 0;
bot.Exp = levelCfg == null || levelCfg.ExpMax <= levelCfg.ExpMin
? (levelCfg?.ExpMin ?? 0)
: System.Random.Shared.NextInt64(levelCfg.ExpMin, levelCfg.ExpMax);
bot.VipLevel = System.Random.Shared.Next(MinBotVipLevel, MaxBotVipLevel + 1);
var props = gameConfig?.PropConfig ?? new List<PropInfConfig>();
var cannons = props.Where(p => p.Type == ProductTypeEnum.炮台 && p.IsEnable && !p.Number.StartsWith("401")).ToList();
var wings = props.Where(p => p.Type == ProductTypeEnum.翅膀 && p.IsEnable && p.IsSale).ToList();
var warGods = props.Where(p => p.Type == ProductTypeEnum.战神 && p.IsEnable && p.IsSale).ToList();
var nonDefaultCannons = cannons.Where(c => c.Number != DefaultCannonNumber).ToList();
bot.CannonId = System.Random.Shared.Next(100) < 30 && nonDefaultCannons.Count > 0
? nonDefaultCannons[System.Random.Shared.Next(nonDefaultCannons.Count)].Number
: DefaultCannonNumber;
bot.WingsId = wings.Count > 0 && System.Random.Shared.Next(100) < 15
? wings[System.Random.Shared.Next(wings.Count)].Number
: string.Empty;
bot.WarGodId = warGods.Count > 0 && System.Random.Shared.Next(100) < 5
? warGods[System.Random.Shared.Next(warGods.Count)].Number
: string.Empty;
}
public static List<RedisGamePlayer> CreateFixedSeatBots(RedisGameRoom room, RoomConfig roomConfig, GameConfig gameConfig, IReadOnlyList<BotProfileEntity> botProfiles, IReadOnlyList<int> fixedSeats = null)
@@ -44,11 +44,11 @@ namespace UserApi.GameConfigs
return this.ToListResult(config?.GoldShopConfigs ?? new List<GoldShopConfig>());
}
[HttpGet("signConfigs")]
public async Task<ListResponse<SignConfig>> GetSignConfigs()
[HttpGet("signConfig")]
public async Task<BaseResponse<SignActivityConfig>> GetSignConfig()
{
var config = await _activityConfigService.Get();
return this.ToListResult(config?.SignConfigs ?? new List<SignConfig>());
return this.ToResult(config?.SignActivityConfig ?? new SignActivityConfig());
}
[HttpGet("reliefConfig")]
+10 -3
View File
@@ -145,13 +145,20 @@ namespace UserApi.Hall
}
[HttpGet("SignIn")]
public async Task<BaseResponse<long>> SignIn()
public async Task<BaseResponse<SignRewardResult>> SignIn()
{
var res = await _userService.Sign(loginUser);
if (res.isok)
return this.ToResult<long>(res.gold);
return this.ToResult(res.result);
else
return this.ToFailResult<long>(res.msg);
return this.ToFailResult<SignRewardResult>(res.msg);
}
[HttpGet("GetSignStatus")]
public async Task<BaseResponse<SignStatusResult>> GetSignStatus()
{
var res = await _userService.GetSignStatus(loginUser);
return this.ToResult(res);
}
+4 -1
View File
@@ -1,4 +1,4 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations.Schema;
using Common.Utils;
using CommonDomain;
using CommonDomain.BaseModel;
@@ -201,6 +201,9 @@ namespace UserDomain.Entity
{
public bool IsSign { get; set; } = false;
public long SignTime { get; set; } = 0;
/// <summary>当前已签到的周期天数(0=未开始,1~7)</summary>
public int SignDay { get; set; } = 0;
}
[BsonIgnoreExtraElements]
public class PlayerFactor
+3 -1
View File
@@ -92,7 +92,9 @@ namespace UserService.User
Task<(bool isok, string msg)> BuyDiamondPayCallBack(string orderId);
Task<(bool isok, string msg, long gold)> Sign(LoginUser loginUser);
Task<(bool isok, string msg, SignRewardResult result)> Sign(LoginUser loginUser);
Task<SignStatusResult> GetSignStatus(LoginUser loginUser);
Task<(bool isok, string msg)> ChangeCannon(string number, LoginUser loginUser);
@@ -0,0 +1,38 @@
using CommonDomain.BaseModel;
namespace UserService.Param
{
/// <summary>签到奖励返回</summary>
public class SignRewardResult
{
/// <summary>本次签到对应第几天(1~7)</summary>
public int Day { get; set; } = 0;
public long Gold { get; set; } = 0;
public int Diamond { get; set; } = 0;
public List<BaseProductCount> Product { get; set; } = new List<BaseProductCount>();
}
/// <summary>签到状态返回</summary>
public class SignStatusResult
{
public bool IsOpen { get; set; } = false;
/// <summary>勾选=不可中断(漏签不回退);未勾选=可中断(漏签重置)</summary>
public bool CannotInterrupt { get; set; } = false;
/// <summary>当前周期已完成到第几天(0~7)</summary>
public int SignDay { get; set; } = 0;
/// <summary>今天签到对应的天数(已签则为其已签天数;未签则为下一次目标天数)</summary>
public int TodayDay { get; set; } = 0;
/// <summary>今天是否已签到</summary>
public bool IsTodaySigned { get; set; } = false;
/// <summary>7 天奖励配置</summary>
public List<SignConfig> Days { get; set; } = new List<SignConfig>();
}
}
+88 -51
View File
@@ -1364,36 +1364,107 @@ namespace UserService.User
}
public async Task<(bool isok, string msg, long gold)> Sign(LoginUser loginUser)
public async Task<(bool isok, string msg, SignRewardResult result)> Sign(LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
if (user == null)
throw new BusinessException("用户不存在");
// if (user.Assets.VipLevel == 0)
// if (user.Sign.IsSign)
var activityConfig = await _activityConfigService.Get();
var signList = activityConfig.SignConfigs;
if (signList.Count == 0)
var signConfig = activityConfig?.SignActivityConfig;
if (signConfig == null || !signConfig.IsOpen)
throw new BusinessException("签到活动未开启");
if (signConfig.Days == null || signConfig.Days.Count == 0)
throw new BusinessException("签到配置不存在");
var sign = GetRandomSignConfig(activityConfig);
if (user.Sign == null)
user.Sign = new SignInfo();
var gold = Convert.ToInt64(sign.Gold * (1 + user.Assets.VipSignInAdd));
var now = DateTime.Now;
var today = now.Date;
DateTime? lastSignDate = user.Sign.SignTime > 0 ? user.Sign.SignTime.ToDateTime().Date : (DateTime?)null;
if (lastSignDate.HasValue && lastSignDate.Value == today)
throw new BusinessException("今日已签到");
user.Sign.IsSign = true;
user.Sign.SignTime = DateTime.Now.ToTimeStamp();
var day = CalcSignDay(user.Sign.SignDay, lastSignDate, today, signConfig.CannotInterrupt);
var dayConfig = signConfig.Days.FirstOrDefault(c => c.Day == day) ?? signConfig.Days.OrderBy(c => c.Day).First();
if (dayConfig == null || dayConfig.GiftList == null || dayConfig.GiftList.Count == 0)
throw new BusinessException($"第{day}天签到奖励未配置");
var gift = GetGift(dayConfig.GiftList);
var gold = Convert.ToInt64(gift.Gold * (1 + user.Assets.VipSignInAdd));
var res = await UpdateAssets(new UserAssetParam
{
UserId = user.Id,
ChangeType = UserAssetChangeTypeEnum.增加,
Source = RewardSource.签到,
Remark = $"第{day}天签到奖励",
Gold = gold,
Diamond = gift.Diamond,
RewardItems = gift.Product
});
if (!res.isok)
return (false, res.msg, null);
var signUpdate = Builders<UserEntity>.Update
.Set(p => p.Sign.IsSign, true)
.Set(p => p.Sign.SignTime, now.ToTimeStamp())
.Set(p => p.Sign.SignDay, day);
await _userMongo.UpdateByIdAsync(user.Id, signUpdate);
return (true, "签到成功", new SignRewardResult
{
Day = day,
Gold = gold,
Diamond = gift.Diamond,
Product = gift.Product ?? new List<BaseProductCount>()
});
}
UserReward userReward = new UserReward() { UserId = user.Id, ChangeCount = gold, CurrencyType = 1, Remark = "签到获取金币", Source = RewardSource.签到, Type = 1, BeforeCount = user.Assets.Gold };
user.Assets.Gold += gold;
userReward.AfterCount = user.Assets.Gold;
await _rewardMongo.InsertOneAsync(userReward);
public async Task<SignStatusResult> GetSignStatus(LoginUser loginUser)
{
var user = await GetUser(loginUser.UserId);
var activityConfig = await _activityConfigService.Get();
var signConfig = activityConfig?.SignActivityConfig;
await _userMongo.ReplaceOneAsync(user);
var result = new SignStatusResult
{
IsOpen = signConfig?.IsOpen ?? false,
CannotInterrupt = signConfig?.CannotInterrupt ?? false,
Days = signConfig?.Days?.OrderBy(c => c.Day).ToList() ?? new List<SignConfig>()
};
return (true, "签到成功", gold);
var today = DateTime.Now.Date;
DateTime? lastSignDate = user?.Sign != null && user.Sign.SignTime > 0
? user.Sign.SignTime.ToDateTime().Date
: (DateTime?)null;
result.SignDay = user?.Sign?.SignDay ?? 0;
result.IsTodaySigned = lastSignDate.HasValue && lastSignDate.Value == today;
result.TodayDay = result.IsTodaySigned
? result.SignDay
: CalcSignDay(result.SignDay, lastSignDate, today, result.CannotInterrupt);
return result;
}
private const int SignCycleDays = 7;
/// <summary>计算本次签到对应的天数(1~7)</summary>
private static int CalcSignDay(int currentDay, DateTime? lastSignDate, DateTime today, bool cannotInterrupt)
{
// 上次签到为昨天:连续签到,天数顺延
if (lastSignDate.HasValue && lastSignDate.Value.Date == today.AddDays(-1))
return currentDay > 0 ? currentDay % SignCycleDays + 1 : 1;
// 漏签:不可中断则继续顺延(累计循环);可中断则重置回第1天
if (currentDay > 0 && cannotInterrupt)
return currentDay % SignCycleDays + 1;
return 1;
}
@@ -2328,40 +2399,6 @@ namespace UserService.User
}
public SignConfig GetRandomSignConfig(ActivityConfig activityConfig)
{
if (activityConfig?.SignConfigs == null || !activityConfig.SignConfigs.Any())
{
return null;
}
var configs = activityConfig.SignConfigs;
var totalProbability = configs.Sum(c => Math.Max(0, c.Probability));
if (totalProbability <= 0)
{
return null;
}
var randomValue = System.Random.Shared.NextDouble() * totalProbability;
double cumulativeProbability = 0;
foreach (var config in configs)
{
var currentProbability = Math.Max(0, config.Probability);
cumulativeProbability += currentProbability;
if (randomValue <= cumulativeProbability)
{
return config;
}
}
return configs.LastOrDefault();
}
public GiftResponse GetGift(List<GiftConfig> giftConfigs)
{
GiftResponse response = new GiftResponse();