使用宝塔网站加速插件来加速网站速度,优化网站 - 知乎

mikel阅读(359)

来源: 使用宝塔网站加速插件来加速网站速度,优化网站 – 知乎

产品简介

宝塔网站加速 是宝塔面板推出的一款网站加速插件,实测博客类网站加速效果很好。

原理是:在http协议层,对动态页面进行缓存,对需要实时信息及已登录的会话跳出缓存,此技术主要针对匿名访问的用户进行加速响应,以减少应用服务器和数据库的开销。

不同网站类型加速效果

内容型网站: 如wordpress, phpcms, 各类企业站,cms,博客,商城等有最佳加速效果

交互型网站: 如 discuz,HYBBS等效果良好

其它网站: 对纯静态、后台管理系统, 如 各类OA系统,API接口等没有加速的意义

使用教程:

只需简单几步,即可完成加速配置

Nginx配置规则错误安装失败 宝塔控制面板nginx错误怎么解决方法

mikel阅读(453)

宝塔控制面板出现切换Nginx版本出现错误,宝塔nginx启动不起来,无法正常启动出现错误提示,服务器上面的网站也打不开。

Nginx配置规则错误:

nginx: [emerg] open() “/www/server/nginx/conf/agent_deny.conf” failed (2: No such file or directory) in /www/server/panel/vhost/nginx/www.cuiruo.com.conf:71

nginx: configuration file /www/server/nginx/conf/nginx.conf test failed

在宝塔控制面板 > 文件 > /www/server/nginx/conf/ 目录下新建文件命名agent_deny.conf就解决了错误问题。

FFmpeg循环推流脚本-荒岛

mikel阅读(584)

来源: FFmpeg循环推流脚本-荒岛

撸了个FFmpeg的循环推流脚本,你的VPS除了吃灰以外还能24小时不间断直播视频。自己用的脚本,单纯分享一下,如有问题自行解决。

需要配合screen运行:

yum -y install screen

开个新窗口:

screen -S stream

功能:

1.目前支持循环推流mp4格式的视频,注意视频文件的名字不能含有空格或其他特殊符号。

2.视频加水印,水印位置默认在右上角。

完整代码如下:

#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
#=================================================================#
#   System Required: CentOS7 X86_64                               #
#   Description: FFmpeg Stream Media Server                       #
#   Author: LALA                                    #
#   Website: https://www.lala.im                                  #
#=================================================================#

# 颜色选择
red='\033[0;31m'
green='\033[0;32m'
yellow='\033[0;33m'
font="\033[0m"

ffmpeg_install(){
# 安装FFMPEG
read -p "你的机器内是否已经安装过FFmpeg4.x?安装FFmpeg才能正常推流,是否现在安装FFmpeg?(yes/no):" Choose
if [ $Choose = "yes" ];then
	yum -y install wget
	wget --no-check-certificate https://www.johnvansickle.com/ffmpeg/old-releases/ffmpeg-4.0.3-64bit-static.tar.xz
	tar -xJf ffmpeg-4.0.3-64bit-static.tar.xz
	cd ffmpeg-4.0.3-64bit-static
	mv ffmpeg /usr/bin && mv ffprobe /usr/bin && mv qt-faststart /usr/bin && mv ffmpeg-10bit /usr/bin
fi
if [ $Choose = "no" ]
then
    echo -e "${yellow} 你选择不安装FFmpeg,请确定你的机器内已经自行安装过FFmpeg,否则程序无法正常工作! ${font}"
    sleep 2
fi
	}

stream_start(){
# 定义推流地址和推流码
read -p "输入你的推流地址和推流码(rtmp协议):" rtmp

# 判断用户输入的地址是否合法
if [[ $rtmp =~ "rtmp://" ]];then
	echo -e "${green} 推流地址输入正确,程序将进行下一步操作. ${font}"
  	sleep 2
	else  
  	echo -e "${red} 你输入的地址不合法,请重新运行程序并输入! ${font}"
  	exit 1
fi 

# 定义视频存放目录
read -p "输入你的视频存放目录 (格式仅支持mp4,并且要绝对路径,例如/opt/video):" folder

# 判断是否需要添加水印
read -p "是否需要为视频添加水印?水印位置默认在右上方,需要较好CPU支持(yes/no):" watermark
if [ $watermark = "yes" ];then
	read -p "输入你的水印图片存放绝对路径,例如/opt/image/watermark.jpg (格式支持jpg/png/bmp):" image
	echo -e "${yellow} 添加水印完成,程序将开始推流. ${font}"
	# 循环
	while true
	do
		cd $folder
		for video in $(ls *.mp4)
		do
		ffmpeg -re -i "$video" -i "$image" -filter_complex overlay=W-w-5:5 -c:v libx264 -c:a aac -b:a 192k -strict -2 -f flv ${rtmp}
		done
	done
fi
if [ $watermark = "no" ]
then
    echo -e "${yellow} 你选择不添加水印,程序将开始推流. ${font}"
    # 循环
	while true
	do
		cd $folder
		for video in $(ls *.mp4)
		do
		ffmpeg -re -i "$video" -c:v copy -c:a aac -b:a 192k -strict -2 -f flv ${rtmp}
		done
	done
fi
	}

# 停止推流
stream_stop(){
	screen -S stream -X quit
	killall ffmpeg
	}

# 开始菜单设置
echo -e "${yellow} CentOS7 X86_64 FFmpeg无人值守循环推流 For LALA.IM ${font}"
echo -e "${red} 请确定此脚本目前是在screen窗口内运行的! ${font}"
echo -e "${green} 1.安装FFmpeg (机器要安装FFmpeg才能正常推流) ${font}"
echo -e "${green} 2.开始无人值守循环推流 ${font}"
echo -e "${green} 3.停止推流 ${font}"
start_menu(){
    read -p "请输入数字(1-3),选择你要进行的操作:" num
    case "$num" in
        1)
        ffmpeg_install
        ;;
        2)
        stream_start
        ;;
        3)
        stream_stop
        ;;
        *)
        echo -e "${red} 请输入正确的数字 (1-3) ${font}"
        ;;
    esac
	}

# 运行开始菜单
start_menu

效果:

赞(36)

C#单元测试,带你快速入门 - 农码一生 - 博客园

mikel阅读(640)

来源: C#单元测试,带你快速入门 – 农码一生 – 博客园

C#单元测试,带你快速入门

注:本文示例环境

  • VS2017
  • XUnit 2.2.0 单元测试框架
  • xunit.runner.visualstudio 2.2.0 测试运行工具
  • Moq 4.7.10 模拟框架

为什么要编写单元测试

对于为什么要编写单元测试,我想每个人都有着自己的理由。对于我个人来说,主要是为了方便修改(bug修复)而不引入新的问题。可以放心大胆的重构,我认为重构觉得是提高代码质量和提升个人编码能力的一个非常有用的方式。好比一幅名画一尊雕像,都是作者不断重绘不断打磨出来的,而优秀的代码也需要不断的重构。
当然好处不仅仅如此。TDD驱动,使代码更加注重接口,迫使代码减少耦合,使开发人员一开始就考虑面对各种情况编写代码,一定程度的保证的代码质量,通过测试方法使后续人员快速理解代码…等。
额,至于不写单元测试的原因也有很多。原因无非就两种:懒、不会。当然你还会找更多的理由的。

框架选型

至于框架的选型。其实本人并不了解也没写过单元测试,这算是第一次真正接触吧。在不了解的情况下怎么选型呢?那就是看哪个最火、用的人多就选哪个。起码出了问题也容易同别人交流。

  • 单元测试框架:XUnit 2.2.0。ASP.NET mvc就是用的这个,此内框架还有:NUnit、MSTest等。
  • 测试运行工具:xunit.runner.visualstudio 2.2.0。类似如:Resharper的xUnit runner插件。
  • 模拟框架:Moq 4.7.10。 ASP.NET mvc、Orchard使用了。此类框架还有:RhinoMocks、NSubstitute、FakeItEasy等。

基本概念

  • AAA逻辑顺序
    • 准备(Arrange)对象,创建对象,进行必要的设置
    • 操作(Act)对象
    • 断言(Assert)某件事情是预期的。
  • Assert(断言):对方法或属性的运行结果进行检测
  • Stub(测试存根\桩对象):用返回指定结果的代码替换方法(去伪造一个方法,阻断对原来方法的调用,为了让测试对象可以正常的执行)
  • Mock(模拟对象):一个带有期望方法被调用的存根(可深入的模拟对象之间的交互方式,如:调用了几次、在某种情况下是否会抛出异常。mock是一种功能丰富的stub)
    Stub和Mock的定义比较抽象不好理解,延伸阅读1阅读2阅读3

好的测试

  • 测试即文档
  • 无限接近言简意赅的自然化语言
  • 测试越简明越好,每个测试只关注一个点。
  • 好的测试足够快,测试易于编写,减少依赖
  • 好的测试应该相互隔离,不依赖于别的测试,不依赖于外部资源
  • 可描述的命名:UnitOfWorkName_ScenarioUnderTest_ExpectedBehavior(命名可团队约定,我甚至觉得中文命名也没什么不可以的)
    • UnitOfWorkName  被测试的方法、一组方法或者一组类
    • Scenario  测试进行的假设条件,例如“登入失败”,“无效用户”或“密码正确”等
    • ExpectedBehavior  在测试场景指定的条件下,你对被测试方法行为的预期

基础实践

“废话”说的够多了,下面撸起袖子开干吧。
下面开始准备工作:

  • vs2017新建一个空项目 UnitTestingDemo
  • 新建类库 TestDemo (用于编写被测试的类)
  • 新建类库 TestDemo.Tests (用于编写单元测试)
  • 对类库 TestDemo.Tests 用nuget 安装XUnit 2.2.0、xunit.runner.visualstudio 2.2.0、Moq 4.7.10。
  • 添加 TestDemo.Tests 对 TestDemo 的引用。

例:

public class Arithmetic
{
    public int Add(int nb1, int nb2)
    {
        return nb1 + nb2;
    }
}

对应的单元测试:(需要导入using Xunit;命名空间。 )

public class Arithmetic_Tests
{
    [Fact]//需要在测试方法加上特性Fact
    public void Add_Ok()
    {
        Arithmetic arithmetic = new Arithmetic();
        var sum = arithmetic.Add(1, 2);
        
        Assert.True(sum == 3);//断言验证
    }
}

一个简单的测试写好了。由于我们使用的vs2017 它出了一个新的功能“Live Unit Testing”,我们可以启用它进行实时的测试。也就是我们编辑单元测试,然后保存的时候,它会自动生成自动测试,最后得出结果。


我们看到了验证通过的绿色√。
注意到测试代码中的参数和结果都写死了。如果我们要对多种情况进行测试,岂不是需要写多个单元测试方法或者进行多次方法执行和断言。这也太麻烦了。在XUnit框架中为我们提供了Theory特性。使用如下:
例:

[Theory]
[InlineData(2, 3, 5)]
[InlineData(2, 4, 6)]
[InlineData(2, 1, 3)] //对应测试方法的形参
public void Add_Ok_Two(int nb1, int nb2, int result)
{
    Arithmetic arithmetic = new Arithmetic();
    var sum = arithmetic.Add(nb1, nb2);
    Assert.True(sum == result);
}


测试了正确的情况,我们也需要测试错误的情况。达到更好的覆盖率。
例:

[Theory]
[InlineData(2, 3, 0)]
[InlineData(2, 4, 0)]
[InlineData(2, 1, 0)] 
public void Add_No(int nb1, int nb2, int result)
{
    Arithmetic arithmetic = new Arithmetic();
    var sum = arithmetic.Add(nb1, nb2);
    Assert.False(sum == result);
}

有时候我们需要确定异常
例:

public int Divide(int nb1, int nb2)
{
    if (nb2==0)
    {
        throw new Exception("除数不能为零");
    }
    return nb1 / nb2;
}
[Fact]      
public void Divide_Err()
{
    Arithmetic arithmetic = new Arithmetic(); 
    Assert.Throws<Exception>(() => { arithmetic.Divide(4, 0); });//断言 验证异常
}

以上为简单的单元测试。接下来,我们讨论更实际更真实的。
我们一般的项目都离不开数据库操作,下面就来实践下对EF使用的测试:

  • 使用nuget安装 EntityFramework 5.0.0

例:

public class StudentRepositories
{
    //...
    public void Add(Student model)
    {
        db.Set<Student>().Add(model);
        db.SaveChanges();
    }
}
[Fact]
public void Add_Ok()
{
    StudentRepositories r = new StudentRepositories();
    Student student = new Student()
    {
        Id = 1,
        Name = "张三"
    };
    r.Add(student);

    var model = r.Students.Where(t => t.Name == "张三").FirstOrDefault();
    Assert.True(model != null);           
}

我们可以看到我们操作的是EF连接的实际库。(注意:要改成专用的测试库)
我们会发现,每测试一次都会产生对应的垃圾数据,为了避免对测试的无干扰性。我们需要对每次测试后清除垃圾数据。

//注意:测试类要继承IDisposable接口
public void Dispose()
{
 StudentRepositories r = new StudentRepositories();
 var models = r.Students.ToList();
 foreach (var item in models)
 {
     r.Delete(item.Id);
 }
}

这样每执行一个测试方法就会对应执行一次Dispose,可用来清除垃圾数据。
我们知道对数据库的操作是比较耗时的,而单元测试的要求是尽可能的减少测试方法的执行时间。因为单元测试执行的比较频繁。基于前面已经对数据库的实际操作已经测试过了,所以我们在后续的上层操作使用Stub(存根)来模拟,而不再对数据库进行实际操作。
例:
我们定义一个接口IStudentRepositories 并在StudentRepositories 继承。

 public interface IStudentRepositories
 {
     void Add(Student model);
 }
 public class StudentRepositories: IStudentRepositories
 {
    //省略。。。 (还是原来的实现)
 }   
public class StudentService
{
    IStudentRepositories studentRepositories;
    public StudentService(IStudentRepositories studentRepositories)
    {
        this.studentRepositories = studentRepositories;
    }
    public bool Create(Student student)
    {
        studentRepositories.Add(student);

        return true;
    }
}

新建一个类,用来测试。这个Create会使用仓储操作数据库。这里不希望实际操作数据库,以达到快速测试执行。

[Fact]
public void Create_Ok()
{
    IStudentRepositories studentRepositories = new StubStudentRepositories();
    StudentService service = new StudentService(studentRepositories);
    var isCreateOk = service.Create(null);
    Assert.True(isCreateOk);
}

public class StubStudentRepositories : IStudentRepositories
{
    public void Add(Student model)
    {
    }
}


图解:

每次做类似的操作都要手动建议StubStudentRepositories存根,着实麻烦。好在Mock框架(Moq)可以自动帮我们完成这个步骤。
例:

[Fact]
public void Create_Mock_Ok()
{
    var studentRepositories = new Mock<IStudentRepositories>();
    var notiy = new Mock<Notiy>();
    StudentService service = new StudentService(studentRepositories.Object);
    var isCreateOk = service.Create(null);
    Assert.True(isCreateOk);
}

相比上面的示例,是不是简化多了。起码代码看起来清晰了,可以更加注重测试逻辑。

下面接着来看另外的情况,并且已经通过了测试

public class Notiy
{
    public bool Info(string messg)
    {
        //发送消息、邮件发送、短信发送。。。
        //.........
        if (string.IsNullOrWhiteSpace(messg))
        {
            return false;
        }
        return true;
    }
}
public class Notiy_Tests
{
    [Fact]
    public void Info_Ok()
    {
        Notiy notiy = new Notiy();
        var isNotiyOk = notiy.Info("消息发送成功");
        Assert.True(isNotiyOk);
    }
}

现在我们接着前面的Create方法加入消息发送逻辑。

public bool Create(Student student)
{
    studentRepositories.Add(student);

    var isNotiyOk = notiy.Info("" + student.Name);//消息通知

    //其他一些逻辑
    return isNotiyOk;
}
[Fact]
public void Create_Mock_Notiy_Ok()
{
    var studentRepositories = new Mock<IStudentRepositories>();
    var notiy = new Mock<Notiy>();
    StudentService service = new StudentService(studentRepositories.Object, notiy.Object);
    var isCreateOk = service.Create(new Student());
    Assert.True(isCreateOk);
}

而前面我们已经对Notiy进行过测试了,接下来我们不希望在对Notiy进行耗时操作。当然,我们可以通过上面的Mock框架来模拟。这次和上面不同,某些情况我们不需要或不想写对应的接口怎么来模拟?那就使用另外一种方式把要测试的方法virtual。
例:

public virtual bool Info(string messg)
{
    //发送消息、邮件发送、短信发送。。。
    //.........
    if (string.IsNullOrWhiteSpace(messg))
    {
        return false;
    }
    return true;
}

测试如下

[Fact]
public void Create_Mock_Notiy_Ok()
{
    var studentRepositories = new Mock<IStudentRepositories>();
    var notiy = new Mock<Notiy>();
    notiy.Setup(f => f.Info(It.IsAny<string>())).Returns(true);//【1】
    StudentService service = new StudentService(studentRepositories.Object, notiy.Object);
    var isCreateOk = service.CreateAndNotiy(new Student());
    Assert.True(isCreateOk);
}

我们发现了标注【1】处的不同,这个代码的意思是,执行模拟的Info方法返回值为true。参数It.IsAny() 是任意字符串的意思。
当然你也可以对不同参数给不同的返回值:

notiy.Setup(f => f.Info("")).Returns(false);
notiy.Setup(f => f.Info("消息通知")).Returns(true);

有时候我们还需要对private方法进行测试

  • 使用nuget 安装 MSTest.TestAdapter 1.1.17
  • 使用nuget 安装 MSTest.TestFramework 1.1.17

例:

private bool XXXInit()
{
    return true;
}
[Fact]
public void XXXInit_Ok()
{
    var studentRepositories = new StudentService();
    var obj = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(studentRepositories);
    Assert.True((bool)obj.Invoke("XXXInit"));
}

如果方法有参数,接着Invoke后面传入即可。

好了,就说这么多吧。只能说测试的内容还真多,想要一篇文章说完是不可能的。但希望已经带你入门了。

附录

xUnit(2.0) 断言 (来源)

  • Assert.Equal() 验证两个参数是否相等,支持字符串等常见类型。同时有泛型方法可用,当比较泛型类型对象时使用默认的IEqualityComparer实现,也有重载支持传入IEqualityComparer
  • Assert.NotEqual() 与上面的相反
  • Assert.Same() 验证两个对象是否同一实例,即判断引用类型对象是否同一引用
  • Assert.NotSame() 与上面的相反
  • Assert.Contains() 验证一个对象是否包含在序列中,验证一个字符串为另一个字符串的一部分
  • Assert.DoesNotContain() 与上面的相反
  • Assert.Matches() 验证字符串匹配给定的正则表达式
  • Assert.DoesNotMatch() 与上面的相反
  • Assert.StartsWith() 验证字符串以指定字符串开头。可以传入参数指定字符串比较方式
  • Assert.EndsWith() 验证字符串以指定字符串结尾
  • Assert.Empty() 验证集合为空
  • Assert.NotEmpty() 与上面的相反
  • Assert.Single() 验证集合只有一个元素
  • Assert.InRange() 验证值在一个范围之内,泛型方法,泛型类型需要实现IComparable,或传入IComparer
  • Assert.NotInRange() 与上面的相反
  • Assert.Null() 验证对象为空
  • Assert.NotNull() 与上面的相反
  • Assert.StrictEqual() 判断两个对象严格相等,使用默认的IEqualityComparer对象
  • Assert.NotStrictEqual() 与上面相反
  • Assert.IsType()/Assert.IsType() 验证对象是某个类型(不能是继承关系)
  • Assert.IsNotType()/Assert.IsNotType() 与上面的相反
  • Assert.IsAssignableFrom()/Assert.IsAssignableFrom() 验证某个对象是指定类型或指定类型的子类
  • Assert.Subset() 验证一个集合是另一个集合的子集
  • Assert.ProperSubset() 验证一个集合是另一个集合的真子集
  • Assert.ProperSuperset() 验证一个集合是另一个集合的真超集
  • Assert.Collection() 验证第一个参数集合中所有项都可以在第二个参数传入的Action序列中相应位置的Action上执行而不抛出异常。
  • Assert.All() 验证第一个参数集合中的所有项都可以传入第二个Action类型的参数而不抛出异常。与Collection()类似,区别在于这里Action只有一个而不是序列。
  • Assert.PropertyChanged() 验证执行第三个参数Action使被测试INotifyPropertyChanged对象触发了PropertyChanged时间,且属性名为第二个参数传入的名称。
  • Assert.Throws()/Assert.Throws()Assert.ThrowsAsync()/Assert.ThrowsAsync() 验证测试代码抛出指定异常(不能是指定异常的子类)如果测试代码返回Task,应该使用异步方法
  • Assert.ThrowsAny() 验证测试代码抛出指定异常或指定异常的子类
  • Assert.ThrowsAnyAsync() 如果测试代码返回Task,应该使用异步方法

Moq(4.7.10) It参数约束

  • Is:匹配确定的给定类型
  • IsAny:匹配给定的任何值
  • IsIn: 匹配指定序列中存在的任何值
  • IsNotIn: 匹配指定序列中未找到的任何值
  • IsNotNull: 找任何值的给定值类型,除了空
  • IsInRange:匹配给定类型的范围
  • IsRegex:正则匹配

相关资料

相关推荐

demo

推荐一款 在线+离线数据 同步框架 Dotmim.Sync - 张善友 - 博客园

mikel阅读(422)

来源: 推荐一款 在线+离线数据 同步框架 Dotmim.Sync – 张善友 – 博客园

移动智能应用可以分为在线模式、纯离线模式与“在线+离线”混合模式。在线模式下系统数据一般存储在服务器端的大中型数据库(如 SQL Server、Oracle、MySQL 等),移动应用依赖于稳定可靠的网络连接;纯离线模式下系统数据一般存储在移动终端的轻量级数据库(如 SQLite等),移动应用不需要网络连接;“在线+离线”混合模式则比较复杂,通常情况下系统数据存储在服务器端,移动终端暂存部分数据,因而形成了分布式异构数据库。在移动应用运行过程中,当移动终端或服务器端执行数据更新操作后,为了保证数据的完整性和一致性,需要进行双向的数据同步。然而,由于移动网络本身具有复杂性、动态性、弱连接性以及通信延迟与带宽相对有限等特性,因而移动应用的数据同步技术备受考验。

微软高级软件工程师 Sébastien Pertus 在 GitHub 网站开源了一种跨平台的关系数据库同步框架 Dotmim.Sync:https://github.com/Mimetis/Dotmim.Sync,该框架基于.NET Standard 2.0  研发,支持在 IOT、 Xamarin、.NET、MAUI 等环境中使用。

Architecture01.png

文档网站: https://dotmimsync.readthedocs.io/ 

Dotmim.Sync框架包含针对多种不同主流关系数据库的子项目解决方案,每个子项目均发布为NuGet程序包,便于开发人员基于.NET平台在项目中添加、移除与更新引用。Nuget 上一共发布了8个Nuget包:

image

其中 Dotmim.Sync.Core是核心的NuGet程序包主要执行数据同步的核心逻辑。Dotmim.Sync.SQLServer、Dotmim.Sync.Sqlite、Dotmim.Sync.MySql、Dotmim.Sync.MariaDB 分别针对SQL Server、 SQLite、MySQL、MariaDB数据库的 NuGet程序包,可以根据实际项目的数据库部署需要,基于 .NET平台在服务器端与客户端程序中分别引用相应的NuGet程序包,进而完成服务器端与客户端数据库数据的同步。Dotmim.Sync.Web.Server 与 Dotmim.Sync.Web.Client NuGet程序包实现 HTTP协议通过Web服务器完成服务器端与客户端数据库的同步操作。

代码仓库里包含了丰富的示例程序,特别是IOT,MAUI,Xamarin 等在线+离线的场景 ,通常架构图如下:

建筑

具备明显的跨平台优势.NET Core ,因此采用.NET Core Web API架构创建基于REST风格的Web API。

核心步骤如下:

Step 1 在服务配置方法中注册同步提供程序:

image

Step 2 创建数据同步控制器,采用依赖注入的方式注入服务器端Web 代理提供程序:

image

Step 3 在控制器的 POST 方法中调用 HandleRequestAsync 方法,执行异步请求,完成数据同步功能:

image

上述完成了服务端的设置,接下来就是设置客户端。我们以MAUI 为例,基于MAUI 设计移动端应用,以Android 系统进行说明:

主要步骤如下:

Step 1 在项目的AndroidManifest.xml文件中添加网络访问、读写外部存储等权限。

image

Step 2 由于Google 从Android P开始已经明确规定禁止http协议额,但是我们的接口都是http协议,从Nougat(Android 7)一个名为“Network Security Configuration”的新安全功能也随之而来。网络安全性配置特性让应用可以在一个安全的声明性配置文件中自定义其网络安全设置,而无需修改应用代码。

image

Step 3 在数据同步事件中,开启子线程,在子线程中执行数据同步操作:

image

image

在数据同步过程中,有两个问题是需要明确的,数据同步方向与冲突问题解决:

image

首先执行数据同步的常规过程,由客户端发起数据同步 POST 请求,服务器端.NET Core Web API尝试执行数据同步任务。其次,当检测到数据冲突时,服务器端检测预先设置的 ConflictResolutionPolicy 属性值,如果其值为 Serverwins,则服务器端获胜,将服务器端的变化数据强制应用到客户端的数据库中,反之则客户端获胜,将客户端的变化数据强制应用到服务器端的数据库中。

1)数据同步方向在 Dotmim.Sync 框架中,提供了用于表征数据同步方向的枚举 SyncDirection。该枚举包含 3 个值:Bidirectional(默认值)、DownloadOnly和 Upload⁃Only,分别对应“双向同步”、“仅下载同步”与“仅上传同步”3 种方向,可以具体为每个数据表SetupTable 分别设定同步方向。

2)通常情况下冲突问题解决Dotmim.Sync 框架采用 SyncOption 对象的配置策略属性 ConflictResolutionPolicy解决数据冲突问题。

ConflictResolutionPolicy的可选项如下:

(1) ConflictResolutionPolicy.Serverwins, 默认选项,表征服务端为所有冲突的获胜方。

(2) ConflictResolutionPolicy.Clientwins 表征客户端为所有冲突的获胜方

LODOP打印提示“有窗口已经打开”,无法继续打印

mikel阅读(468)

 LODOP打印提示“有窗口已经打开”,无法继续打印


首先点击“设置”,看下当前选择的共享打印机是否正确可用,选正确可用的打印机,否则会提示窗口已打开后,不显示窗口,并且半天后才弹出预览窗口

1、把电脑浏览器左下角或者右下角隐藏的预览窗口关了

 
2、重启电脑是最简单的方法
 
3、打印预览页面 点右键 有菜单选项, 打印后自动关闭,可以避免上述问题

Debug模式下,无法获取本地变量或参数的值,因为它在此指令指针中不可用,可能是因为它已经被优化掉了_IT流渊的博客-CSDN博客

mikel阅读(480)

来源: Debug模式下,无法获取本地变量或参数的值,因为它在此指令指针中不可用,可能是因为它已经被优化掉了_IT流渊的博客-CSDN博客

Debug模式下,调试中,无法查看变量值;
提示:无法获取本地变量或参数的值,因为它在此指令指针中不可用,可能是因为它已经被优化掉了

解决方法:
在项目属性的生成选项卡里-“优化代码”,不用打勾!

程序员开发正遭 AI 「革命」_CSDN资讯的博客-CSDN博客

mikel阅读(413)

来源: 程序员开发正遭 AI 「革命」_CSDN资讯的博客-CSDN博客

【CSDN 编者按】AI,一个可以让人充满无限想象力的技术。近年来,随着 GPT-3、BERT 等大语言模型的崛起,以及昨日最新 Stable Diffusion 2.0 开源文本图像模型的发布,也让很多「所想即所得」的应用场景逐渐成为现实。反观其背后,技术人在推动 AI 落地的过程中,包括自身的开发习惯、方式等也正被 AI 重置。

作者 | 卞安 责编 | 屠敏

出品 | CSDN(ID:CSDNnews)

AI 作画,作为一个局部领域的产品方向,在基于机器学习算法基础上,为世界打开了一扇大门,随着 AI 作画类软件的大放异彩,基于 AI 进行内容生产话题也越来越受到关注。

作为一名程序员,我们又应该怎么来看待 AI 的这些成果对于开发方式的影响,在此,笔者有幸受邀为各位小伙伴分享一下个人的看法。

 

在美国科罗拉多州艺术博览会上基于 Midjourney 获奖的 AI 画作《太空歌剧院》

 

AI 将改写程序员的开发方式

AI 的底层算法一般是基于神经网络进行建模,通过在神经元上的数学算法不断的训练数据调整建模,使误差向最小靠近的过程。这是一种非常酷的思维方式,它使得计算机可以通过算法寻找到数据变化的趋势。在此基础上发展起来的生成对抗网络,使得计算机学习和模仿数字化内容变的更加可信,这些数字化内容不但包括文本(新闻和小说生成)、图像(AI绘图与视频),3D 模型(AI 生成游戏场景),也包括编程代码和文档。

虽然我们目前还没有能够走到直接通过 AI 来生成完整的项目的地步,但在 AI 大发展的背景下,如何高效、快速、更好的达到编程目标,正在成为解决问题中一个重要的考虑因素。

我们可以对比 AI 的工作原理,反思编程工作中的痛点,预测一些潜在的可能性,也许未来这些可能性会极大的推动我们的编程和工作方式。

从工作形态的进化趋势上来看,预期会分为三个阶段:

 

 

第一阶段:编码工作的辅助智能化
这一阶段的主要体现是:通过搜集、归类、梳理编程开发项目中大量的可复用部分(如算法、功能函数或模块)形成数据集,通过对输入意向和数据集进行训练和学习的方式,形成优良的辅助功能,提升程序员的开发效率。

在具体实践上,一些企业已经推出了相关工具类产品:

GitHub Copilot : 通过语音输入指令让 IDE 自动提示并生成代码。

Copilot 是 GitHub 今年早些时候推出的人工智能编程辅助工具,2022 年 6 月 22 日已正式上线(https://github.com/features/copilot),定价每月 10 美元(约 66.9 元人民币)或每年 100 美元(约 669 元人民币),对学生用户和流行开源项目的维护者免费提供。

经过数十亿行代码的训练,它可以将自然语言提示转化为数十种语言的编码建议,支持 Python、JavaScript、TypeScript、Java、Ruby 和 Go 等编程语言。根据今年 GitHub Universe 开发者大会报告给出的数据,Copilot 已经通过基于 AI 的编码建议,帮助全球开发者的工作效率提高了 55%。

 

2. GodeGeeX:通过输入文字描述让 IDE 自动提示并生成代码。

与此相近的一款国产工具叫做 GodeGeeX(https://models.aminer.cn/codegeex/),来自清华大学知识工程实验证(KEG),CodeGeeX 是一款具有 130 亿参数的多语言代码生成模型,采用华为 MindSpore 框架实现,在鹏城实验室“鹏城云脑II”中的 192 个节点(共 1536 个国产昇腾 910 AI 处理器)上训练而成。

截至 2022 年 6 月 22 日,CodeGeeX 历时两个月在 20 多种编程语言的代码语料库(>8500 亿 Token)上预训练得到。它可以跟据你的描述,或者上下文自动生成一段代码,和以往的代码补全功能是完成不同的。目前支持 Python、C++、Java、JavaScript、Go 五种主流编程语言,而且在准确度上表现较好。

 

除此之外,还有 Kite、Codota、DeepCode 等一些基于机器学习的 AI 代码生成工具,本质上都是通过大量的代码训练,来智能预测出需要生成的代码,加快编程的效率。

总体来说,这一阶段的产品推动者不需要面向业务,只需要考虑面向程序员,可以达到辅助程序员提升业务开发速度的目标,也可以较好的通过工具化产品形成规范,解决企业的代码风格化统一问题。未来可能会广泛的成为程序员工作中的标配,程序员将不会再花大量的精力在高复用度的具体代码函数设计,而更多的把精力放在业务模块的设计实现。

 

第二阶段:业务模块的智能生成
这一阶段的主要体现是:通过搜集、归类、梳理相关领域业务开发项目中大量的通用业务(如游戏研发中的登录模块、热更新模块、聊天模块、战斗模块等业务逻辑)形成数据集,通过对业务设计意向和数据集进行训练和学习的方式,自动的进行业务模块前后端框架生成,优化工作流程,提升业务模块的产品设计迭代和研发速度。

我们可以通过一个实例工具软件来理解这个过程,Adobe 推出过的网页三剑客之一的 Dreamweave,其中提供了大量的模块生成功能,比如通过设定数据集和表单的控件对应关系,就可以生成前后端的代码,实际提供一个可以运行的业务模块。

当然,这是由没有结合人工智能辅助的情况,如果基于新的智能化生成,那我们在开发时,智能化生成模块会自动跟据上下文来提示是否需要生成相应的业务前后端模块,我们只需要再跟据生成的模块进行微调或扩展即可达到我们的要求。

下图是笔者使用 DreamWeaver 来生成一个简单的登录模块,只需要设定一下数据表字段,即可生成表单,完成登录判断和跳转功能。

 

又或者在游戏开发领域。作者曾在企业负责技术中台设计时,推动过面向各游戏项目组通用业务模块的抽象与开发,比如热更新、原生接口调用,甚至包括游戏战斗机制等大量的通用业务,对于每一个项目几乎都是需要的,只是因为技术栈或者界面的不同而需要重复的开发,这些工作往往耗时耗力。

站在一个更高的维度,这些业务完全可以基于机器学习训练结合需求变化被快速地进行调整而满足各个项目的使用。所以,基于企业的技术中台,把这些通用业务的统一框架和智能化定制与人工智能结合,基于主流开发软件、引擎工具或平台工具推出可实际操作的软件或插件。从而形成更好的开发工作流是非常有意义的。

这个阶段目前是各领域企业争相降本增效的方向之一,程序员的工作正在进一步从通用业务开发中解放,而把更多的把精力放在具体产品的需求上。

 

第三阶段:产品原型的智能生成
这一阶段主要体现是:通过对产品研发工作流中可智能化生成的部分进行整合和优化,达到能够根据用户需求自动的生成具备完整功能逻辑和效果表现的产品项目原型的目标,帮助产品经理快速看到期望的结果。

这里以几个案例加以说明:

一、市场上大量自助建网站,电商小程序类的平台。

这些平台直接面向用户方,提供给用户可以自助订制化的业务模块并生成最终产品的服务。如腾讯云的自助式建站:

 

这些平台虽然具备直接生成最终产品的能力,但其底层逻辑主要是基于一定数量的前后端模板和风格模板进行组合,通过固化的需求条目选项得到产品各模板模块的组合结果,产品的领域有限、形态有限、规模有限,目前大部分不具备通过对用户需求分析和产品形态数据集进行机器学习来生成目标产品的能力。

在数字人研发企业里,目前已经在通过对人脸照片,动作视频采用人物脸部识别,动作识别技术来建立模型数据集,通过机器学习对三维模型重建和自动动作绑定,快速化生产高精度数字人,这种方式与以往通过美术建模,手动绑定骨骼及人物动作的工作流有较大差别,它可以大大的加快数字人的生成效率、规模和精度。

 

在 GTC 大会上,黄仁勋宣布推出一个全方位的虚拟化身平台 —— Omniverse Avatar(来源:https://developer.nvidia.com/nvidia-omniverse-platform/ace)

二、在一些游戏方向,如剧情文字类游戏(AVG)领域,开发者也在通过剧本生成,二次元立绘生成类软件,通过脚本进行完整的游戏产品生产尝试,可以将原本需要几个月才能完成的剧情文字游戏,在短短几个小时内生成出来。

当然,这些方面的业务相对简单,但随着人工智能的发展,相信不久的未来,一个软件产品设计师可以通过提供一些简单的需求描述得到一个完整可运行的产品DEMO。它所要做的,是通过不断的修改需求描述来不断提交,得到期望的产品原型。

 

总结

最后,我想说的是,内容生成的算法目前在一些方面有了较大突破,但在软件开发领域,特别是开发者工具领域,我们也需要相关企业和创业者不断的结合这些突破,落地到具体的工具软件产品中来提升开发者的工作流程和体验,推出更好的产品形态,谁能够在未来提供更好的智能化内容生成体验,也就会在未来更受欢迎。

作者介绍:卞安,游戏引擎技术专家,CSDN 博客专家,独立软件创业者。从事游戏研发行业近二十年,孜孜不倦地学习和思考引擎研发技术和工具链设计,曾任端游企业引擎技术总监,Cocos 游戏引擎产品总监。目前在学习游戏引擎与 AI 的结合,致力于开发基于 Python 语言的全栈式开发工具软件产品《PyMe》。

 

————————————————
版权声明:本文为CSDN博主「CSDN资讯」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/csdnnews/article/details/128125034

redis之mq实现发布订阅模式 - 云天 - 博客园

mikel阅读(449)

来源: redis之mq实现发布订阅模式 – 云天 – 博客园

概述

Redis不仅可作为缓存服务器,还可用作消息队列,本示例演示如何使用redis实现发布/订阅消息队列。

  • 在Redis中,发布者没有将消息发送给特定订阅者的程序。相反,发布的消息被描述为通道,而不知道(如果有的话)可能有哪些订阅者。
  • 订阅者表示对一个或多个主题感兴趣,只接收感兴趣的消息,而不知道(如果有的话)发布者是什么。
  • 发布者和订阅者的这种解耦可以实现更大的可伸缩性和更动态的网络拓扑。

代码实现

redis实现mq的存储方式很多,可以使用list,zset及stream,这些数据的存储结构决定了怎么消费问题(消息是一次使用、允许多次使用、允许多端消息等),比如使用list,我们可以使用leftPush插入消息,使用rightPop消费消息,实现一条消息一次消息,可以参考与以示例代码:

    @Test
    public void testMq() {
        for (int i = 0; i < 10; i++) {
            redisTemplate.opsForList().leftPush("task-queue", "data" + i);
            log.info("插入了一个新的任务==>{}", "data" + i);
        }
        String taskId = redisTemplate.opsForList().rightPop("task-queue").toString();
        log.info("处理成功,清除任务==>{}", taskId);
    }

1.配置代码RedisConfig.java

package demo.data.mqRedis.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类,方便调试redis
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        //使用StringRedisSerializer来序列化和反序列化redis的ke
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());

        //开启事务
        redisTemplate.setEnableTransactionSupport(true);

        redisTemplate.setConnectionFactory(redisConnectionFactory);

        return redisTemplate;
    }

    @Bean
    MessageListenerAdapter messageListener() {
        return new MessageListenerAdapter(new RedisMessageSubscriber());
    }

    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                            MessageListenerAdapter listenerAdapter) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(listenerAdapter, topic());

        return container;
    }

    @Bean
    MessagePublisher redisPublisher() {
        return new RedisMessagePublisher(redisTemplate, topic());
    }

    @Bean
    ChannelTopic topic() {
        return new ChannelTopic("messageQueue");
    }
}

2.定义消息发布接口MessagePublisher.java

package demo.data.mqRedis.config;

public interface MessagePublisher {
    void publish(String message);
}

3.发布方实现RedisMessagePublisher.java

package demo.data.mqRedis.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;

/**
 * 消息发布方
 */
public class RedisMessagePublisher implements MessagePublisher {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private ChannelTopic topic;

    public RedisMessagePublisher(
            RedisTemplate<String, Object> redisTemplate, ChannelTopic topic) {
        this.redisTemplate = redisTemplate;
        this.topic = topic;
    }

    public void publish(String message) {
        redisTemplate.convertAndSend(topic.getTopic(), message);
    }
}

4.消息接收方RedisMessageSubscriber.java

package demo.data.mqRedis.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * 消息订阅方
 */
@Service
@Slf4j
public class RedisMessageSubscriber implements MessageListener {

    public static List<String> messageList = new ArrayList<>();

    public void onMessage(Message message, byte[] pattern) {
        messageList.add(message.toString());
        log.info("订阅方接收到了消息==>{}", message.toString());
    }
}

5.最后贴上application.yml配置

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password:

查看运行结果

1.编写测试用例试发布消息TestRedisMQ.java

package demo.data.mqRedis;

import demo.data.mqRedis.config.RedisMessagePublisher;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.UUID;

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class TestRedisMQ {

    @Autowired
    RedisMessagePublisher redisMessagePublisher;

    @Test
    public void testMq() {
        String message = "Message " + UUID.randomUUID();
        redisMessagePublisher.publish(message);
    }
}

2.运行结果

2019-09-05 15:51:33.931  INFO 10772 --- [    container-2] d.d.m.config.RedisMessageSubscriber      : 订阅方接收到了消息==>"Message c95959bf-6c30-4801-bc80-0e1e3c9f81bc"

订阅方成功接收到消息了

资料