当前位置:AIGC资讯 > AIGC > 正文

从0到1,为ASP.NET Core项目添加redis支持(全程使用copilot编码)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

前言 一、需求背景 二、询问copilot工作流 1.简单询问 2.细节询问 3.代码定制化 三、最终验证 总结

前言

最近做需求需要用到redis,之前只在java用过,c#的使用还不清楚,故总结记录一下工作流。全程使用copilot进行代码编写

一、需求背景

需求是一个简单的获取token的需求,token获取需要经过http调用第三方系统,故为了减少http调用,设置redis来缓存token数据。

二、询问copilot工作流

1.简单询问

上来就先简单问问,如何导入redis:

可以看到,由于是刚开始发起提问,没有上下文,它给我整个了python的,我需要c#的,故继续提问。

这次是c#的方法了,但是更类似那种自己测试来玩玩的写法, 不符合我们的ASP.NET Core项目需求,故再次具体提问。



可以看到,这次的回答就比较具体了,从appsetting配置,到startup的注入,到实际代码里面的使用(他这里是直接在controller里面使用,我们一般还是建个RedisHeloer工具类来统一使用,后面再具体询问这方面)都有给出。至此,理论上是可以完整的测试并使用redis了,但是,我们还需了解更多细节才能应用到自己的代码里。

2.细节询问

我们可以注意到,虽然上一步中给出了大体使用步骤,但是还有一些地方的细节是模糊不清的,比如这个redis的ConnectionString,就写得比较不清不楚,一个localhost,显然不能满足需求

故就此细节再次进行提问:


这次的回答就比较具体了,从中我们可知redis链接串还可以指定端口号,指定密码,指定数据库等属性,最终我们也是直接采用了它给出的完整配置。

至此,对应到我们的项目中,已经添加了如下代码:

首先是appsetting文件,定义了链接串:

  "Redis": {
    "ConnectionString": "localhost:6379,password=yourpassword,ssl=False,abortConnect=False,defaultDatabase=0"
  }

然后是startup文件,定义了redis链接的注入,以及我们redis工具类的注入:

    public static void ConfigureServices(IServiceCollection services,
        IConfigurationManager configuration)
    {

        // redis setting
        var redisConnectionString = configuration.GetSection("Redis:ConnectionString").Value;
        services.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect(redisConnectionString));
        services.AddScoped<RedisHelper>();

    }

完成基础的配置跟注入后,就是具体的功能方法的提问,

现在我需要一个方法,可以做到redis有值的时候取值,无值的时候调用我传入的方法获取值,并set到redis去。可以看到,copilot给我返回了一个代码,但是注意,里面涉及了序列化与反序列化方法,并没有实现,我们可以继续就此追问。


这里它 给出了两种实现json序列化与反序列化的两种方式,System.Text.Json 和 Newtonsoft.Json ,我这里使用 Newtonsoft.Json 。
同时,我们设置rediskey跟value的时候,需要考虑过期时间,故继续询问如何设置过期时间。


可以看到,我们可以用StringSet方法来设置过期时间,它接收TimeSpan类型的参数。为了代码方便管理,像redis的过期时间,还有前面提到的rediskey,一般都是定义好常量,直接使用,而TimeSpan类型,由于不能设置成常量,所以我们把常量定义为过期时间的数值。

至此,对应到我们的项目中,又新添加了如下代码:
定义常量的静态类,定义了key跟过期时间。过期时间设置为3500s,是因为我们调用http接口获取的token过期时间是3600s,这样能保证让时间全覆盖

    public static class RedisConstants
    {
        public static class RedisKeys
        {
            public const string CRM_ADMIN_TOKEN_KEY = "crm_admin_token_key";
        }

        public static class RedisExpireTime
        {
            // 3500s
            public const int CRM_ADMIN_TOKEN_EXPIRE_TIME = 3500;
        }
    }

定义redis调用的RedisHelper工具类:

    public class RedisHelper
    {
        private readonly IDatabase _database;

        public RedisHelper(IConnectionMultiplexer connectionMultiplexer)
        {
            _database = connectionMultiplexer.GetDatabase();
        }

        public T GetOrSetValue<T>(string key, int expirtime, Func<T> valueFactory)
        {
            // 尝试从Redis获取值
            var value = _database.StringGet(key);
            if (value.HasValue)
            {
                Console.WriteLine("Get value from Redis!");
                // 如果存在,反序列化并返回
                return Deserialize<T>(value);
            }
            else
            {
                Console.WriteLine("redis not value , Get value from Factory!");
                // 如果不存在,调用valueFactory获取值
                var newValue = valueFactory();
                // 序列化并存储到Redis
                _database.StringSet(key, Serialize(newValue), TimeSpan.FromSeconds(expirtime));
                return newValue;
            }
        }

        private byte[] Serialize<T>(T value)
        {
            // 实现序列化逻辑,根据实际情况选择合适的序列化方式
            var jsonString = JsonConvert.SerializeObject(value);
            return System.Text.Encoding.UTF8.GetBytes(jsonString);
        }

        private T Deserialize<T>(RedisValue value)
        {
            // 实现反序列化逻辑,根据实际情况选择合适的反序列化方式
            var jsonString = System.Text.Encoding.UTF8.GetString(value);
            return JsonConvert.DeserializeObject<T>(jsonString);
        }
    }

3.代码定制化

至此,copilot已经给出一套完整的demo了,但是,还不够。毕竟copilot无法直接读取整个项目的上下文,只能根据提问给出一些比较通用的解决方案,我们还要考虑自己的项目本身,如何让copilot给出的建议代码更好的融入我们的项目。还记得我之前提到的http调用的方法吗?该方法是一个异步的方法,通过http调用来获取token。
该获取token的方法签名如下

public async Task<string> GetAdminTokenFromCRM(string authParam)

而copilot建议的代码如下

 public T GetOrSetValue<T>(string key, int expirtime, Func<T> valueFactory)

这个传入的方法并不是异步方法,且GetOrSetValue方法也不是异步的,故需要进行改造,一开始我按自己的想法来改,但是还是比较缺少对异步编程这块的知识,报了几个错。于是把代码圈中询问copilot


可以看到,copilot已经针对我错误的改造给出了建议。
同时也让我了解到Task的一些相关知识,在此概括总结下:
1.如果用Task定义了一个异步方法A,那么调用这个方法A的方法,以及所有的上层方法都要用 async Task 来定义
2.调用异步方法时,要在前面加 await 关键字
3.异步方法内部可以调同步方法,调用同步方法时无需加 await 关键字
至此完成了代码的定制化
最终,对应到我们的项目中,又新添加,或者更改了如下代码:
AlthHelper类,

    public class CRMAuthHelper
    {
        private readonly CRMHttpHelper _cRMHttpHelper;
        private readonly RedisHelper _redisHelper;

        public CRMAuthHelper(CRMHttpHelper cRMHttpHelper,RedisHelper redisHelper)
        {
            _cRMHttpHelper = cRMHttpHelper;
            _redisHelper = redisHelper;
        }

        public async Task<string> GetAdminiToken(string authParam)
        {
            string token = await GetFromRedis(authParam);
            return token;
        }

        public async Task<string> GetFromRedis(string authParam)
        {
            string token = await _redisHelper.GetOrSetValue<string>(RedisConstants.RedisKeys.CRM_ADMIN_TOKEN_KEY,
                RedisConstants.RedisExpireTime.CRM_ADMIN_TOKEN_EXPIRE_TIME,
                () => _cRMHttpHelper.GetAdminTokenFromCRM(authParam));
            return token;
        }
    }

更改后的RedisHelper工具类

    public class RedisHelper
    {
        private readonly IDatabase _database;

        public RedisHelper(IConnectionMultiplexer connectionMultiplexer)
        {
            _database = connectionMultiplexer.GetDatabase();
        }

        public async Task<T> GetOrSetValue<T>(string key, int expirtime, Func<Task<T>> valueFactory)
        {
            // 尝试从Redis获取值
            var value = _database.StringGet(key);
            if (value.HasValue)
            {
                Console.WriteLine("Get value from Redis!");
                // 如果存在,反序列化并返回
                return Deserialize<T>(value);
            }
            else
            {
                Console.WriteLine("redis not value , Get value from Factory!");
                // 如果不存在,调用valueFactory获取值
                var newValue = await valueFactory();
                // 序列化并存储到Redis
                _database.StringSet(key, Serialize(newValue), TimeSpan.FromSeconds(expirtime));
                return newValue;
            }
        }

        private byte[] Serialize<T>(T value)
        {
            // 实现序列化逻辑,根据实际情况选择合适的序列化方式
            var jsonString = JsonConvert.SerializeObject(value);
            return System.Text.Encoding.UTF8.GetBytes(jsonString);
        }

        private T Deserialize<T>(RedisValue value)
        {
            // 实现反序列化逻辑,根据实际情况选择合适的反序列化方式
            var jsonString = System.Text.Encoding.UTF8.GetString(value);
            return JsonConvert.DeserializeObject<T>(jsonString);
        }
    }

下面是调用获取token时的局部代码,仅需调用AuthHelper即可

            // get adminauth
            string accesstoken = await _authHelper.GetAdminiToken(cRMSystemConfig.AuthParam);

三、最终验证

下面验证一下,首先是首次调用时,确实走到了http调用获取token的方法

第二次再调用,则会直接使用redis里面的值

总结

以上就是本次copilot的使用工作流,在此总结一下:
1.对于自身的需求,可以先抛出简单的提示词
2.再根据copilot的回答进一步引导提问并优化细节
3.针对最终的建议代码,再次进行定制化提问,以适应已有项目
每日一遍,copilot真是太好用啦!

更新时间 2024-07-05