提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言 一、需求背景 二、询问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真是太好用啦!