This commit is contained in:
zpc 2026-01-25 15:44:07 +08:00
parent 368d71d977
commit d8b86a4f3a
15 changed files with 671 additions and 37 deletions

1
.gitignore vendored
View File

@ -279,3 +279,4 @@ dist
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*
server/C#/HoneyBox/src/HoneyBox.Api/bin/* server/C#/HoneyBox/src/HoneyBox.Api/bin/*
.kiro/settings/mcp.json

View File

@ -4,14 +4,15 @@
* *
* 更新说明 * 更新说明
* - 2026年迁移至 .NET 10 后端 * - 2026年迁移至 .NET 10 后端
* - 开发环境http://localhost:5238 * - 开发环境http://localhost:5238 http://192.168.195.15:2822
* - 测试环境: * - 测试环境:
* - 生产环境: * - 生产环境:
*/ */
// 测试环境配置 - .NET 10 后端 // 测试环境配置 - .NET 10 后端
const testing = { const testing = {
baseUrl: 'http://localhost:5238', // baseUrl: 'https://app.zpc-xy.com/honey/api',
baseUrl: 'http://192.168.1.24:5238',
imageUrl: 'https://youdas-1308826010.cos.ap-shanghai.myqcloud.com', imageUrl: 'https://youdas-1308826010.cos.ap-shanghai.myqcloud.com',
loginPage: '', loginPage: '',
wxAppId: '' wxAppId: ''

View File

@ -44,6 +44,8 @@
</template> </template>
<script> <script>
import RouterManager from '@/common/router.js'
export default { export default {
name: 'DetailButton', name: 'DetailButton',
props: { props: {
@ -77,9 +79,49 @@ export default {
} }
}, },
methods: { methods: {
//
checkLogin() {
const token = uni.getStorageSync('token');
if (!token) {
//
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
if (currentPage) {
const currentRoute = currentPage.route;
const currentParams = currentPage.options || {};
let redirectPath = '/' + currentRoute;
if (Object.keys(currentParams).length > 0) {
const paramString = Object.keys(currentParams)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(currentParams[key])}`)
.join('&');
redirectPath += '?' + paramString;
}
uni.setStorageSync('redirect', redirectPath);
}
uni.showToast({
title: '请先登录',
icon: 'none'
});
RouterManager.navigateTo('/pages/user/login', {}, 'navigateTo')
.catch(err => {
console.error('登录页面跳转失败:', err);
});
return false;
}
return true;
},
handleButtonClick(num) { handleButtonClick(num) {
console.log(num); console.log(num);
//
if (!this.checkLogin()) {
return;
}
if (num == -1) { if (num == -1) {
this.$emit('button-wx-click', [0, buyNum]); this.$emit('button-wx-click', [0, buyNum]);

View File

@ -1,6 +1,6 @@
{ {
"name" : "友达赏王者", "name" : "友达赏王者",
"appid" : "__UNI__2E6CB39", "appid" : "__UNI__C225F9A",
"description" : "", "description" : "",
"versionName" : "1.0.2", "versionName" : "1.0.2",
"versionCode" : 102, "versionCode" : 102,
@ -140,7 +140,7 @@
"quickapp" : {}, "quickapp" : {},
/* */ /* */
"mp-weixin" : { "mp-weixin" : {
"appid" : "wxa17265f5fe8374b1", "appid" : "wxbbb54463fe289c5a",
"setting" : { "setting" : {
"urlCheck" : false, "urlCheck" : false,
"es6" : false, "es6" : false,

View File

@ -6,7 +6,9 @@
</template> </template>
<script> <script>
import { getDanYeContent } from '@/common/server/config.js'; import {
getDanYeContent
} from '@/common/server/config.js';
export default { export default {
components: {}, components: {},
@ -80,8 +82,13 @@
// API: danye // API: danye
const res = await getDanYeContent(e); const res = await getDanYeContent(e);
if (res.status === 1) { if (res.status === 1) {
if (res.data.content != null) {
this.rule = res.data.content;
} else {
this.rule = res.data; this.rule = res.data;
} }
}
}, },

View File

@ -1,4 +1,4 @@
using System.Security.Claims; using System.Security.Claims;
using HoneyBox.Core.Interfaces; using HoneyBox.Core.Interfaces;
using HoneyBox.Model.Base; using HoneyBox.Model.Base;
using HoneyBox.Model.Models.Invitation; using HoneyBox.Model.Models.Invitation;
@ -33,7 +33,7 @@ public class InvitationController : ControllerBase
/// POST /api/invitation /// POST /api/invitation
/// Requirements: 9.1-9.2 /// Requirements: 9.1-9.2
/// </summary> /// </summary>
[HttpPost("invitation")] [HttpGet("invitation")]
[Authorize] [Authorize]
public async Task<ApiResponse<InvitationInfoResponse>> GetInvitationInfo([FromBody] InvitationInfoRequest? request) public async Task<ApiResponse<InvitationInfoResponse>> GetInvitationInfo([FromBody] InvitationInfoRequest? request)
{ {

View File

@ -1,4 +1,4 @@
using System.Security.Claims; using System.Security.Claims;
using HoneyBox.Core.Interfaces; using HoneyBox.Core.Interfaces;
using HoneyBox.Model.Base; using HoneyBox.Model.Base;
using HoneyBox.Model.Models.Asset; using HoneyBox.Model.Models.Asset;
@ -425,13 +425,13 @@ public class UserController : ControllerBase
/// 获取权益中心信息 /// 获取权益中心信息
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// POST /api/quan_yi /// Get /api/quan_yi
/// ///
/// 获取当前用户的权益中心信息,包含等级列表和可领取奖品 /// 获取当前用户的权益中心信息,包含等级列表和可领取奖品
/// Requirements: 8.1 /// Requirements: 8.1
/// </remarks> /// </remarks>
/// <returns>权益中心数据</returns> /// <returns>权益中心数据</returns>
[HttpPost("quan_yi")] [HttpGet("quan_yi")]
[Authorize] [Authorize]
[ProducesResponseType(typeof(ApiResponse<QuanYiResponse>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse<QuanYiResponse>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<QuanYiResponse>), StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(ApiResponse<QuanYiResponse>), StatusCodes.Status401Unauthorized)]

View File

@ -1,4 +1,4 @@
{ {
"profiles": { "profiles": {
"http": { "http": {
"commandName": "Project", "commandName": "Project",
@ -6,7 +6,7 @@
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
}, },
"dotnetRunMessages": true, "dotnetRunMessages": true,
"applicationUrl": "http://localhost:5238" "applicationUrl": "http://*:5238"
}, },
"Container (Dockerfile)": { "Container (Dockerfile)": {
"commandName": "Docker", "commandName": "Docker",

View File

@ -1,5 +1,6 @@
using HoneyBox.Core.Interfaces; using HoneyBox.Core.Interfaces;
using HoneyBox.Model.Data; using HoneyBox.Model.Data;
using HoneyBox.Model.Entities;
using HoneyBox.Model.Models; using HoneyBox.Model.Models;
using HoneyBox.Model.Models.Goods; using HoneyBox.Model.Models.Goods;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -1872,15 +1873,38 @@ public class GoodsService : IGoodsService
if (user != null) if (user != null)
{ {
// 查询领主的排名记录
var kingRank = await _dbContext.GoodsKingRanks
.Where(r => r.UserId == goods.KingUserId && r.GoodsId == goodsId)
.OrderByDescending(r => r.Id)
.Select(r => new { r.Count, r.ZNums, r.OrderListId })
.FirstOrDefaultAsync();
// 获取中奖奖品信息
string jiangTitle = "";
string jiangImg = "";
if (kingRank != null && kingRank.OrderListId > 0)
{
var orderItem = await _dbContext.OrderItems
.Where(oi => oi.Id == kingRank.OrderListId)
.Select(oi => new { oi.GoodslistTitle, oi.GoodslistImgurl })
.FirstOrDefaultAsync();
if (orderItem != null)
{
jiangTitle = orderItem.GoodslistTitle ?? "";
jiangImg = FormatImageUrl(orderItem.GoodslistImgurl);
}
}
kingUser = new LingZhuKingUserDto kingUser = new LingZhuKingUserDto
{ {
Id = user.Id, Id = user.Id,
Nickname = user.Nickname ?? "", Nickname = user.Nickname ?? "",
HeadImg = FormatImageUrl(user.HeadImg), HeadImg = FormatImageUrl(user.HeadImg),
JiangTitle = "", JiangTitle = jiangTitle,
JiangImg = "", JiangImg = jiangImg,
Count = 0, Count = kingRank?.Count ?? 0,
ZNums = 0 ZNums = kingRank?.ZNums ?? 0
}; };
} }
} }
@ -1913,7 +1937,101 @@ public class GoodsService : IGoodsService
ShangInfo = gi.ShangId.HasValue && shangInfos.TryGetValue(gi.ShangId.Value, out var info) ? info : null ShangInfo = gi.ShangId.HasValue && shangInfos.TryGetValue(gi.ShangId.Value, out var info) ? info : null
}).ToList(); }).ToList();
// 4. 返回结果 (goods_king_rank表未迁移返回空列表) // 4. 获取挑战者列表或领主记录
var listResponse = new PageResponse<LingZhuListItemDto>
{
Data = new List<LingZhuListItemDto>(),
Total = 0,
Page = page,
PageSize = pageSize,
LastPage = 0
};
if (type == 1)
{
// 挑战人数 - 按用户分组,获取每个用户最新的一条记录
var distinctUserIds = await _dbContext.GoodsKingRanks
.Where(r => r.GoodsId == goodsId)
.Select(r => r.UserId)
.Distinct()
.ToListAsync();
var total = distinctUserIds.Count;
var lastPage = (int)Math.Ceiling((double)total / pageSize);
// 获取分页后的用户ID
var pagedUserIds = distinctUserIds
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToList();
// 获取这些用户的最新记录
var rankList = new List<GoodsKingRank>();
foreach (var userId in pagedUserIds)
{
var latestRank = await _dbContext.GoodsKingRanks
.Where(r => r.GoodsId == goodsId && r.UserId == userId)
.OrderByDescending(r => r.Id)
.FirstOrDefaultAsync();
if (latestRank != null)
{
rankList.Add(latestRank);
}
}
var userList = await _dbContext.Users
.Where(u => pagedUserIds.Contains(u.Id))
.Select(u => new { u.Id, u.Nickname, u.HeadImg })
.ToListAsync();
var users = userList.ToDictionary(u => u.Id, u => new { u.Nickname, u.HeadImg });
listResponse.Data = rankList.Select(r => new LingZhuListItemDto
{
UserId = r.UserId,
Nickname = users.TryGetValue(r.UserId, out var u) ? u.Nickname ?? "" : "",
HeadImg = users.TryGetValue(r.UserId, out var u2) ? FormatImageUrl(u2.HeadImg) : "",
Addtime = r.Addtime,
EndTime = r.EndTime,
Time = ""
}).ToList();
listResponse.Total = total;
listResponse.LastPage = lastPage;
}
else if (type == 2)
{
// 领主记录
var query = _dbContext.GoodsKingRanks
.Where(r => r.GoodsId == goodsId);
var total = await query.CountAsync();
var lastPage = (int)Math.Ceiling((double)total / pageSize);
var rankList = await query
.OrderByDescending(r => r.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
var userIds = rankList.Select(r => r.UserId).Distinct().ToList();
var userList = await _dbContext.Users
.Where(u => userIds.Contains(u.Id))
.Select(u => new { u.Id, u.Nickname, u.HeadImg })
.ToListAsync();
var users = userList.ToDictionary(u => u.Id, u => new { u.Nickname, u.HeadImg });
listResponse.Data = rankList.Select(r => new LingZhuListItemDto
{
UserId = r.UserId,
Nickname = users.TryGetValue(r.UserId, out var u) ? u.Nickname ?? "" : "",
HeadImg = users.TryGetValue(r.UserId, out var u2) ? FormatImageUrl(u2.HeadImg) : "",
Addtime = r.Addtime,
EndTime = r.EndTime,
Time = CalculateTimeSpan(r.Addtime, r.EndTime > 0 ? r.EndTime : (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds())
}).ToList();
listResponse.Total = total;
listResponse.LastPage = lastPage;
}
return new LingZhuKingResponseDto return new LingZhuKingResponseDto
{ {
Goods = new LingZhuGoodsDto Goods = new LingZhuGoodsDto
@ -1923,15 +2041,27 @@ public class GoodsService : IGoodsService
LingzhuShangId = goods.LingzhuShangId LingzhuShangId = goods.LingzhuShangId
}, },
KingUser = kingUser, KingUser = kingUser,
List = new PageResponse<LingZhuListItemDto> List = listResponse,
{
Data = new List<LingZhuListItemDto>(),
Total = 0,
Page = page,
PageSize = pageSize,
LastPage = 0
},
LingGoodsList = lingGoodsListDto LingGoodsList = lingGoodsListDto
}; };
} }
/// <summary>
/// 计算时间间隔的可读字符串
/// </summary>
private static string CalculateTimeSpan(int startTime, int endTime)
{
var seconds = endTime - startTime;
if (seconds < 0) return "0秒";
var days = seconds / 86400;
var hours = (seconds % 86400) / 3600;
var minutes = (seconds % 3600) / 60;
var secs = seconds % 60;
if (days > 0) return $"{days}天{hours}小时";
if (hours > 0) return $"{hours}小时{minutes}分钟";
if (minutes > 0) return $"{minutes}分钟{secs}秒";
return $"{secs}秒";
}
} }

View File

@ -65,16 +65,22 @@ public class WechatService : IWechatService
try try
{ {
// 记录配置信息(脱敏) // 从数据库获取微信配置
var maskedAppId = _wechatSettings.AppId?.Length > 8 var wechatConfig = await GetWechatSettingFromDbAsync();
? $"{_wechatSettings.AppId.Substring(0, 4)}****{_wechatSettings.AppId.Substring(_wechatSettings.AppId.Length - 4)}" var appId = wechatConfig?.AppId ?? _wechatSettings.AppId;
: "未配置"; var appSecret = wechatConfig?.AppSecret ?? _wechatSettings.AppSecret;
var maskedSecret = string.IsNullOrEmpty(_wechatSettings.AppSecret)
? "未配置"
: $"{_wechatSettings.AppSecret.Substring(0, 4)}****";
_logger.LogInformation("[微信登录] 配置信息: AppId={AppId}, AppSecret={AppSecret}", maskedAppId, maskedSecret);
var url = $"{WechatCodeToSessionUrl}?appid={_wechatSettings.AppId}&secret={_wechatSettings.AppSecret}&js_code={code}&grant_type=authorization_code"; // 记录配置信息(脱敏)
var maskedAppId = appId?.Length > 8
? $"{appId.Substring(0, 4)}****{appId.Substring(appId.Length - 4)}"
: "未配置";
var maskedSecret = string.IsNullOrEmpty(appSecret)
? "未配置"
: $"{appSecret.Substring(0, 4)}****";
_logger.LogInformation("[微信登录] 配置信息: AppId={AppId}, AppSecret={AppSecret}, 来源={Source}",
maskedAppId, maskedSecret, wechatConfig != null ? "数据库" : "appsettings");
var url = $"{WechatCodeToSessionUrl}?appid={appId}&secret={appSecret}&js_code={code}&grant_type=authorization_code";
_logger.LogInformation("[微信登录] 调用微信API: {Url}", WechatCodeToSessionUrl); _logger.LogInformation("[微信登录] 调用微信API: {Url}", WechatCodeToSessionUrl);
@ -590,6 +596,106 @@ public class WechatService : IWechatService
} }
} }
/// <summary>
/// 从数据库获取微信小程序配置
/// 优先从 miniprogram_setting 读取默认小程序配置,如果没有则回退到 wechat_setting
/// </summary>
private async Task<WechatSettings?> GetWechatSettingFromDbAsync()
{
try
{
// 1. 优先从 miniprogram_setting 读取默认小程序配置
var miniprogramConfig = await _dbContext.Configs
.Where(c => c.ConfigKey == "miniprogram_setting")
.Select(c => c.ConfigValue)
.FirstOrDefaultAsync();
if (!string.IsNullOrEmpty(miniprogramConfig))
{
var config = JsonSerializer.Deserialize<JsonElement>(miniprogramConfig);
if (config.TryGetProperty("miniprograms", out var miniprograms) && miniprograms.ValueKind == JsonValueKind.Array)
{
// 查找默认小程序配置 (is_default = 1)
foreach (var mp in miniprograms.EnumerateArray())
{
var isDefault = mp.TryGetProperty("is_default", out var isDefaultProp) &&
(isDefaultProp.ValueKind == JsonValueKind.Number ? isDefaultProp.GetInt32() == 1 : isDefaultProp.GetString() == "1");
if (isDefault)
{
var appId = mp.TryGetProperty("appid", out var appIdProp) ? appIdProp.GetString() : null;
var appSecret = mp.TryGetProperty("appsecret", out var appSecretProp) ? appSecretProp.GetString() : null;
if (!string.IsNullOrEmpty(appId) && !string.IsNullOrEmpty(appSecret))
{
_logger.LogDebug("[微信配置] 从 miniprogram_setting 读取默认小程序配置: AppId={AppId}",
appId.Length > 8 ? $"{appId.Substring(0, 4)}****{appId.Substring(appId.Length - 4)}" : appId);
return new WechatSettings
{
AppId = appId,
AppSecret = appSecret
};
}
}
}
// 如果没有默认配置,使用第一个小程序配置
var firstMp = miniprograms.EnumerateArray().FirstOrDefault();
if (firstMp.ValueKind == JsonValueKind.Object)
{
var appId = firstMp.TryGetProperty("appid", out var appIdProp) ? appIdProp.GetString() : null;
var appSecret = firstMp.TryGetProperty("appsecret", out var appSecretProp) ? appSecretProp.GetString() : null;
if (!string.IsNullOrEmpty(appId) && !string.IsNullOrEmpty(appSecret))
{
_logger.LogDebug("[微信配置] 从 miniprogram_setting 读取第一个小程序配置: AppId={AppId}",
appId.Length > 8 ? $"{appId.Substring(0, 4)}****{appId.Substring(appId.Length - 4)}" : appId);
return new WechatSettings
{
AppId = appId,
AppSecret = appSecret
};
}
}
}
}
// 2. 回退到 wechat_setting旧版单一配置
var wechatSettingConfig = await _dbContext.Configs
.Where(c => c.ConfigKey == "wechat_setting")
.Select(c => c.ConfigValue)
.FirstOrDefaultAsync();
if (!string.IsNullOrEmpty(wechatSettingConfig))
{
var config = JsonSerializer.Deserialize<JsonElement>(wechatSettingConfig);
var appId = config.TryGetProperty("appid", out var appIdProp) ? appIdProp.GetString() : null;
var appSecret = config.TryGetProperty("appSecret", out var appSecretProp) ? appSecretProp.GetString() : null;
if (!string.IsNullOrEmpty(appId) && !string.IsNullOrEmpty(appSecret))
{
_logger.LogDebug("[微信配置] 从 wechat_setting 读取配置: AppId={AppId}",
appId.Length > 8 ? $"{appId.Substring(0, 4)}****{appId.Substring(appId.Length - 4)}" : appId);
return new WechatSettings
{
AppId = appId,
AppSecret = appSecret
};
}
}
_logger.LogDebug("[微信配置] 数据库中未找到有效的微信配置");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "[微信配置] 从数据库读取配置失败");
}
return null;
}
/// <summary> /// <summary>
/// 获取商户配置(优先从数据库读取) /// 获取商户配置(优先从数据库读取)
/// </summary> /// </summary>

View File

@ -122,6 +122,8 @@ public partial class HoneyBoxDbContext : DbContext
public virtual DbSet<EquityLevelPrize> EquityLevelPrizes { get; set; } public virtual DbSet<EquityLevelPrize> EquityLevelPrizes { get; set; }
public virtual DbSet<GoodsKingRank> GoodsKingRanks { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
// Connection string is configured in Program.cs via dependency injection // Connection string is configured in Program.cs via dependency injection
@ -3371,6 +3373,52 @@ public partial class HoneyBoxDbContext : DbContext
.HasConstraintName("fk_equity_level_prizes_equity_levels"); .HasConstraintName("fk_equity_level_prizes_equity_levels");
}); });
modelBuilder.Entity<GoodsKingRank>(entity =>
{
entity.HasKey(e => e.Id).HasName("pk_goods_king_ranks");
entity.ToTable("goods_king_ranks", tb => tb.HasComment("领主赏记录表,记录领主占领和挑战信息"));
entity.HasIndex(e => e.UserId, "ix_goods_king_ranks_user_id");
entity.HasIndex(e => e.GoodsId, "ix_goods_king_ranks_goods_id");
entity.HasIndex(e => new { e.UserId, e.GoodsId }, "ix_goods_king_ranks_user_goods");
entity.Property(e => e.Id)
.HasComment("主键ID")
.HasColumnName("id");
entity.Property(e => e.UserId)
.HasComment("用户ID领主")
.HasColumnName("user_id");
entity.Property(e => e.GoodsId)
.HasComment("商品ID")
.HasColumnName("goods_id");
entity.Property(e => e.Count)
.HasDefaultValue(0)
.HasComment("占领时的抽奖次数")
.HasColumnName("count");
entity.Property(e => e.ZNums)
.HasDefaultValue(0)
.HasComment("被挑战次数")
.HasColumnName("z_nums");
entity.Property(e => e.Money)
.HasDefaultValue(0m)
.HasComment("领主收益金额")
.HasColumnType("decimal(10, 2)")
.HasColumnName("money");
entity.Property(e => e.OrderListId)
.HasDefaultValue(0)
.HasComment("关联的中奖记录ID")
.HasColumnName("order_list_id");
entity.Property(e => e.Addtime)
.HasDefaultValue(0)
.HasComment("成为领主的时间戳")
.HasColumnName("addtime");
entity.Property(e => e.EndTime)
.HasDefaultValue(0)
.HasComment("结束领主的时间戳")
.HasColumnName("end_time");
});
OnModelCreatingPartial(modelBuilder); OnModelCreatingPartial(modelBuilder);
} }

View File

@ -0,0 +1,54 @@
using System;
namespace HoneyBox.Model.Entities;
/// <summary>
/// 领主赏记录表,记录领主占领和挑战信息
/// </summary>
public partial class GoodsKingRank
{
/// <summary>
/// 主键ID
/// </summary>
public int Id { get; set; }
/// <summary>
/// 用户ID领主
/// </summary>
public int UserId { get; set; }
/// <summary>
/// 商品ID
/// </summary>
public int GoodsId { get; set; }
/// <summary>
/// 占领时的抽奖次数(多少发晋升领主)
/// </summary>
public int Count { get; set; }
/// <summary>
/// 被挑战次数(其他人抽奖次数)
/// </summary>
public int ZNums { get; set; }
/// <summary>
/// 领主收益金额
/// </summary>
public decimal Money { get; set; }
/// <summary>
/// 关联的中奖记录ID
/// </summary>
public int OrderListId { get; set; }
/// <summary>
/// 成为领主的时间戳
/// </summary>
public int Addtime { get; set; }
/// <summary>
/// 结束领主的时间戳0表示当前领主
/// </summary>
public int EndTime { get; set; }
}

View File

@ -531,7 +531,7 @@ public class GoodsListItemDto
[JsonPropertyName("surplus_stock")] [JsonPropertyName("surplus_stock")]
public int SurplusStock { get; set; } public int SurplusStock { get; set; }
[JsonPropertyName("img_url")] [JsonPropertyName("imgurl")]
public string ImgUrl { get; set; } = string.Empty; public string ImgUrl { get; set; } = string.Empty;
[JsonPropertyName("goods_type")] [JsonPropertyName("goods_type")]

View File

@ -0,0 +1,48 @@
-- 创建 goods_king_ranks 表
-- 领主赏记录表,记录领主占领和挑战信息
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='goods_king_ranks' AND xtype='U')
BEGIN
CREATE TABLE goods_king_ranks (
id INT IDENTITY(1,1) NOT NULL,
user_id INT NOT NULL,
goods_id INT NOT NULL,
count INT NOT NULL DEFAULT 0,
z_nums INT NOT NULL DEFAULT 0,
money DECIMAL(10,2) NOT NULL DEFAULT 0,
order_list_id INT NOT NULL DEFAULT 0,
addtime INT NOT NULL DEFAULT 0,
end_time INT NOT NULL DEFAULT 0,
CONSTRAINT pk_goods_king_ranks PRIMARY KEY CLUSTERED (id)
);
-- 创建索引
CREATE INDEX ix_goods_king_ranks_user_id ON goods_king_ranks(user_id);
CREATE INDEX ix_goods_king_ranks_goods_id ON goods_king_ranks(goods_id);
CREATE INDEX ix_goods_king_ranks_user_goods ON goods_king_ranks(user_id, goods_id);
-- 添加表注释
EXEC sys.sp_addextendedproperty
@name=N'MS_Description',
@value=N'领主赏记录表,记录领主占领和挑战信息',
@level0type=N'SCHEMA', @level0name=N'dbo',
@level1type=N'TABLE', @level1name=N'goods_king_ranks';
-- 添加列注释
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'主键ID', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'goods_king_ranks', @level2type=N'COLUMN', @level2name=N'id';
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'用户ID领主', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'goods_king_ranks', @level2type=N'COLUMN', @level2name=N'user_id';
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'商品ID', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'goods_king_ranks', @level2type=N'COLUMN', @level2name=N'goods_id';
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'占领时的抽奖次数', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'goods_king_ranks', @level2type=N'COLUMN', @level2name=N'count';
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'被挑战次数', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'goods_king_ranks', @level2type=N'COLUMN', @level2name=N'z_nums';
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'领主收益金额', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'goods_king_ranks', @level2type=N'COLUMN', @level2name=N'money';
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'关联的中奖记录ID', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'goods_king_ranks', @level2type=N'COLUMN', @level2name=N'order_list_id';
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'成为领主的时间戳', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'goods_king_ranks', @level2type=N'COLUMN', @level2name=N'addtime';
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'结束领主的时间戳', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'goods_king_ranks', @level2type=N'COLUMN', @level2name=N'end_time';
PRINT '表 goods_king_ranks 创建成功';
END
ELSE
BEGIN
PRINT '表 goods_king_ranks 已存在';
END
GO

View File

@ -0,0 +1,197 @@
/**
* 领主赏记录表迁移脚本
* MySQL goods_king_rank 表迁移到 SQL Server goods_king_ranks
*/
const mysql = require('mysql2/promise');
const sql = require('mssql');
// MySQL 配置
const mysqlConfig = {
host: '192.168.195.16',
port: 1887,
user: 'root',
password: 'Dbt@com@123',
database: 'youdas',
charset: 'utf8mb4'
};
// SQL Server 配置
const sqlServerConfig = {
server: '192.168.195.15',
port: 1433,
user: 'sa',
password: 'Dbt@com@123',
database: 'honey_box',
options: {
encrypt: false,
trustServerCertificate: true
}
};
// 批量大小
const BATCH_SIZE = 1000;
async function createTable(pool) {
console.log('创建 goods_king_ranks 表...');
const createTableSql = `
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='goods_king_ranks' AND xtype='U')
BEGIN
CREATE TABLE goods_king_ranks (
id INT IDENTITY(1,1) PRIMARY KEY,
user_id INT NOT NULL,
goods_id INT NOT NULL,
count INT NOT NULL DEFAULT 0,
z_nums INT NOT NULL DEFAULT 0,
money DECIMAL(10,2) NOT NULL DEFAULT 0,
order_list_id INT NOT NULL DEFAULT 0,
addtime INT NOT NULL DEFAULT 0,
end_time INT NOT NULL DEFAULT 0,
CONSTRAINT pk_goods_king_ranks PRIMARY KEY CLUSTERED (id)
);
CREATE INDEX ix_goods_king_ranks_user_id ON goods_king_ranks(user_id);
CREATE INDEX ix_goods_king_ranks_goods_id ON goods_king_ranks(goods_id);
CREATE INDEX ix_goods_king_ranks_user_goods ON goods_king_ranks(user_id, goods_id);
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'领主赏记录表,记录领主占领和挑战信息', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'goods_king_ranks';
END
`;
await pool.request().query(createTableSql);
console.log('表创建完成');
}
async function getSourceCount(mysqlConn) {
const [rows] = await mysqlConn.execute('SELECT COUNT(*) as count FROM goods_king_rank');
return rows[0].count;
}
async function getTargetCount(pool) {
const result = await pool.request().query('SELECT COUNT(*) as count FROM goods_king_ranks');
return result.recordset[0].count;
}
async function migrateData(mysqlConn, sqlPool) {
const sourceCount = await getSourceCount(mysqlConn);
console.log(`源表记录数: ${sourceCount}`);
if (sourceCount === 0) {
console.log('源表无数据,跳过迁移');
return;
}
// 清空目标表
console.log('清空目标表...');
await sqlPool.request().query('TRUNCATE TABLE goods_king_ranks');
// 开启 IDENTITY_INSERT
await sqlPool.request().query('SET IDENTITY_INSERT goods_king_ranks ON');
let offset = 0;
let migratedCount = 0;
while (offset < sourceCount) {
console.log(`迁移进度: ${offset}/${sourceCount}`);
// 从 MySQL 读取数据
const [rows] = await mysqlConn.execute(
`SELECT id, user_id, goods_id, count, z_nums, money, order_list_id, addtime, end_time
FROM goods_king_rank
ORDER BY id
LIMIT ? OFFSET ?`,
[BATCH_SIZE, offset]
);
if (rows.length === 0) break;
// 批量插入到 SQL Server
const table = new sql.Table('goods_king_ranks');
table.create = false;
table.columns.add('id', sql.Int, { nullable: false });
table.columns.add('user_id', sql.Int, { nullable: false });
table.columns.add('goods_id', sql.Int, { nullable: false });
table.columns.add('count', sql.Int, { nullable: false });
table.columns.add('z_nums', sql.Int, { nullable: false });
table.columns.add('money', sql.Decimal(10, 2), { nullable: false });
table.columns.add('order_list_id', sql.Int, { nullable: false });
table.columns.add('addtime', sql.Int, { nullable: false });
table.columns.add('end_time', sql.Int, { nullable: false });
for (const row of rows) {
table.rows.add(
row.id,
row.user_id || 0,
row.goods_id || 0,
row.count || 0,
row.z_nums || 0,
row.money || 0,
row.order_list_id || 0,
row.addtime || 0,
row.end_time || 0
);
}
const request = sqlPool.request();
await request.bulk(table);
migratedCount += rows.length;
offset += BATCH_SIZE;
}
// 关闭 IDENTITY_INSERT
await sqlPool.request().query('SET IDENTITY_INSERT goods_king_ranks OFF');
// 重置自增序列
const maxIdResult = await sqlPool.request().query('SELECT MAX(id) as maxId FROM goods_king_ranks');
const maxId = maxIdResult.recordset[0].maxId || 0;
await sqlPool.request().query(`DBCC CHECKIDENT ('goods_king_ranks', RESEED, ${maxId})`);
console.log(`迁移完成,共迁移 ${migratedCount} 条记录`);
}
async function validate(mysqlConn, sqlPool) {
console.log('\n验证迁移结果...');
const sourceCount = await getSourceCount(mysqlConn);
const targetCount = await getTargetCount(sqlPool);
console.log(`源表记录数: ${sourceCount}`);
console.log(`目标表记录数: ${targetCount}`);
if (sourceCount === targetCount) {
console.log('✓ 记录数一致');
} else {
console.log('✗ 记录数不一致!');
}
}
async function main() {
let mysqlConn = null;
let sqlPool = null;
try {
console.log('连接 MySQL...');
mysqlConn = await mysql.createConnection(mysqlConfig);
console.log('MySQL 连接成功');
console.log('连接 SQL Server...');
sqlPool = await sql.connect(sqlServerConfig);
console.log('SQL Server 连接成功');
await createTable(sqlPool);
await migrateData(mysqlConn, sqlPool);
await validate(mysqlConn, sqlPool);
console.log('\n迁移任务完成');
} catch (error) {
console.error('迁移失败:', error);
process.exit(1);
} finally {
if (mysqlConn) await mysqlConn.end();
if (sqlPool) await sqlPool.close();
}
}
main();