.Net Core 2.0的一些不大一样的地方(二)——服务器环境

我在《.Net Core 2.0的一些不大一样的地方(一)——基础身份认证》中,说了下关于Cookies的不同之处,在这篇文章中,我说下关于服务器的相关问题。
服务器的环境安装,在官网中介绍的.Net Core 2.0环境的安装方法,是通过源的方法来安装,我尝试了下,失败了,可能是和我的服务器中有之前的.Net Core 1.0环境有关。把原来的环境删掉,先删除/usr/local/bin/dotnet软链接,之后再删除掉.net core的文件夹/opt/dotnet。不过我后面再没有试通过源的方式安装是否没问题,这里我还是使用.net core 1.0的时候安装的方法,下载.net core 2.0 sdk,之后解压缩设置软链接的方式安装。
微软官方给出的通过源安装的方法,参见:《.NET and C# - Get Started in 10 Minutes》一文,自行选择自己使用的操作系统,来按步骤安装。
如果在程序中,使用了身份认证(如Cookies等,见《.Net Core 2.0的一些不大一样的地方(一)——基础身份认证》),那么还需要在Startup.cs文件的Configure方法中,在调用UseAuthentication之前调用UseForwardedHeaders,如下:

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});

app.UseAuthentication();

这样,在做身份验证的时候,能够通过反代服务器取到用户访问的URL,不然的话,会因为URL不一致(用户访问的是Nginx服务器上绑定的域名,而.net core服务器使用的是localhost)导致验证失败。
nginx服务器的安装及反代服务配置与之前的版本没有什么区别,有区别的是守护程序的配置,使用的还是systemd(systemctl),配置中,需要多设置一项:
WorkingDirectory=站点的根目录
如:

[Unit]
Description=Example .NET Web API Application running on Ubuntu
[Service]
WorkingDirectory=/var/aspnetcore/hellomvc
ExecStart=/usr/local/bin/dotnet /var/aspnetcore/hellomvc/hellomvc.dll
Restart=always
RestartSec=10 # Restart service after 10 seconds if dotnet service crashes
SyslogIdentifier=dotnet-example
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
[Install]
WantedBy=multi-user.target

如果不设置这项的话,所有的静态资源文件都请求不到,返回502或404错误。

好了,基本就这些了吧,遇到其他的再继续补充。

.Net Core 2.0的一些不大一样的地方(一)——基础身份认证

近日,把之前使用.net core 1.0写的网站,使用.net core 2.0改写了一下,发现一些不大一样的地方,如果不注意的话,会出现些问题。
一、先说下关于使用Cookie来验证用户登录的地方:
在.net core 1.x时代,具体作法如我前面的文章《.Net Core系列教程(四)—— 基础身份认证》所说,这里我就不重新写了
而在.net core 2.0中,需要做以下调整:
1)在Startup.cs文件中,ConfigureServices方法下添加:

            services.AddAuthentication(options=> {
                options.DefaultChallengeScheme = "Cookie";
                options.DefaultSignInScheme = "Cookie";
                options.DefaultAuthenticateScheme = "Cookie";
            })
            .AddCookie("Cookie", m =>
            {
                m.LoginPath = new PathString("/Manage/CPanel/Login");
                m.AccessDeniedPath = new PathString("/Manage/CPanel/Forbidden");
                m.LogoutPath = new PathString("/Manage/CPanel/Logout");
                m.Cookie.Path = "/";
            });

完整的代码如下:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddApplicationInsightsTelemetry(Configuration);

            services.AddAuthentication(options=> {
                options.DefaultChallengeScheme = "Cookie";
                options.DefaultSignInScheme = "Cookie";
                options.DefaultAuthenticateScheme = "Cookie";
            })
            .AddCookie("Cookie", m =>
            {
                m.LoginPath = new PathString("/Manage/CPanel/Login");
                m.AccessDeniedPath = new PathString("/Manage/CPanel/Forbidden");
                m.LogoutPath = new PathString("/Manage/CPanel/Logout");
                m.Cookie.Path = "/";
            });

            services.AddOptions();
            services.Configure<Models.ConnectionStrings>(Configuration.GetSection("ConnectionStrings"));
            services.AddAuthorization();    //Form基础验证
            services.AddMvc();
        }

之后在Configure方法下添加:

app.UseAuthentication();

完整代码:

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();


            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
            });

            app.UseAuthentication();


            //app.UseApplicationInsightsRequestTelemetry();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            //app.UseApplicationInsightsExceptionTelemetry();

            app.UseStaticFiles();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "area",
                    template: "{area:exists}/{controller=CPanel}/{action=Index}/{id?}");

                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

之后在控制器中使用:
登录时:

            result = _manage.Login(login);
            if (result.status)
            {//登录成功
                string token = result.data.ToString();  //登录成功后生成的token,用于验证登录有效性
                var claims = new List<Claim>()
                {
                    new Claim(ClaimTypes.Name,login.username) 
                };
                var userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, token));
                await HttpContext.SignInAsync("Cookie", userPrincipal,
                  new Microsoft.AspNetCore.Authentication.AuthenticationProperties
                  {
                      ExpiresUtc = DateTime.UtcNow.AddHours(12),
                      IsPersistent = true,
                      AllowRefresh = false
                  });
            }

验证有效性:

            var auth = await HttpContext.AuthenticateAsync("Cookie");
            if(auth.Succeeded)
            {    //验证有效
                string username = auth.Principal.Identity.Name;    //用户名
                //通过验证后的其他处理代码
            }

在其他需要验证是否登录状态,可以在对应的Action或Controller上增加特性:[Authorize],就可以了。

-==以下部分为2018年5月13日新增==-
二、使用Jwt方式
Jwt是Json Web Token的缩写,下面是关于Jwt介绍的一些搬运:

JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库。

JWT包含了使用.分隔的三部分: Header 头部 Payload 负载 Signature 签名
其结构看起来是这样的Header.Payload.Signature

Header
在header中通常包含了两部分:token类型和采用的加密算法。{ "alg": "HS256", "typ": "JWT"} 接下来对这部分内容使用 Base64Url 编码组成了JWT结构的第一部分。

Payload
Token的第二部分是负载,它包含了claim, Claim是一些实体(通常指的用户)的状态和额外的元数据,有三种类型的claim:reserved, public 和 private.Reserved claims: 这些claim是JWT预先定义的,在JWT中并不会强制使用它们,而是推荐使用,常用的有 iss(签发者),exp(过期时间戳), sub(面向的用户), aud(接收方), iat(签发时间)。 Public claims:根据需要定义自己的字段,注意应该避免冲突 Private claims:这些是自定义的字段,可以用来在双方之间交换信息 负载使用的例子:{ "sub": "1234567890", "name": "John Doe", "admin": true} 上述的负载需要经过Base64Url编码后作为JWT结构的第二部分。

Signature
创建签名需要使用编码后的header和payload以及一个秘钥,使用header中指定签名算法进行签名。例如如果希望使用HMAC SHA256算法,那么签名应该使用下列方式创建: HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) 签名用于验证消息的发送者以及消息是没有经过篡改的。 完整的JWT 完整的JWT格式的输出是以. 分隔的三段Base64编码,与SAML等基于XML的标准相比,JWT在HTTP和HTML环境中更容易传递。 下列的JWT展示了一个完整的JWT格式,它拼接了之前的Header, Payload以及秘钥签名。

关于概念性的介绍就写到这里,下面是使用方法。
先说流程:
客户端提交用户名和密码,发起登录请求;服务器接收到请求后,验证用户名和密码的合法性,验证通过,给生成token返回给客户端;
客户端得到token之后自行保存;
客户端再次发起其他需要用户登录身份的请求时,在HTTP头中带上前面申请到的token;服务器接受到请求后,验证token的合法性,进行下一步操作。

这里就涉及到了签名和验签两部分。先是签名:
在自己的项目中,找一个适合的地方,添加以下几个类:

  1. RSAKeyHelper.cs

  2. TokenAuthOption.cs

  3. RsaParameterStorage.cs

RSAKeyHelper.cs类内容:

    public class RSAKeyHelper
    {
        public static RSAParameters GenerateKey()
        {
            string keyDir = $"{PlatformServices.Default.Application.ApplicationBasePath}key";
            if (TryGetKeyParameters(keyDir, true, out RSAParameters keyParams) == false)
            {
                keyParams = GenerateAndSaveKey(keyDir);
            }
            return keyParams;
        }

        /// <summary>
        /// 从本地文件中读取用来签发 Token 的 RSA Key
        /// </summary>
        /// <param name="filePath">存放密钥的文件夹路径</param>
        /// <param name="withPrivate"></param>
        /// <param name="keyParameters"></param>
        /// <returns></returns>
        public static bool TryGetKeyParameters(string filePath, bool withPrivate, out RSAParameters keyParameters)
        {
            string filename = withPrivate ? "key.json" : "key.public.json";
            keyParameters = default(RSAParameters);
            string path = Path.Combine($"{filePath}/", filename);
            //Console.WriteLine($"filePath:{path}");
            if (File.Exists(path) == false) return false;
            string json = File.ReadAllText(path);
            var parameterStorage = JsonConvert.DeserializeObject<RsaParameterStorage>(json);
            keyParameters.D = parameterStorage.D;
            keyParameters.DP = parameterStorage.DP;
            keyParameters.DQ = parameterStorage.DQ;
            keyParameters.Exponent = parameterStorage.Exponent;
            keyParameters.InverseQ = parameterStorage.InverseQ;
            keyParameters.Modulus = parameterStorage.Modulus;
            keyParameters.P = parameterStorage.P;
            keyParameters.Q = parameterStorage.Q;            
            return true;
        }

        /// <summary>
        /// 生成并保存 RSA 公钥与私钥
        /// </summary>
        /// <param name="filePath">存放密钥的文件夹路径</param>
        /// <returns></returns>
        public static RSAParameters GenerateAndSaveKey(string filePath)
        {
            RSAParameters publicKeys, privateKeys;
            using (var rsa = new RSACryptoServiceProvider(2048))
            {
                try
                {
                    privateKeys = rsa.ExportParameters(true);
                    publicKeys = rsa.ExportParameters(false);
                }
                finally
                {
                    rsa.PersistKeyInCsp = false;
                }
            }
            if (Directory.Exists(filePath) == false) Directory.CreateDirectory(filePath);
            File.WriteAllText(Path.Combine(filePath, "key.json"), ToJsonString(privateKeys));
            File.WriteAllText(Path.Combine(filePath, "key.public.json"), ToJsonString(publicKeys));
            return privateKeys;
        }

        // 转换成 json 字符串
        static string ToJsonString(RSAParameters parameters)
        {
            var parameterStorage = new RsaParameterStorage
            {
                D = parameters.D,
                DP = parameters.DP,
                P = parameters.P,
                DQ = parameters.DQ,
                Q = parameters.Q,
                Exponent = parameters.Exponent,
                InverseQ = parameters.InverseQ,
                Modulus = parameters.Modulus
            };
            return JsonConvert.SerializeObject(parameterStorage);
        }
    }

这个类主要干了这些事:
1.生成公钥私钥对并转成JSON保存到本地;
2.读取保存在本地的公钥私钥对并生成Key

TokenAuthOption.cs类内容:

    public class TokenAuthOption
    {
        /// <summary>
        /// 受众,代表将要使用这些token的实体
        /// </summary>
        public static string Audience { get; };

        /// <summary>
        /// 发行者,表示生成token的实体
        /// </summary>
        public static string Issuer { get; };

        public static RsaSecurityKey Key { get; } = new RsaSecurityKey(RSAKeyHelper.GenerateKey());
        
        /// <summary>
        /// 安全密钥和创建签名算法
        /// </summary>
        public static SigningCredentials SigningCredentials { get; } = new SigningCredentials(Key, SecurityAlgorithms.RsaSha256Signature);

        /// <summary>
        /// 有效期
        /// </summary>
        public static TimeSpan ExpiresSpan { get; } = TimeSpan.FromDays(30);
    }

RsaParameterStorage.cs类,是用于保存和读取生成的公私钥对的转换,因为ASP.NET Core 2.0之后,私密信息在转成JSON时不允许保存了,必须要转一下,为了省事,这里就用了个最笨的办法:

    public class RsaParameterStorage
    {
        public byte[] D { get; set; }
        public byte[] DP { get; set; }
        public byte[] DQ { get; set; }
        public byte[] Exponent { get; set; }
        public byte[] InverseQ { get; set; }
        public byte[] Modulus { get; set; }
        public byte[] P { get; set; }
        public byte[] Q { get; set; }
    }

上面这些都是准备工作,下面开始配置:
打开Startup.cs文件,在ConfigureServices方法中添加:

           //Jwt验证
            //http://www.cnblogs.com/rocketRobin/p/8058760.html
            services.AddAuthentication(options => {
                options.DefaultAuthenticateScheme = "JwtBearer";
                options.DefaultChallengeScheme = "JwtBearer";                
            })            
            .AddJwtBearer("JwtBearer", jwtBearerOptions =>
            {
                jwtBearerOptions.RequireHttpsMetadata = false;
                
                jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
                {                    
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = TokenAuthOption.Key,
                    ValidateIssuer = true,
                    ValidIssuer = TokenAuthOption.Issuer,//The name of the issuer,
                    ValidateAudience = true,
                    ValidAudience = TokenAuthOption.Audience,//The name of the audience,
                    ValidateLifetime = true, //validate the expiration and not before values in the token
                    ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date
                };
                jwtBearerOptions.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = c =>
                    {
                        c.NoResult();
                        c.Response.StatusCode = 200;
                        c.Response.ContentType = "application/json";
                        return c.Response.WriteAsync(
                            JsonConvert.SerializeObject(
                                new Biz126.Models.ReturnModel<object>()
                                {
                                    Status = false,
                                    Message = "登录信息已失效",
                                    Code = 403,
                                    ResultData = 
                                    new
                                    {
                                        tokenExpired = true
                                    }
                                }
                            )
                        );
                    }
                };
            });

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();  

这里需要注意:如果有开启CORS跨域的话,一定要放在CORS的配置之后。
还是在Startup.cs文件中,在Configure方法内,如果有CORS配置的话,也是在CORS配置之后,添加:

app.UseAuthentication();

我的完整的Startup.cs文件如下,包含了Redis、数据库连接、CORS跨域、log4net和Jwt验证:

    public class Startup
    {
        public static ILoggerRepository repository { get; set; }
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();

            if (env.IsDevelopment())
            {
                // This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
                builder.AddApplicationInsightsSettings(developerMode: true);
            }
            Configuration = builder.Build();
            RedisHelper.InitializeConfiguration(Configuration);          //Redis  

            repository = LogManager.CreateRepository("NETCoreRepository");
            XmlConfigurator.Configure(repository, new FileInfo("log4net.config"));
        }

        public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOptions();
            services.Configure<Dictionary<string, string>>(Configuration.GetSection("ConnectionStrings"));   //数据库连接

            //CORS跨域
            services.AddCors(options =>
            {
                options.AddPolicy("AnyOrigin", builder =>
                {
                    builder
                        .WithOrigins("http://localhost:65176")
                        .AllowAnyOrigin()
                        .AllowAnyMethod()
                        .AllowAnyHeader()
                        .AllowCredentials();
                });
            });

            services.Configure<MvcOptions>(options =>
            {
                options.Filters.Add(new CorsAuthorizationFilterFactory("AnyOrigin"));
            });

            //Jwt验证
            //http://www.cnblogs.com/rocketRobin/p/8058760.html
            services.AddAuthentication(options => {
                options.DefaultAuthenticateScheme = "JwtBearer";
                options.DefaultChallengeScheme = "JwtBearer";                
            })            
            .AddJwtBearer("JwtBearer", jwtBearerOptions =>
            {
                jwtBearerOptions.RequireHttpsMetadata = false;
                
                jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
                {                    
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = TokenAuthOption.Key,
                    ValidateIssuer = true,
                    ValidIssuer = TokenAuthOption.Issuer,//The name of the issuer,
                    ValidateAudience = true,
                    ValidAudience = TokenAuthOption.Audience,//The name of the audience,
                    ValidateLifetime = true, //validate the expiration and not before values in the token
                    ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date
                };
                jwtBearerOptions.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = c =>
                    {
                        c.NoResult();
                        c.Response.StatusCode = 200;
                        c.Response.ContentType = "application/json";
                        return c.Response.WriteAsync(
                            JsonConvert.SerializeObject(
                                new Biz126.Models.ReturnModel<object>()
                                {
                                    Status = false,
                                    Message = "登录信息已失效",
                                    Code = 403,
                                    ResultData = 
                                    new
                                    {
                                        tokenExpired = true
                                    }
                                }
                            )
                        );
                    }
                };
            });

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();         
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            loggerFactory.AddLog4Net();

            //CORS跨域
            app.UseCors("AnyOrigin");
            app.UseAuthentication();

            app.UseStaticFiles();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "area",
                    template: "{area:exists}/{controller=Product}/{action=List}/{id?}");

                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

    }

这里配置结束,下面是开始使用了:
先增加一个生成token的方法,我这里是单独拿的一个类来做的:
Authorize.cs类:

    public static class Authorize
    {
        /// <summary>
        /// 生成Token
        /// </summary>
        /// <param name="userToken"></param>
        /// <returns></returns>
        public static string GenerateToken(Biz126.User.Models.UserToken userToken)
        {
            var handler = new JwtSecurityTokenHandler();
            string jti = TokenAuthOption.Audience + userToken.User_Name + DateTime.Now.Add(TokenAuthOption.ExpiresSpan).Millisecond;
            jti = WebTools.Tools.MD5Cryptog(jti); // Jwt 的一个参数,用来标识 Token
            ClaimsIdentity identity = new ClaimsIdentity(
                new GenericIdentity(userToken.User_Name, "TokenAuth"),
                new[] {
                    new Claim("user_id", userToken.User_Id.ToString()), //用户ID
                    new Claim("user_name",userToken.User_Name), //用户名
                    new Claim("user_type",userToken.User_Type.ToString()),   //身份
                    new Claim("jti",jti,ClaimValueTypes.String) // jti,用来标识 token
                }
            );
            var securityToken = handler.CreateToken(new SecurityTokenDescriptor
            {
                Issuer = TokenAuthOption.Issuer,
                Audience = TokenAuthOption.Audience,
                SigningCredentials = TokenAuthOption.SigningCredentials,
                Subject = identity,
                NotBefore = DateTime.Now,
                Expires = DateTime.Now.Add(TokenAuthOption.ExpiresSpan)
            });
            return handler.WriteToken(securityToken);
        }
    }

重写IHttpContextAccessorExtension.cs类,得到当前用户信息:

    public static class IHttpContextAccessorExtension
    {
        public static User.Models.UserToken CurrentUser(this IHttpContextAccessor httpContextAccessor)
        {
            var claimsIdentity = httpContextAccessor?.HttpContext?.User?.Identity as ClaimsIdentity;
            //var stringUserId = claimsIdentity?.Claims?.FirstOrDefault(x => x.Type.Equals("user_id", StringComparison.CurrentCultureIgnoreCase))?.Value;
            //int.TryParse(stringUserId ?? "0", out int userId);

            var userToken = new User.Models.UserToken();
            var claims = claimsIdentity?.Claims?.ToList();
            var propertys = userToken.GetType().GetProperties();
            foreach (PropertyInfo property in propertys)
            {
                string name = property.Name.ToLower();
                var value = claims?.FirstOrDefault(x => x.Type.Equals(name, StringComparison.CurrentCultureIgnoreCase))?.Value;
                property.SetValue(userToken, string.IsNullOrEmpty(value) ? null : Convert.ChangeType(value, property.PropertyType), null);
            }
            return userToken;
        }
    }

用户登录时,验证账号密码通过,调用Authorize.GenerateToken(userinfo)方法生成token返回给客户端;
客户端在请求头中增加"Authorization",值为"Bearer"+空格+Token,如“Bearer Header.Payload.Signature”

服务器WebAPI接口在控制器的构造函数中,这样写:

        private Biz126.Models.UserToken current_userToken = new Biz126.Models.UserToken();   //当前用户基本信息

        public MemberController(IOptions<Dictionary<string, string>> settings, IHttpContextAccessor httpContextAccessor)
        {
            current_userToken = httpContextAccessor.CurrentUser();
        }

就可以拿到保存在token中的用户信息了。

当然,记得要在需要使用验证的控制器或者方法上,增加[Authorize]特性,如果不需要验证的话,要增加[AllowAnonymous]。

以上。

使用iText5来处理PDF

项目要求,通过pdf模板,把用户提交的数据保存到一个PDF文件中。其中有文字内容,也有图片。之前选了aspose.pdf,因为抠门,不能花钱买,就从网上找的的开心版,好不容易出来点模板,结果插入图片的时候,同一页只能插入一张图片,而官方的试用版是可以正常两张的,另外字段比较多,速度比较慢,几百个字段需要一分多钟,效率很低,放弃。之后尝试iText,发现要比aspose.pdf好用的多,下面就说下用法。
需要通过nuget安装iTextSharp,选第一个,版本号是5.5.12,也就是iText5版本,该版本是AGPL许可。
完整代码如下:

using iTextSharp.text;
using iTextSharp.text.pdf;
using System.IO;

namespace Tools
{
    public class iTextPdf
    {
        Document pdfDocument = new Document();
        private string _path = "";

        #region 内容属性
        /// <summary>
        /// 文字
        /// </summary>
        public string Content { get; set; }

        /// <summary>
        /// X坐标
        /// </summary>
        public float X { get; set; }

        /// <summary>
        /// Y坐标
        /// </summary>
        public float Y { get; set; }

        /// <summary>
        /// 页索引(从0开始)
        /// </summary>
        public int PageIndex { get; set; }

        /// <summary>
        /// 类型
        /// 1-文字
        /// 2-勾选(图片)
        /// 3-签名
        /// 4-身份证正面
        /// 5-身份证反而
        /// </summary>
        public int Type { get; set; }

        /// <summary>
        /// 字体大小
        /// </summary>
        public float textSize { get; set; }

        /// <summary>
        /// 宽度(图片使用)
        /// </summary>
        public float Width { get; set; }

        /// <summary>
        /// 高度(图片使用)
        /// </summary>
        public float Height { get; set; }
        #endregion


        public iTextPdf()
        {
            _path = string.Format("{0}/template.pdf", System.Web.HttpContext.Current.Request.MapPath(System.Web.Configuration.WebConfigurationManager.AppSettings["ConfigDir"]));
        }

        public iTextPdf(string path)
        {
            _path = path;
        }

        public void SavePdf(List<iTextPdf> pdflist,string savefile)
        {
            using (Stream inputPdfStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.Read))   //打开pdf模板
            using (Stream outputPdfStream = new FileStream(savefile, FileMode.Create, FileAccess.Write, FileShare.None))    //创建新的pdf对象用于保存
            {
                var reader = new PdfReader(inputPdfStream);
                var stamper = new PdfStamper(reader, outputPdfStream);
                var list = pdflist.OrderBy(x => x.PageIndex);   //对页号运行排序
                //FontFactory.RegisterDirectory("C:\\WINDOWS\\Fonts");  //字体目录
                list.Select(x => x.PageIndex).Distinct().ToList().ForEach(page =>
                {   //先按页进行遍历,可以减少实例化pdfContentByte的次数,减少资源占用
                    var pdfContentByte = stamper.GetOverContent(page);
                    Rectangle pagesize = reader.GetPageSizeWithRotation(page);
                    float height = pagesize.Height;
                    list.Where(x => x.PageIndex.Equals(page)).ToList().ForEach(item =>
                    { //针对每页进行要插入的内容进行遍历
                        switch (item.Type)
                        {
                            case 1:
                            case 2:
                                string content = item.Content;
                                float fontsize = 12;    //字号
                                if (!string.IsNullOrEmpty(content))
                                {   //只处理有文字内容的
                                    if ((2).Equals(item.Type))
                                    {   //对勾选的内容进行处理
                                        if ("1".Equals(content))
                                        {   //标记为1的,内容换成√,按插入文字处理
                                            content = "√"; 
                                            fontsize = 16;
                                        }
                                        else
                                        {
                                            content = "";
                                            break;
                                        }

                                    }
                                    BaseFont bfTimes = BaseFont.CreateFont(System.Web.HttpContext.Current.Request.MapPath("~/fonts/simhei.ttf"), BaseFont.IDENTITY_H, BaseFont.EMBEDDED);   //注册字体,支持中文
                                    pdfContentByte.SetColorFill(BaseColor.BLACK);

                                    pdfContentByte.SetFontAndSize(bfTimes, fontsize);   //设置字体及字号
                                    pdfContentByte.BeginText(); //开始处理文字
                                    pdfContentByte.ShowTextAligned(Element.ALIGN_LEFT, content, item.X, height - item.Y, 0);
                                    pdfContentByte.EndText();   //文字处理达成
                                }

                                break;
                            case 4:
                            case 5:  //身份证图片
                                string imgpath = System.Web.HttpContext.Current.Request.MapPath(item.Content);  //图片物理路径
                                using (Stream inputImageStream = new FileStream(imgpath, FileMode.Open, FileAccess.Read, FileShare.Read))   //打开图片
                                {
                                    Image image = Image.GetInstance(inputImageStream);
                                    image.SetAbsolutePosition(item.X, item.Y);  //图片坐标
                                    image.ScaleAbsolute(item.Width, item.Height);   //设置图片宽度和高度
                                    pdfContentByte.AddImage(image);
                                }
                                break;
                        }
                    });
                });

                stamper.Close();    //关闭同时保存
            }
        }
    }
}

关于亿达普罗旺斯业主维权的一点看法

今天,有网友反映,大连亿达房产的普罗旺斯业主集体到大连高新区管委会门前维权,原因是买房时承诺的学区化为泡影。
据了解到的情况是这样的,开发商普罗旺斯小区一二三期打着12年理工教育的口号卖房,房子卖差不多了就偷换概念弄了个理工的老师当校长的普通学校。而在买房之前,也有人在民心网询问学校的事情,而且也得到了相关部门的确认,是理工附属学校。
当然,有人会说开发商无法确定学区,学区是教育局定的;也有人会说,这是开发商虚假宣传,应该去找开发商的,到管委会找什么事;还有的人会冷嘲热讽,嘲笑买学区房的业主,笑话他们孩子将来的好与坏只与孩子和家长有关,学区房无用。等等等等。
我想说的是,正是学区是教育局定的,而且在买房之前得到了教育局的肯定,说是理工附属学校,业主才会买的房,那么去找政府不找开发商,是站得住脚的;关于嘲笑别人买学区房的问题,我想说的是,要么是吃不到葡萄说葡萄酸,要么就和刘强东说的自己脸盲没觉得奶茶漂亮是一个道理,因为读的都是贵族学校,不需要考虑学区。
有的人强调重点在人,学区无用,关于这个问题,我再多聊两句。在孩子的教育这个问题上,我承认“人”是重点之一,但并不是唯一因素。影响孩子未来发展的因素有很多,除了孩子自身之外,家庭、学校在这其中都扮演着重要的角色。有其父必有其子、孟母三迁等说的就是家庭、学校环境对孩子的影响。
打一个比方,农村的最好的学校,放到城里也不及一个中等的学校。考上同一个大学成绩相当的两个孩子,来自农村的孩子在中学时的名次,要比城市孩子的名次要高,而其他的综合素质要比城市孩子的要差很多;同一个城市里的高中,重点中学的重本率都要比最差的高中本科率要高,差的学校打架斗殴拿刀捅人的要比重点学校的要多。当然,你又可能会说,是家长没教育好,这个理由我无法反驳,但是我想不明白的是,孩子在学校的时候,我还能跟着到学校看着他吗?现在能明白吧?
关于这件事,业主去政府门口固然不对,但是当弱势群体与强势群体抗衡时,又打不过他,除了找他爹之外,还有什么办法呢?
有人说法制不健全,就这件事来说,是法制不健全的原因吗?当然你也可以归咎到法制不健全上。以中国目前的法律法规,绝大多数的民事刑事纠纷都能找到处理方法。
有法不依,执法不严,办事不公,人情社会,这是根源,当然也需要更加完善的监督体系。
俗话说严以律己,宽以待人,希望机关人员都能严格的要求自己,宽容的对待百姓。

附相关资料:
相关文章:大连高新,学区房之殇。

防原文被删备份
防原文被删备份

2017.7.3 9:30 高新区管委会门前
2017.7.3 9:30 高新区管委会门前

网友提供的相关资料

经过网络搜索,普罗旺斯业主维权不止一次:
2016.2.21 大连亿达普罗旺斯业主维权纪实
2016.12.31 亿达普罗旺斯二期违规卖车位,亿达物业蛮横欺压普罗旺斯二期

亿达普罗旺斯小区简介:
(以下信息来源于网络)
亿达旅顺南路区域开发之首期低密度高端住宅社区,26年开发经验的巅峰代表作。项目独有大连稀缺的山谷地貌,空气质量绝佳、景观资源丰富。超大山体公园与低密建筑自然相生,幽静、私密、尊贵。依托软件园二期的迅猛发展,凌水湾、河口湾及大连天地的相继兴建,轻轨及地铁一号线的陆续投入使用,区域未来的商业和交通配套日益完善,发展潜力巨大。

详细信息

  • 楼盘名: 亿达普罗旺斯

  • 楼盘地址: 大连高新园区西段,小平岛沿旅顺南路西行1公里

  • 楼盘特色:近轻轨、品牌房企、山景房

  • 所属区域板块: 高新旅顺南路

  • 轨道交通: 大连轻轨8号线-蔡大岭站

  • 房屋现状: 期房

  • 售价: 18000元/平米备注: 顶跃18000元/平,底跃20000元/平

  • 开盘说明:

  • 预计交房时间: 2013-10-01

  • 交房说明: 一期2013年10月1日交房,二期2014年10月30日交房。

基本信息

  • 开发商: 大连软件园安博开发有限公司

  • 投资商: 美国安博基金

  • 售楼处地址: 大连高新园区西段,小平岛沿旅顺南路西行1公里

  • 物业类型: 住宅

  • 建筑形式: 多层、小高层、高层

  • 装修情况: 毛坯、精装修

  • 产权年限: 70年

  • 楼座信息: 一期:2栋30层高层、17栋4-6层多层、11栋8-11层小高层;二期:19栋(1-19#)11层小高层、6栋(20-25#)32层高层

  • 供热方式: 地热

  • 规划面积: 135000平方米

  • 建筑面积: 165000平方米

  • 规划户数: 1284户

  • 车位数: 1:1

  • 容积率: 1.26

  • 绿化率: 42%

  • 得房率: 高层80%;小高层88%

  • 物业公司:大连亿达物业管理有限公司

  • 物业费: 2.60元/平方米·月

配套信息

  • 公交: 和平广场-大龙王塘(蔡大岭站)、站北广场-旅顺(蔡大岭站)、802路(普罗旺斯站)

  • 轨道交通:大连轻轨8号线(202路延伸线)(蔡大岭站)

  • 教育: 中心小学、书香园小学、群英小学、辽宁师范大学附中、育明高中、大连第十七中学、大连理工大学附小

  • 医院: 医大二院

  • 银行: 浦发银行、建设银行、大连银行

  • 社区配套: 小区内部配套有超市、便利店等利民设施

  • 其他:小区内部配套有:便利店、洗衣房等

  • 所属小学: 大连理工大学附属学校小学部

  • 所属中学: 大连理工大学附属学校初中部

楼盘相关信息
来源:购房网

致逝去的青春

今天,又是一年一度的高考。遥想14年前,我第一次参加高考,那年应该是第一年调整到6月7日开始高考吧,也是辽宁省第一年大综合,而我们也是高考政策调整下的牺牲品,我当时选的理科问题还不大,当时选文科的死了一大批。
时间过去的太过久远,很多很多事情我已经记不清了。>
不记得当年在哪个考场,更不记得有哪些考题,只是依稀记得,我们去县城(当时是叫做市,县级市)考试,住在党校,而学校也是我们学校每年高考的定点住宿的地方。考试的时候大巴车把我们送到考场,当年正值非典,还记每天早中晚各量一次体温,如果体温异常要及时上报。进考场之前也要量体温,体温异常的需要怎么处理我就不知道了,因为我没经历过也没见过有体温异常的,只是后来听说对于异常的,会单独安排考场,有穿防护服的来监考。
还记得那时候,因为非典,我们已经在学校连续呆了好几个月了回不了家,而且也出不了学校大门,家长来看孩子,就在大铁门的外面,让人感觉像是来探监。终于有一次,学校放我们回去了,但是不让我们自己坐车走,而是用大巴车把我们送回家,这也是唯一一次回家做的免费的车。走到快到家的路上,才发现正在修路,多年的土路要铺柏油路了,心中的自豪感突然的就起来了,难掩喜悦之情。
还记得那时候,每天早上天不亮就要起床,参加集体的晨练,就是以班为单位,集体跑步,记不清每天要跑多少圈了,要跑多长时间。跑完步之后再回宿舍去洗漱,再去食堂吃早餐,再去上早自习。
还记得那时候,每天晚上的晚自习中间休息的时间,都会去操场跑步,拉单杠撑双杠,让我这个瘦弱的身体,也有了两块胸肌,一直到现在还很明显。
还记得那时候,我暗恋的女生,现在已经不联系了,不知现在过的怎样,孩子也已经很大了吧。
转眼间,我已经离开当年的高中14年,而我也一直再也没回去看过。这13年如果说长的话,真的感觉是飞一样;如果说短的话,早已经物是人非。当年曾经教过我的老师都已经不在这里了吧,而学校的高中部也停止了招生,黄了。
难免会有点感慨。就这样吧。

分类

最新文章

最近回复

  • 青山: 某种原因,暂停友链,抱歉。
  • 青山: 计划搬迁到腾讯云,正...
  • 老徐: 具体要哪个呢?
  • 老徐: 是不是有点老?
  • 青山: 哇,林志炫
  • 老白: 哇,这改的可以,能不...
  • 老徐: 23333
  • 许建华: 我是为了表情包来的~
  • vultr vps: 感谢分享深入学习
  • 青山: 每一次都是不同的感受

归档

标签云

C# .net core asp.net 情感 SQL mongodb sql server EasyUI 安全 激活 linux 身份验证 https typecho .net sql注入 kms MVC IIS 高并发 IE 坑爹 服务器 mysql Oracle Combobox Datagrid 口语 数据抓取

其它