一、前言

前一阵子关于.NET的各大公众号都发表了关于gRpc的消息,而随之而来的就是一波关于.NET Core下如何使用的教程,但是在这众多的教程中基本都是泛泛而谈,难以实际在实际环境中使用,而该篇教程以gRpc为主,但是使用了其SSL/TLS,这样更加符合实际的生产使用,期间也会配套的讲解Docker、openssl等。

二、服务端

a.准备工作

笔者的项目分为三个部分分别如下所示:

Sino.GrpcService.Host(控制台):宿主程序

Sino.GrpcService.Impl(类库):实现协议

Sino.GrpcService.Protocol(类库):生成协议

最终的项目如下图所示:

每个项目的project.json如下所示:

  1. {
  2. "version": "1.0.0-*",
  3. "buildOptions": {
  4. "emitEntryPoint": true,
  5. "copyToOutput": [ "server.crt", "server.key", "appSettings.json", "appSettings.*.json" ]
  6. },
  7. "dependencies": {
  8. "Microsoft.NETCore.App": {
  9. "type": "platform",
  10. "version": "1.0.0"
  11. },
  12. "Sino.GrpcService.Impl": "1.0.0-*",
  13. "Microsoft.Extensions.Configuration.Json": "1.0.0",
  14. "Microsoft.Extensions.Configuration.Binder": "1.0.0",
  15. "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0"
  16. },
  17. "frameworks": {
  18. "netcoreapp1.0": {
  19. "imports": [ "dnxcore50", "net452" ]
  20. }
  21. },
  22. "publishOptions": {
  23. "include": [ "server.crt", "server.key", "appSettings.json", "appSettings.*.json" ]
  24. }
  25. }

其中“buildOptions”和“publishOptions”中我们将后面我们需要的证书包含到输出和发布中,其中我们还利用了“Configuration”相关组件去读取配置信息。

Sino.GrpcService.Impl:

  1. {
  2. "version": "1.0.0-*",
  3. "dependencies": {
  4. "Autofac": "4.1.1",
  5. "Google.Protobuf": "3.1.0",
  6. "Grpc.Core": "1.0.1-pre1",
  7. "NETStandard.Library": "1.6.0",
  8. "Sino.GrpcService.Protocol": "1.0.0-*",
  9. "MongoDB.Driver": "2.3.0",
  10. "Microsoft.Extensions.Configuration.Abstractions": "1.0.0"
  11. },
  12. "frameworks": {
  13. "netstandard1.6": {
  14. "imports": "dnxcore50"
  15. }
  16. }
  17. }

其中我们安装了“MongoDb.Driver”,为了能够贴近真实的情况,笔者这里采用MongoDb作为数据源来提供数据,当然读者为了能够快速上手可以硬编码一些数据。

Sino.GrpcService.Protocol:

  1. {
  2. "version": "1.0.0-*",
  3. "dependencies": {
  4. "Google.Protobuf": "3.1.0",
  5. "Grpc.Core": "1.0.1-pre1",
  6. "NETStandard.Library": "1.6.0"
  7. },
  8. "frameworks": {
  9. "netstandard1.6": {
  10. "imports": "dnxcore50"
  11. },
  12. "net452": {}
  13. }
  14. }

至此项目的初始化结束。

b.编写协议

首先我们打开Sino.GrpcService.Protocol项目,在其中新建一个msg.proto文件,打开msg.proto文件,我们将在其中编写基于proto3语言的协议,以便后面自动生成到各语言,如果读者需要更深入的学习可以打开该网站Proto3语言指南

这里我们定义我们当前使用的是proto3语言并且包名(生成为C#则为命名空间)为:

  1. syntax = "proto3";
  2. package Sino.GrpcService;

笔者为该服务定义了1个服务,且有4种方法:

  1. service MsgService{
  2. rpc GetList(GetMsgListRequest) returns (GetMsgListReply){}
  3. rpc GetOne(GetMsgOneRequest) returns (GetMsgOneReply){}
  4. rpc Edit(EditMsgRequest) returns (EditMsgReply){}
  5. rpc Remove(RemoveMsgRequest) returns (RemoveMsgReply){}
  6. }

对应到其中每个方法的接收参数和返回参数的定义如下:

  1. message GetMsgListRequest {
  2. int64 UserId = ;
  3. string Title = ;
  4. int64 StartTime = ;
  5. int64 EndTime = ;
  6. }
  7.  
  8. message GetMsgListReply {
  9. message MsgItem {
  10. string Id = ;
  11. string Title = ;
  12. string Content = ;
  13. int64 UserId = ;
  14. int64 Time = ;
  15. }
  16. repeated MsgItem Items = ;
  17. int64 Count = ;
  18. bool IsSuccess = ;
  19. string ErrorMsg = ;
  20. }
  21.  
  22. message GetMsgOneRequest {
  23. string Id = ;
  24. }
  25.  
  26. message GetMsgOneReply {
  27. string Id = ;
  28. string Title = ;
  29. string Content = ;
  30. int64 UserId = ;
  31. int64 Time = ;
  32. bool IsSuccess = ;
  33. string ErrorMsg = ;
  34. }
  35.  
  36. message EditMsgRequest {
  37. string Id = ;
  38. string Title = ;
  39. string Content = ;
  40. }
  41.  
  42. message EditMsgReply {
  43. bool IsSuccess = ;
  44. string ErrorMsg = ;
  45. }
  46.  
  47. message RemoveMsgRequest {
  48. string Id = ;
  49. }
  50.  
  51. message RemoveMsgReply {
  52. bool IsSuccess = ;
  53. string ErrorMsg = ;
  54. }

到这为止我们就完成了协议的编写。

c.将协议生成为C#代码

相对于网站的很多关于C#使用gRpc的教程都是基于.NET项目框架下的,所以可以安装gRpc.Tools,但是.NET Core安装后是找不到工具的,所以读者可以新建一个.NET项目安装该类库,然后将其中的工具复制到Sino.GrpcService.Protocol中,这里读者需要根据你当前的系统去选择,复制完成之后在该项目中新建一个名为“ProtocGenerate.cmd”的文件,在其中输入以下指令:

  1. protoc -I . --csharp_out . --grpc_out . --plugin=protoc-gen-grpc=grpc_csharp_plugin.exe msg.proto

然后读者直接双击运行,就会看到项目下生成了“Msg.cs”和“MsgGrpc.cs”两个文件,这样就完成了所有协议部分的工作了,最终的项目结构如下所示:

d.编写实现代码

有了协议层之后我们就可以开始编写实现了,因为笔者这里使用了MongoDb提供数据所以下文篇幅会较长。

首先打开Sino.GrpcService.Impl项目在其中新建Model文件,然后在该文件夹下新建MsgDM.cs文件,该文件主要是定义MongoDb存储的数据结构,具体内容如下所示:

  1. /// <summary>
  2. /// 消息体
  3. /// </summary>
  4. public sealed class MsgDM
  5. {
  6. /// <summary>
  7. /// 编号
  8. /// </summary>
  9. public ObjectId Id { get; set; }
  10.  
  11. /// <summary>
  12. /// 标题
  13. /// </summary>
  14. public string Title { get; set; }
  15.  
  16. /// <summary>
  17. /// 内容
  18. /// </summary>
  19. public string Content { get; set; }
  20.  
  21. /// <summary>
  22. /// 用户编号
  23. /// </summary>
  24. public long UserId { get; set; }
  25.  
  26. /// <summary>
  27. /// 时间
  28. /// </summary>
  29. public long Time { get; set; }
  30. }

紧接着我们新建Repositories文件夹,在其中新建四个文件分别为“IDataContext.cs”、“DataContext.cs”、“IMsgRepository.cs”和“MsgRepository.cs”。打开IDataContext.cs文件在其中编写如下内容:

  1. /// <summary>
  2. /// 数据库上下文
  3. /// </summary>
  4. public interface IDataContext
  5. {
  6. IMongoDatabase Database { get; set; }
  7. }

打开DataContext.cs文件进行数据库初始化相关工作:

  1. public class DataContext : IDataContext
  2. {
  3. public IMongoDatabase Database { get; set; }
  4.  
  5. public DataContext(IConfigurationRoot config)
  6. {
  7. var client = new MongoClient(config.GetConnectionString("mongodb"));
  8. Database = client.GetDatabase("aSQ0cWkEshl8NiVn");
  9. }
  10. }

打开IMsgRepository.cs,我们需要在其中定义仓储提供的操作:

  1. /// <summary>
  2. /// 消息仓储
  3. /// </summary>
  4. public interface IMsgRepository
  5. {
  6. /// <summary>
  7. /// 获取列表
  8. /// </summary>
  9. Task<List<MsgDM>> GetList(long userId, string title, long startTime, long endTime);
  10.  
  11. /// <summary>
  12. /// 获取实体
  13. /// </summary>
  14. Task<MsgDM> Get(string id);
  15.  
  16. /// <summary>
  17. /// 更新实体
  18. /// </summary>
  19. Task<bool> Update(MsgDM data);
  20.  
  21. /// <summary>
  22. /// 添加实体
  23. /// </summary>
  24. Task<string> Insert(MsgDM data);
  25.  
  26. /// <summary>
  27. /// 删除实体
  28. /// </summary>
  29. Task<bool> Delete(string id);
  30. }

对应的我们还需要打开MsgRepository.cs文件实现该接口:

  1. public class MsgRepository : IMsgRepository
  2. {
  3. private IDataContext _dataContext;
  4. private IMongoCollection<MsgDM> _collection;
  5.  
  6. public MsgRepository(IDataContext dataContext)
  7. {
  8. _dataContext = dataContext;
  9. _collection = _dataContext.Database.GetCollection<MsgDM>("msg");
  10. }
  11.  
  12. public async Task<bool> Delete(string id)
  13. {
  14. var filter = Builders<MsgDM>.Filter.Eq(x => x.Id, new ObjectId(id));
  15. var result = await _collection.DeleteOneAsync(filter);
  16. return result.DeletedCount == ;
  17. }
  18.  
  19. public Task<MsgDM> Get(string id)
  20. {
  21. var objectId = new ObjectId(id);
  22. var result = (from item in _collection.AsQueryable()
  23. where item.Id == objectId
  24. select item).FirstOrDefault();
  25. return Task.FromResult(result);
  26. }
  27.  
  28. public Task<List<MsgDM>> GetList(long userId, string title, long startTime, long endTime)
  29. {
  30. IQueryable<MsgDM> filter = _collection.AsQueryable();
  31. if (userId != )
  32. filter = filter.Where(x => x.UserId == userId);
  33. if (!string.IsNullOrEmpty(title))
  34. filter = filter.Where(x => x.Title.Contains(title));
  35. if (startTime != )
  36. filter = filter.Where(x => x.Time > startTime);
  37. if (endTime != )
  38. filter = filter.Where(x => x.Time < startTime);
  39.  
  40. return Task.FromResult(filter.ToList());
  41. }
  42.  
  43. public async Task<string> Insert(MsgDM data)
  44. {
  45. await _collection.InsertOneAsync(data);
  46. return data.Id.ToString();
  47. }
  48.  
  49. public async Task<bool> Update(MsgDM data)
  50. {
  51. var filter = Builders<MsgDM>.Filter.Eq(x => x.Id, data.Id);
  52. var update = Builders<MsgDM>.Update.Set(x => x.Title, data.Title).Set(x => x.Content, data.Content);
  53.  
  54. var result = await _collection.UpdateOneAsync(Builders<MsgDM>.Filter.Eq(x => x.Id, data.Id), update);
  55.  
  56. return result.ModifiedCount == ;
  57. }
  58. }

完成了上面关于数据库的工作,下面我们就进入正题,开始实现gRpc服务了,首先我们在项目根目录下新建MsgServiceImpl.cs文件,在其中实现我们协议中的服务:

  1. public class MsgServiceImpl : MsgService.MsgServiceBase
  2. {
  3. private IMsgRepository _msgRepository;
  4.  
  5. public MsgServiceImpl(IMsgRepository msgRepository)
  6. {
  7. _msgRepository = msgRepository;
  8. }
  9.  
  10. public override async Task<GetMsgListReply> GetList(GetMsgListRequest request, ServerCallContext context)
  11. {
  12. var result = new GetMsgListReply();
  13. var list = await _msgRepository.GetList(request.UserId, request.Title, request.StartTime, request.EndTime);
  14. result.IsSuccess = true;
  15. result.Items.AddRange(list.Select(x => new GetMsgListReply.Types.MsgItem
  16. {
  17. UserId = x.UserId,
  18. Title = x.Title,
  19. Time = x.Time,
  20. Content = x.Content
  21. }).ToList());
  22. return result;
  23. }
  24.  
  25. public override async Task<EditMsgReply> Edit(EditMsgRequest request, ServerCallContext context)
  26. {
  27. var result = new EditMsgReply();
  28. result.IsSuccess = await _msgRepository.Update(new MsgDM
  29. {
  30. Id = new MongoDB.Bson.ObjectId(request.Id),
  31. Title = request.Title,
  32. Content = request.Content
  33. });
  34.  
  35. return result;
  36. }
  37.  
  38. public override async Task<GetMsgOneReply> GetOne(GetMsgOneRequest request, ServerCallContext context)
  39. {
  40. var msg = await _msgRepository.Get(request.Id);
  41.  
  42. return new GetMsgOneReply
  43. {
  44. IsSuccess = true,
  45. Id = msg.Id.ToString(),
  46. UserId = msg.UserId,
  47. Title = msg.Title,
  48. Content = msg.Content,
  49. Time = msg.Time
  50. };
  51. }
  52.  
  53. public override async Task<RemoveMsgReply> Remove(RemoveMsgRequest request, ServerCallContext context)
  54. {
  55. var result = new RemoveMsgReply();
  56. result.IsSuccess = await _msgRepository.Delete(request.Id);
  57.  
  58. return result;
  59. }
  60. }

三、证书生成

a.安装openssl

首先读者需要从该网站下载openssl安装程序:

Openssl下载

笔者的系统是Win10 64所以下载的是“Win64 OpenSSL v1.1.0b”。

b.制作证书

网上有很多的教程,但是对于新手来说直接给绕晕了,有的有ca、client和service有的没有,这里笔者提供一个全面的cmd脚本(默认CA是自己):

  1. @echo off
  2. set OPENSSL_CONF=c:\OpenSSL-Win64\bin\openssl.cfg
  3.  
  4. echo Generate CA key:
  5. openssl genrsa -passout pass: -des3 -out ca.key
  6.  
  7. echo Generate CA certificate:
  8. openssl req -passin pass: -new -x509 -days -key ca.key -out ca.crt -subj "/C=CN/ST=JS/L=ZJ/O=sino/OU=test/CN=root"
  9.  
  10. echo Generate server key:
  11. openssl genrsa -passout pass: -des3 -out server.key
  12.  
  13. echo Generate server signing request:
  14. openssl req -passin pass: -new -key server.key -out server.csr -subj "/C=CN/ST=JS/L=ZJ/O=sino/OU=test/CN=root"
  15.  
  16. echo Self-sign server certificate:
  17. openssl x509 -req -passin pass: -days -in server.csr -CA ca.crt -CAkey ca.key -set_serial -out server.crt
  18.  
  19. echo Remove passphrase from server key:
  20. openssl rsa -passin pass: -in server.key -out server.key
  21.  
  22. echo Generate client key
  23. openssl genrsa -passout pass: -des3 -out client.key
  24.  
  25. echo Generate client signing request:
  26. openssl req -passin pass: -new -key client.key -out client.csr -subj "/C=CN/ST=JS/L=ZJ/O=sino/OU=test/CN=root"
  27.  
  28. echo Self-sign client certificate:
  29. openssl x509 -passin pass: -req -days -in client.csr -CA ca.crt -CAkey ca.key -set_serial -out client.crt
  30.  
  31. echo Remove passphrase from client key:
  32. openssl rsa -passin pass: -in client.key -out client.key

以上的脚本也会生成我们下面Demo中使用的证书。

四、完善服务端

用了上面的证书之后我们需要继续把服务端启动gRpc服务部分的代码书写完毕,这里笔者是采用命令行形式运行的,所以gRpc的启动是独立放在一个文件文件中,如下RpcConfiguration所示:

  1. public static class RpcConfiguration
  2. {
  3. private static Server _server;
  4. private static IContainer _container;
  5.  
  6. public static void Start(IConfigurationRoot config)
  7. {
  8. var builder = new ContainerBuilder();
  9.  
  10. builder.RegisterInstance(config).As<IConfigurationRoot>();
  11. builder.RegisterInstance(new DataContext(config)).As<IDataContext>();
  12. builder.RegisterAssemblyTypes(typeof(IDataContext).GetTypeInfo().Assembly).Where(t => t.Name.EndsWith("Repository")).AsImplementedInterfaces();
  13.  
  14. _container = builder.Build();
  15. var servercert = File.ReadAllText(@"server.crt");
  16. var serverkey = File.ReadAllText(@"server.key");
  17. var keypair = new KeyCertificatePair(servercert, serverkey);
  18. var sslCredentials = new SslServerCredentials(new List<KeyCertificatePair>() { keypair });
  19. _server = new Server
  20. {
  21. Services = { MsgService.BindService(new MsgServiceImpl(_container.Resolve<IMsgRepository>())) },
  22. Ports = { new ServerPort("0.0.0.0", , sslCredentials) }
  23. };
  24. _server.Start();
  25. _server.ShutdownTask.Wait();
  26. }
  27.  
  28. public static void Stop()
  29. {
  30. _server?.ShutdownAsync().Wait();
  31. }
  32. }

其中我们使用了server.crtserver.key这两个证书,所以在Host项目中需要将这个两个证书文件copy到项目根目录下,如果需要发布的时候包含则需要在project.json中配置如下节:

  1. "publishOptions": {
  2. "include": [ "server.crt", "server.key", "appSettings.json", "appSettings.*.json" ]
  3. }

最后我们需要在Program中启动对应的gRpc即可。

五、客户端编写

完成了服务端的编写剩下的就是客户端的编写,当然客户端的编写相对容易很多,笔者这里直接把Sino.GrpcService.Protocol项目包含到客户端解决方案中了(在正式开发中建议采用nuget包进行管理),为了简单起见,所以只调用了其中一个服务接口:

  1. public static class MsgServiceClient
  2. {
  3. private static Channel _channel;
  4. private static MsgService.MsgServiceClient _client;
  5.  
  6. static MsgServiceClient()
  7. {
  8. var cacert = File.ReadAllText("server.crt");
  9. var ssl = new SslCredentials(cacert);
  10. var channOptions = new List<ChannelOption>
  11. {
  12. new ChannelOption(ChannelOptions.SslTargetNameOverride,"root")
  13. };
  14. _channel = new Channel("grpcservice.t0.daoapp.io:61130", ssl, channOptions);
  15. _client = new MsgService.MsgServiceClient(_channel);
  16. }
  17.  
  18. public static GetMsgListReply GetList(int userId, string title, long startTime, long endTime)
  19. {
  20. return _client.GetList(new GetMsgListRequest
  21. {
  22. UserId = userId,
  23. Title = title,
  24. StartTime = startTime,
  25. EndTime = endTime
  26. });
  27. }
  28. }

需要注意下其中“ChannelOptions.SslTargetNameOverride”这部分是必须的,因为我们是自己生成的证书,所以域名是root,如果是生产环境可以不需要。

六、利用Docker运行

a.安装Docker For Windows

这里需要win10的系统,这样可以直接在ps中直接利用docker指令了。

b.编写Dockerfile

因为1.1版本出来了,但是经过本人的验证,如果你的应用不升级是无法使用该镜像的,默认使用1.1,所以这里我们的Dockerfile需要指定下特定的版本,否则是无法构建的,我们首先在解决方案的根目录下新建Dockerfile文件,然后在其中放入以下命令:

  1. FROM microsoft/dotnet:1.0-sdk-projectjson
  2.  
  3. ADD ./ /usr/local/src
  4. WORKDIR /usr/local/src/Sino.GrpcService.Host/
  5.  
  6. RUN cd /usr/local/src/
  7. RUN dotnet restore -v http://api.nuget.org/v3/index.json
  8. RUN dotnet build
  9.  
  10. EXPOSE
  11.  
  12. CMD ["dotnet","run"]

c.生成镜像并运行

我们打开ps,然后cd到解决方案的文件夹下利用:

  1. docker build -t gRpcService:1.0 .

开始构建,基于国内的情况建议大家将docker默认拉取镜像的地址调整下。生成好之后,利用以下指令去启动即可:

  1. docker run -d name -p : gRpcService gRpcService:1.0

当然客户端连接的地址和端口也要根据-p指定的情况去调整。

七、其他

对应的源码可以访问以下地址:

https://github.com/Vip56/Sino.GrpcService

https://github.com/Vip56/Sino.GrpcClient

如果需要询问相关问题的可以短消息给我。

.NET Core下使用gRpc公开服务(SSL/TLS)的更多相关文章

  1. .NET Core使用gRPC打造服务间通信基础设施

    一.什么是RPC rpc(远程过程调用)是一个古老而新颖的名词,他几乎与http协议同时或更早诞生,也是互联网数据传输过程中非常重要的传输机制. 利用这种传输机制,不同进程(或服务)间像调用本地进程中 ...

  2. 使用gRPC打造服务间通信基础设施

    一.什么是RPC rpc(远程过程调用)是一个古老而新颖的名词,他几乎与http协议同时或更早诞生,也是互联网数据传输过程中非常重要的传输机制. 利用这种传输机制,不同进程(或服务)间像调用本地进程中 ...

  3. .net core下使用FastHttpApi构建web聊天室

    一般在dotnet core下构建使用web服务应用都使用asp.net core,但通过FastHttpApi组建也可以方便地构建web服务应用,在FastHttpApi功能的支持下构建多人聊天室是 ...

  4. Akka-CQRS(10)- gRPC on SSL/TLS 安全连接

    使用gRPC作为云平台和移动前端的连接方式,网络安全应该是必须考虑的一个重点.gRPC是支持ssl/tls安全通讯机制的.用了一个周末来研究具体使用方法,实际上是一个周末的挖坑填坑过程.把这次经历记录 ...

  5. .net core下简单构建高可用服务集群

    一说到集群服务相信对普通开发者来说肯定想到很复杂的事情,如zeekeeper ,反向代理服务网关等一系列的搭建和配置等等:总得来说需要有一定经验和规划的团队才能应用起来.在这文章里你能看到在.net ...

  6. 项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获

    项目开发中的一些注意事项以及技巧总结   1.jquery采用ajax向后端请求时,MVC框架并不能返回View的数据,也就是一般我们使用View().PartialView()等,只能返回json以 ...

  7. Akka-CQRS(13)- SSL/TLS for gRPC and HTTPS:自签名证书产生和使用

    到现在,我们已经完成了POS平台和前端的网络集成.不过,还是那句话:平台系统的网络安全是至关重要的.前一篇博客里我们尝试实现了gRPC ssl/tls网络连接,但测试时用的证书如何产生始终没有搞清楚. ...

  8. dotnet core高吞吐Http api服务组件FastHttpApi

    简介 是dotNet core下基于Beetlex实现的一个高度精简化和高吞吐的HTTP API服务开源组件,它并没有完全实现HTTP SERVER的所有功能,而是只实现了在APP和WEB中提供数据服 ...

  9. .net core 下使用StackExchange的Redis库访问超时解决

    原文:.net core 下使用StackExchange的Redis库访问超时解决 目录 问题:并发稍微多的情况下Redis偶尔返回超时 给出了参考网址? 结论 小备注 引用链接 问题:并发稍微多的 ...

随机推荐

  1. poj -- 1042 Gone Fishing(枚举+贪心)

    题意: John现有h个小时的空闲时间,他打算去钓鱼.钓鱼的地方共有n个湖,所有的湖沿着一条单向路顺序排列(John每在一个湖钓完鱼后,他只能走到下一个湖继续钓),John必须从1号湖开始钓起,但是他 ...

  2. Daily Scrum 12.14

    今日完成任务: 优化了问题页面显示问题的算法:两名开发人员有CCF考试,今天没有完成任务,任务顺延到明天. 明日任务: 黎柱金 解决资源显示全部为同一个PDF的BUG 晏旭瑞 资源搜索问题 孙思权 做 ...

  3. ajax 如何提交数据到后台jsp页面,以及提交完跳转到jsp页面

    我logincheck.jsp页面取传参数代码: String user=request.getParameter("user1"); String pwd=request.get ...

  4. hibernate存储过程 3

              hibernate存储过程 User.hbm.xml文件的内容如下: <?xml version="1.0"?> <!DOCTYPE hibe ...

  5. 一个Email

    Email 1.接受表单数据并用单独变量保存起来,判断用户协议,这个是必须同意的.2.判断验证码,利用验证码类Captcha.3.判断用户名,密码,邮箱规则,利用Verify类验证.4.判断唯一性,邮 ...

  6. K/3 Cloud开发之旅 -- 主页自定义篇(一)

    如果说我们要进行主页自定义篇,首先涉及到的就是登陆的自定义,那么如何进行登录界面的自定义呢 其实登陆界面自定义主要就是图片的替换 ,那么我们就看下登陆界面的图片的组成 登录页面底图有两部分组成,一个是 ...

  7. input输入内容时放大问题

    最近做的微信网站有一个关于input输入框页面放大的问题.比如登录页面刚打开时正常,但用户输入信息登录时,页面就会放大.解决这个问题,首先需要在头部加一个 <meta name="vi ...

  8. 【洛谷P2737】Beef McNuggets

    首先有这样一个结论:若p,q为自然数,且gcd(p,q)=1,那么px+qy不能表示的最大数为pq-p-q 那么本题中p,q均取决于最大的两个数,不妨取256,那么上界为256^2-256*2 之后就 ...

  9. php怎么解决超链接中的中文参数转码问题?

    如题,我需要通过前端的网页传递一个中文参数(如:电脑)给后端的PHP文件,传递方式是通过超链接 "index.php/search/keyword/电脑" ,很明显的中文在传递过程 ...

  10. css3 自定义动画(1)

    <style> /*@-webkit-keyframes 动画名称 {} 用时:-webkit-animation:时间 动画名称; */ /* @-webkit-keyframes mo ...