C#实现一个带过期时间的积分系统的完整解决方案

方式一:C#

// Model/IntegralRecord.cs
public class IntegralRecord
{
    public long Id { get; set; }
    public short Type { get; set; }
    public string OriginalId { get; set; }
    public int Value { get; set; }
    public DateTime ExpireTime { get; set; }
    public string Reason { get; set; }
    public string UserId { get; set; }
    public bool Deleted { get; set; }
    public DateTime CreateTime { get; set; }
    public DateTime UpdateTime { get; set; }
}

// Model/UsableIntegral.cs
public class UsableIntegral
{
    public long Id { get; set; }
    public long RecordId { get; set; }
    public int Value { get; set; }
    public DateTime ExpireTime { get; set; }
    public string UserId { get; set; }
    public bool Deleted { get; set; }
    public DateTime CreateTime { get; set; }
    public DateTime UpdateTime { get; set; }
}

// Model/ReduceIntegralDetail.cs
public class ReduceIntegralDetail
{
    public long Id { get; set; }
    public long ReduceId { get; set; }
    public long AddId { get; set; }
    public int UsedValue { get; set; }
    public DateTime ExpireTime { get; set; }
    public string UserId { get; set; }
    public bool Deleted { get; set; }
    public DateTime CreateTime { get; set; }
    public DateTime UpdateTime { get; set; }
}

// Service/IntegralService.cs
public class IntegralService
{
    private readonly DbContext _context;
    
    public IntegralService(DbContext context)
    {
        _context = context;
    }

    // 获取用户当前可用总积分
    public async Task<int> GetUserTotalIntegral(string userId)
    {
        return await _context.UsableIntegrals
            .Where(u => u.UserId == userId && !u.Deleted)
            .SumAsync(u => u.Value);
    }

    // 获取用户积分明细
    public async Task<List<IntegralDetailDto>> GetUserIntegralDetails(string userId)
    {
        var records = await _context.IntegralRecords
            .Where(r => r.UserId == userId)
            .OrderByDescending(r => r.CreateTime)
            .Select(r => new IntegralDetailDto
            {
                Id = r.Id,
                Type = r.Type,
                Value = r.Value,
                ExpireTime = r.ExpireTime,
                Reason = r.Reason,
                CreateTime = r.CreateTime
            })
            .ToListAsync();

        return records;
    }

    // 处理积分过期
    public async Task HandleExpiredIntegral()
    {
        var now = DateTime.Now;
        var expiredIntegrals = await _context.UsableIntegrals
            .Where(u => !u.Deleted && u.ExpireTime <= now)
            .ToListAsync();

        foreach (var integral in expiredIntegrals)
        {
            // 标记为已删除
            integral.Deleted = true;
            integral.UpdateTime = now;

            // 添加过期记录
            var record = new IntegralRecord
            {
                Type = 3, // 过期类型
                Value = -integral.Value,
                ExpireTime = now,
                Reason = "积分过期",
                UserId = integral.UserId,
                CreateTime = now,
                UpdateTime = now
            };
            _context.IntegralRecords.Add(record);

            // 添加扣减详情
            var detail = new ReduceIntegralDetail
            {
                ReduceId = record.Id,
                AddId = integral.RecordId,
                UsedValue = integral.Value,
                ExpireTime = integral.ExpireTime,
                UserId = integral.UserId,
                CreateTime = now,
                UpdateTime = now
            };
            _context.ReduceIntegralDetails.Add(detail);
        }

        await _context.SaveChangesAsync();
    }

    // 使用积分
    public async Task<bool> UseIntegral(string userId, int value, string reason)
    {
        using var transaction = await _context.Database.BeginTransactionAsync();
        try
        {
            var availableIntegrals = await _context.UsableIntegrals
                .Where(u => u.UserId == userId && !u.Deleted)
                .OrderBy(u => u.ExpireTime)
                .ThenBy(u => u.Value)
                .ToListAsync();

            var totalAvailable = availableIntegrals.Sum(u => u.Value);
            if (totalAvailable < value)
            {
                return false;
            }

            var now = DateTime.Now;
            var remainingValue = value;
            var reduceRecord = new IntegralRecord
            {
                Type = 2, // 使用积分类型
                Value = -value,
                ExpireTime = now,
                Reason = reason,
                UserId = userId,
                CreateTime = now,
                UpdateTime = now
            };
            _context.IntegralRecords.Add(reduceRecord);
            await _context.SaveChangesAsync();

            foreach (var integral in availableIntegrals)
            {
                if (remainingValue <= 0) break;

                var useValue = Math.Min(remainingValue, integral.Value);
                remainingValue -= useValue;

                if (useValue == integral.Value)
                {
                    integral.Deleted = true;
                }
                else
                {
                    integral.Value -= useValue;
                }
                integral.UpdateTime = now;

                var detail = new ReduceIntegralDetail
                {
                    ReduceId = reduceRecord.Id,
                    AddId = integral.RecordId,
                    UsedValue = useValue,
                    ExpireTime = integral.ExpireTime,
                    UserId = userId,
                    CreateTime = now,
                    UpdateTime = now
                };
                _context.ReduceIntegralDetails.Add(detail);
            }

            await _context.SaveChangesAsync();
            await transaction.CommitAsync();
            return true;
        }
        catch
        {
            await transaction.RollbackAsync();
            throw;
        }
    }
}

// Controller/IntegralController.cs
[ApiController]
[Route("api/[controller]")]
public class IntegralController : ControllerBase
{
    private readonly IntegralService _integralService;

    public IntegralController(IntegralService integralService)
    {
        _integralService = integralService;
    }

    [HttpGet("total/{userId}")]
    public async Task<ActionResult<int>> GetTotalIntegral(string userId)
    {
        var total = await _integralService.GetUserTotalIntegral(userId);
        return Ok(total);
    }

    [HttpGet("details/{userId}")]
    public async Task<ActionResult<List<IntegralDetailDto>>> GetIntegralDetails(string userId)
    {
        var details = await _integralService.GetUserIntegralDetails(userId);
        return Ok(details);
    }

    [HttpPost("use")]
    public async Task<ActionResult> UseIntegral([FromBody] UseIntegralRequest request)
    {
        var success = await _integralService.UseIntegral(request.UserId, request.Value, request.Reason);
        if (!success)
        {
            return BadRequest("积分不足");
        }
        return Ok();
    }
}

方式二:SQL Server 存储过程方式

-- 创建获取用户总积分的存储过程
CREATE PROCEDURE sp_GetUserTotalIntegral
    @UserId VARCHAR(40)
AS
BEGIN
    SELECT SUM(Value) as TotalIntegral
    FROM UsableIntegral
    WHERE UserId = @UserId 
    AND Deleted = 0;
END;

-- 创建获取用户积分明细的存储过程
CREATE PROCEDURE sp_GetUserIntegralDetails
    @UserId VARCHAR(40)
AS
BEGIN
    SELECT 
        Id,
        Type,
        Value,
        ExpireTime,
        Reason,
        CreateTime
    FROM IntegralRecord
    WHERE UserId = @UserId
    ORDER BY CreateTime DESC;
END;

-- 创建处理过期积分的存储过程
CREATE PROCEDURE sp_HandleExpiredIntegral
AS
BEGIN
    BEGIN TRANSACTION;
    
    DECLARE @Now DATETIME = GETDATE();
    
    -- 找出过期的积分记录
    DECLARE @ExpiredIntegrals TABLE (
        Id BIGINT,
        RecordId BIGINT,
        Value INT,
        ExpireTime DATETIME,
        UserId VARCHAR(40)
    );
    
    INSERT INTO @ExpiredIntegrals
    SELECT Id, RecordId, Value, ExpireTime, UserId
    FROM UsableIntegral
    WHERE Deleted = 0 
    AND ExpireTime <= @Now;
    
    -- 标记过期积分为已删除
    UPDATE UsableIntegral
    SET Deleted = 1,
        UpdateTime = @Now
    FROM UsableIntegral u
    INNER JOIN @ExpiredIntegrals e ON u.Id = e.Id;
    
    -- 添加过期记录
    INSERT INTO IntegralRecord (
        Type,
        Value,
        ExpireTime,
        Reason,
        UserId,
        CreateTime,
        UpdateTime
    )
    SELECT 
        3, -- 过期类型
        -Value,
        @Now,
        '积分过期',
        UserId,
        @Now,
        @Now
    FROM @ExpiredIntegrals;
    
    -- 获取新插入的记录ID
    DECLARE @LastInsertId TABLE (Id BIGINT);
    INSERT INTO @LastInsertId
    SELECT SCOPE_IDENTITY();
    
    -- 添加扣减详情
    INSERT INTO ReduceIntegralDetail (
        ReduceId,
        AddId,
        UsedValue,
        ExpireTime,
        UserId,
        CreateTime,
        UpdateTime
    )
    SELECT 
        (SELECT Id FROM @LastInsertId),
        RecordId,
        Value,
        ExpireTime,
        UserId,
        @Now,
        @Now
    FROM @ExpiredIntegrals;
    
    COMMIT TRANSACTION;
END;

前端

// types.ts
interface IntegralDetail {
    id: number;
    type: number;
    value: number;
    expireTime: string;
    reason: string;
    createTime: string;
}

interface UseIntegralRequest {
    userId: string;
    value: number;
    reason: string;
}

// api.ts
const api = {
    async getUserTotalIntegral(userId: string): Promise<number> {
        const response = await fetch(`/api/integral/total/${userId}`);
        return response.json();
    },

    async getUserIntegralDetails(userId: string): Promise<IntegralDetail[]> {
        const response = await fetch(`/api/integral/details/${userId}`);
        return response.json();
    },

    async useIntegral(request: UseIntegralRequest): Promise<void> {
        const response = await fetch('/api/integral/use', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(request),
        });
        if (!response.ok) {
            throw new Error('Failed to use integral');
        }
    },
};

// IntegralPage.tsx
import React, { useEffect, useState } from 'react';

export const IntegralPage: React.FC = () => {
    const [totalIntegral, setTotalIntegral] = useState(0);
    const [details, setDetails] = useState<IntegralDetail[]>([]);
    const [loading, setLoading] = useState(true);
    const userId = '1'; // 假设用户ID为1

    useEffect(() => {
        const loadData = async () => {
            try {
                const [total, detailsList] = await Promise.all([
                    api.getUserTotalIntegral(userId),
                    api.getUserIntegralDetails(userId)
                ]);
                setTotalIntegral(total);
                setDetails(detailsList);
            } catch (error) {
                console.error('Failed to load integral data:', error);
            } finally {
                setLoading(false);
            }
        };

        loadData();
    }, []);

    const handleUseIntegral = async (value: number) => {
        try {
            await api.useIntegral({
                userId,
                value,
                reason: '积分兑换'
            });
            // 刷新数据
            const [total, detailsList] = await Promise.all([
                api.getUserTotalIntegral(userId),
                api.getUserIntegralDetails(userId)
            ]);
            setTotalIntegral(total);
            setDetails(detailsList);
        } catch (error) {
            console.error('Failed to use integral:', error);
        }
    };

    if (loading) {
        return <div>Loading...</div>;
    }

    return (
        <div className="p-4">
            <h1 className="text-2xl font-bold mb-4">我的积分</h1>
            
            <div className="bg-white rounded-lg shadow p-6 mb-6">
                <div className="text-lg font-semibold">当前可用积分</div>
                <div className="text-3xl font-bold text-blue-600">{totalIntegral}</div>
            </div>

            <div className="bg-white rounded-lg shadow">
                <div className="p-4 border-b">
                    <h2 className="text-lg font-semibold">积分明细</h2>
                </div>
                <div className="overflow-x-auto">
                    <table className="w-full">
                        <thead>
                            <tr className="bg-gray-50">
                                <th className="px-6 py-3 text-left">类型</th>
                                <th className="px-6 py-3 text-left">积分值</th>
                                <th className="px-6 py-3 text-left">过期时间</th>
                                <th className="px-6 py-3 text-left">原因</th>
                                <th className="px-6 py-3 text-left">时间</th>
                            </tr>
                        </thead>
                        <tbody>
                            {details.map(detail => (
                                <tr key={detail.id} className="border-t">
                                    <td className="px-6 py-4">
                                        {detail.type === 1 ? '获取' : 
                                         detail.type === 2 ? '使用' : '过期'}
                                    </td>
// IntegralPage.tsx continued...
                                    <td className={`px-6 py-4 ${detail.value > 0 ? 'text-green-600' : 'text-red-600'}`}>
                                        {detail.value > 0 ? '+' : ''}{detail.value}
                                    </td>
                                    <td className="px-6 py-4">{new Date(detail.expireTime).toLocaleDateString()}</td>
                                    <td className="px-6 py-4">{detail.reason}</td>
                                    <td className="px-6 py-4">{new Date(detail.createTime).toLocaleString()}</td>
                                </tr>
                            ))}
                            {details.length === 0 && (
                                <tr>
                                    <td colSpan={5} className="px-6 py-4 text-center text-gray-500">
                                        暂无积分记录
                                    </td>
                                </tr>
                            )}
                        </tbody>
                    </table>
                </div>
            </div>

            {/* 积分使用模块 */}
            <div className="mt-6 bg-white rounded-lg shadow p-6">
                <h2 className="text-lg font-semibold mb-4">使用积分</h2>
                <div className="flex items-center space-x-4">
                    <input 
                        type="number"
                        className="border rounded px-4 py-2 w-32"
                        placeholder="输入积分"
                        min="1"
                        max={totalIntegral}
                        onChange={(e) => setUseIntegralValue(Number(e.target.value))}
                        value={useIntegralValue}
                    />
                    <button
                        className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 disabled:bg-gray-400"
                        disabled={!useIntegralValue || useIntegralValue > totalIntegral}
                        onClick={() => handleUseIntegral(useIntegralValue)}
                    >
                        确认使用
                    </button>
                </div>
            </div>
        </div>
    );
};

// IntegralStatistics.tsx - 积分统计组件
import React, { useEffect, useState } from 'react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts';

interface IntegralStatistics {
    date: string;
    earned: number;
    used: number;
    expired: number;
}

export const IntegralStatistics: React.FC = () => {
    const [statistics, setStatistics] = useState<IntegralStatistics[]>([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        const loadStatistics = async () => {
            try {
                const response = await fetch('/api/integral/statistics');
                const data = await response.json();
                setStatistics(data);
            } catch (error) {
                console.error('Failed to load statistics:', error);
            } finally {
                setLoading(false);
            }
        };

        loadStatistics();
    }, []);

    if (loading) {
        return <div>Loading statistics...</div>;
    }

    return (
        <div className="bg-white rounded-lg shadow p-6">
            <h2 className="text-lg font-semibold mb-4">积分统计</h2>
            <div className="w-full h-80">
                <LineChart
                    width={800}
                    height={300}
                    data={statistics}
                    margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
                >
                    <CartesianGrid strokeDasharray="3 3" />
                    <XAxis dataKey="date" />
                    <YAxis />
                    <Tooltip />
                    <Legend />
                    <Line type="monotone" dataKey="earned" stroke="#4CAF50" name="获得积分" />
                    <Line type="monotone" dataKey="used" stroke="#2196F3" name="使用积分" />
                    <Line type="monotone" dataKey="expired" stroke="#FF5722" name="过期积分" />
                </LineChart>
            </div>
        </div>
    );
};

// notification.ts - 积分通知服务
export const IntegralNotification = {
    showSuccess(message: string) {
        // 实现成功提示
        console.log('Success:', message);
    },

    showError(message: string) {
        // 实现错误提示
        console.error('Error:', message);
    },

    showWarning(message: string) {
        // 实现警告提示
        console.warn('Warning:', message);
    }
};

// constants.ts - 常量定义
export const INTEGRAL_TYPES = {
    EARN: 1,
    USE: 2,
    EXPIRE: 3
} as const;

export const INTEGRAL_TYPE_NAMES = {
    [INTEGRAL_TYPES.EARN]: '获取积分',
    [INTEGRAL_TYPES.USE]: '使用积分',
    [INTEGRAL_TYPES.EXPIRE]: '积分过期'
} as const;

// hooks/useIntegral.ts - 积分相关自定义Hook
import { useCallback, useState } from 'react';

export const useIntegral = (userId: string) => {
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState<string | null>(null);

    const refreshIntegral = useCallback(async () => {
        setLoading(true);
        try {
            const [total, details] = await Promise.all([
                api.getUserTotalIntegral(userId),
                api.getUserIntegralDetails(userId)
            ]);
            return { total, details };
        } catch (err) {
            setError(err instanceof Error ? err.message : '获取积分信息失败');
            return null;
        } finally {
            setLoading(false);
        }
    }, [userId]);

    const useIntegral = useCallback(async (value: number, reason: string) => {
        setLoading(true);
        try {
            await api.useIntegral({ userId, value, reason });
            return true;
        } catch (err) {
            setError(err instanceof Error ? err.message : '使用积分失败');
            return false;
        } finally {
            setLoading(false);
        }
    }, [userId]);

    return {
        loading,
        error,
        refreshIntegral,
        useIntegral,
        clearError: () => setError(null)
    };
};

现在我们已经完成了完整的前后端实现,包括:

  1. 后端:
  • 实体模型设计
  • 业务逻辑服务层
  • API控制器
  • SQL存储过程
  1. 前端:
  • TypeScript类型定义
  • API调用封装
  • 积分页面组件
  • 积分统计图表
  • 通知服务
  • 常量定义
  • 自定义Hook

主要功能包括:

  1. 查看当前可用积分总额
  2. 查看积分明细记录
  3. 使用积分功能
  4. 积分统计图表展示
  5. 过期积分自动处理

使用方法:

  1. 后端部署:
  • 创建数据库表
  • 部署API服务
  • 配置定时任务执行过期积分处理存储过程
  1. 前端部署:
  • 安装依赖: React, Recharts等
  • 配置API地址
  • 构建并部署前端应用
  1. 使用:
# 安装依赖
npm install

# 开发模式运行
npm run dev

# 构建生产版本
npm run build

您可以根据具体需求进行以下扩展:

  1. 添加更多积分获取途径
  2. 优化统计图表展示
  3. 添加积分规则配置
  4. 实现积分兑换商城
  5. 添加积分变动通知
  6. 完善权限控制

需要任何修改或有其他需求都可以告诉我。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值