
一个普通程序员
我的博客即将入驻“云栖社区”,诚邀技术同仁一同入驻。
在开发过程中Spring Mvc 默认 Url和参数名称都是区分大小写的 比如:www.a.com/user/getUserInfo?userId=1 www.a.com/user/getuserInfo?userId=1 www.a.com/user/getUserInfo?userid=1 www.a.com/user/getuserinfo?userid=1 这些都认为不同的地址和参数,在实际中用户根本不区分这些,所以我们要忽略大小写 URL忽略大小写 import org.springframework.context.annotation.Configuration; import org.springframework.util.AntPathMatcher; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; /** * Created by tianwei on 2017/6/22. */ @Configuration public class SpringWebConfig extends WebMvcConfigurationSupport { @Override public void configurePathMatch(PathMatchConfigurer configurer) { AntPathMatcher pathMatcher = new AntPathMatcher(); pathMatcher.setCaseSensitive(false); configurer.setPathMatcher(pathMatcher); } } 参数名称忽略大小写 import java.io.IOException; import java.util.Collections; import java.util.Enumeration; import java.util.Map; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.web.filter.OncePerRequestFilter; public class CaseInsensitiveRequestParameterNameFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { filterChain.doFilter(new CaseInsensitiveParameterNameHttpServletRequest(request), response); } public static class CaseInsensitiveParameterNameHttpServletRequest extends HttpServletRequestWrapper { private final LinkedCaseInsensitiveMap<String[]> map = new LinkedCaseInsensitiveMap<>(); @SuppressWarnings("unchecked") public CaseInsensitiveParameterNameHttpServletRequest(HttpServletRequest request) { super(request); map.putAll(request.getParameterMap()); } @Override public String getParameter(String name) { String[] array = this.map.get(name); if (array != null && array.length > 0) return array[0]; return null; } @Override public Map<String, String[]> getParameterMap() { return Collections.unmodifiableMap(this.map); } @Override public Enumeration<String> getParameterNames() { return Collections.enumeration(this.map.keySet()); } @Override public String[] getParameterValues(String name) { return this.map.get(name); } } } 定义Bean <!--输入参数忽略大小写--> <bean id="caseInsensitiveRequestFilterProxy" class="org.springframework.web.filter.DelegatingFilterProxy"> <property name="targetBeanName" value="caseInsensitiveRequestFilter"/> </bean> <bean id="caseInsensitiveRequestFilter" class="com.hantianwei.util.CaseInsensitiveRequestParameterNameFilter"> </bean> web.xml 增加Filter <filter> <filter-name>caseInsensitiveRequestFilterProxy</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>caseInsensitiveRequestFilterProxy</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 到此再次运行项目就可以了,最上面的URL访问的是同一页面了
需求 IDEA 很方便集成了Maven,但是也有相应的问题,比如使用Maven仓没有包的时候不太方便,这时我们需要建立自已的本地仓库来实现 实现 找到Idea的安装目录下面的Maven,我的在 C:\Program Files (x86)\JetBrains\IntelliJ IDEA 2016.3\plugins\maven\lib\maven3 打开conf 目录中的 settings.xml 找到 <!-- localRepository | The path to the local repository maven will use to store artifacts. | | Default: ${user.home}/.m2/repository <localRepository>/path/to/local/repo</localRepository> --> 修改为 <localRepository>D:/Work/maven-dependcies</localRepository> 本地仓就建完了,这时需要加入我们自已的JAR, <dependency> <groupId>taobao-sdk</groupId> <artifactId>taobao-sdk-java-auto</artifactId> <version>1.0</version> </dependency> 需要在本地仓目录(D:/Work/maven-dependcies)下新建 taobao-sdk/taobao-sdk-java-auto/1.0/taobao-sdk-java-auto-1.0.jar 到些建完了,项目可以正常运行
Duboo是什么 DUBBO是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,是阿里巴巴SOA服务化治理方案的核心框架,每天为2,000+个服务提供3,000,000,000+次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。可以看出在阿里内部广泛应用,类似的还有Spring Cloud。 准备工作 准备两虚机,我这里用了CentOS7.2,加上本机可组成多提供者和消费者(当然一个虚机和不用虚机也可以)我准备的两台IP为:192.168.124.129(用于 dubbo-admin) 192.168.124.131(用于 zookeeper 注册中心) 具体实现步骤 1、安装zookeeper 在其中一台虚机(192.168.124.131)中安装zookeeper,我这里安装的是zookeeper-3.4.9 下载后上传到虚机任意目录后解压,我上传到了 /usr/local 中文件夹名 zookeeper 后启动 /usr/local/zookeeper/bin/zkServer.sh start [root@localhost ~]# /usr/local/zookeeper/bin/zkServer.sh start ZooKeeper JMX enabled by default Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg Starting zookeeper ... STARTED 2、部署dubbo-amdin 在另一台虚机(192.168.124.129)中部署dubbo-amdin 如果用的是duboo-admin 2.5.4的话是不能用jdk1.8,可以从github下载源码编译,最新版是支持的 部署很简单, 用编译好的直接替换 Tomcat 下webapps/ROOT中文件,打开WEB-INF/dubbo.properties并进行如下修改 dubbo.registry.address=zookeeper://192.168.124.131:2181 注册中心地址 dubbo.admin.root.password=root --管理员密码 dubbo.admin.guest.password=guest --用户密码 打开 192.168.124.129:8080 出现登录界面如下: 用root账号登录界面如下: 登录成功后看到如下界面,提供者和消费者都还没有,接下来的任务是注册提供者 3、提供者实现(Provider) 先建项目: 最格结构如下: 提供者配置文件applicationProvider.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- 具体的实现bean --> <bean id="demoService" class="io.kuaibao.provider.service.impl.UserServiceImpl" /> <!-- 提供方应用信息,用于计算依赖关系 --> <dubbo:application name="dubbo_provider" /> <!-- 使用zookeeper注册中心暴露服务地址 --> <dubbo:registry address="zookeeper://192.168.124.131:2181" /> <!-- 用dubbo协议在20880端口暴露服务 --> <dubbo:protocol name="dubbo" port="20881" /> <!-- 声明需要暴露的服务接口 --> <dubbo:service interface="io.kuaibao.provider.service.UserService" ref="demoService" /> </beans> 4、消费者 消费者配置文件applicationConsumer.xml <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 --> <dubbo:application name="dubbo_consumer" /> <!-- 使用multicast广播注册中心暴露发现服务地址 --> <dubbo:registry protocol="zookeeper" address="zookeeper://192.168.124.131:2181" /> <!-- 生成远程服务代理,可以和本地bean一样使用demoService --> <dubbo:reference id="demoService" interface="io.kuaibao.provider.service.UserService" check="false" /> </beans> MVC配置 spring-mvc.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"> <!-- 自动扫描 @Controller--> <context:component-scan base-package="io.kuaibao"/> <!--避免IE执行AJAX时,返回JSON出现下载文件 --> <bean id="mappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> </list> </property> </bean> <!-- 启动SpringMVC的注解功能,完成请求和注解POJO的映射 --> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="messageConverters"> <list> <ref bean="mappingJacksonHttpMessageConverter"/> <!-- JSON转换器 --> </list> </property> </bean> <!-- 定义跳转的文件的前后缀 ,视图模式配置 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp"/> </bean> <!-- 文件上传配置 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 默认编码 --> <property name="defaultEncoding" value="UTF-8"/> <!-- 上传文件大小限制为31M,31*1024*1024 --> <property name="maxUploadSize" value="32505856"/> <!-- 内存中的最大值 --> <property name="maxInMemorySize" value="4096"/> </bean> <!-- 处理请求时返回json字符串的中文乱码问题 --> <mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>application/json;charset=UTF-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> </beans> 5、运行情况 运行提供者, 再看Dubbo Admin中提供者界面,已经注册成功 运得消费者后再看Dubbo Admin中消费者界面,已注册成功 打包提供者和消费者后发布到 129服务器,后运行 在Dubbo Admin 中看到提供者和消费者都是两个 6、负载均衡 增加负载,设置为轮询 把本地代码修改返回加上 本地 两个字后,我们刷新消费者可以看到,有这两个字和没有信息交替出现,证明我们的负载成功了 7、Github 相关代码放到了Github中 地址:https://github.com/kuaibao/kuaibao-dubbo 只是最简单的实现,如有好的建议请帮忙改进,谢谢!
需求 现在有些后辍的域名不支持备案,这个时候需要用免备案主机或空间做个反向代理,这样可实现内容存放在国内主机统一管理 实现 用 php-dynamic-mirror 可实现,并在头部进行域名转换,可实现多个域名反向代理到不同的站点,一共两个文件简单方便,做个收藏 index.php <?php $domain = $_SERVER['SERVER_NAME']; $mirror = "www.mz.cn"; switch ($domain) { case "www.a.cn": $mirror = "www.az.cn"; break; case "www.b.com.cn": $mirror = "www.bz.com"; break; default: $mirror = "www.mz.cn"; } $req = $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'] . " HTTP/1.0\r\n"; $length = 0; foreach ($_SERVER as $k => $v) { if (substr($k, 0, 5) == "HTTP_") { $k = str_replace('_', ' ', substr($k, 5)); $k = str_replace(' ', '-', ucwords(strtolower($k))); if ($k == "Host") $v = $mirror; # Alter "Host" header to mirrored server if ($k == "Accept-Encoding") $v = "identity;q=1.0, *;q=0"; # Alter "Accept-Encoding" header to accept unencoded content only if ($k == "Keep-Alive") continue; # Drop "Keep-Alive" header if ($k == "Connection" && $v == "keep-alive") $v = "close"; # Alter value of "Connection" header from "keep-alive" to "close" $req .= $k . ": " . $v . "\r\n"; } } $body = @file_get_contents('php://input'); $req .= "Content-Type: " . $_SERVER['CONTENT_TYPE'] . "\r\n"; $req .= "Content-Length: " . strlen($body) . "\r\n"; $req .= "\r\n"; $req .= $body; #print $req; $fp = fsockopen($mirror, 80, $errno, $errmsg, 30); if (!$fp) { print "HTTP/1.0 502 Failed to connect remote server\r\n"; print "Content-Type: text/html\r\n\r\n"; print "<html><body>Failed to connect to $mirror due to:<br>[$errno] $errstr</body></html>"; exit; } fwrite($fp, $req); $headers_processed = 0; $reponse = ''; while (!feof($fp)) { $r = fread($fp, 8192); if (!$headers_processed) { $response .= $r; $nlnl = strpos($response, "\r\n\r\n"); $add = 4; if (!$nlnl) { $nlnl = strpos($response, "\n\n"); $add = 2; } if (!$nlnl) continue; $headers = substr($response, 0, $nlnl); $cookies = 'Set-Cookie: '; if (preg_match_all('/^(.*?)(\r?\n|$)/ims', $headers, $matches)) for ($i = 0; $i < count($matches[0]); ++$i) { $ct = $matches[1][$i]; # if (substr($ct, 0, 12) == "Set-Cookie: ") { # $cookies .= substr($ct, 12) . ','; # header($cookies); # } else header($ct, false); # print '>>' . $ct . "\r\n"; } print substr($response, $nlnl + $add); $headers_processed = 1; } else print $r; } fclose ($fp); ?> .htaccess <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] </IfModule> 文件 文件下载
微信小程序官方文档 https://mp.weixin.qq.com/debug/wxadoc/dev/api/location.html JS代码 //index.js //获取应用实例 var app = getApp() Page({ data: { motto: '示例小程序-获取当前地理位、速度', userInfo: {}, hasLocation:false, location:{} }, //事件处理函数 bindViewTap: function() { wx.navigateTo({ url: '../logs/logs' }) }, onLoad: function () { console.log('onLoad') var that = this //调用应用实例的方法获取全局数据 app.getUserInfo(function(userInfo){ //更新数据 that.setData({ userInfo:userInfo }) }), wx.getLocation( { success: function( res ) { console.log( res ) that.setData( { hasLocation: true, location: { longitude: res.longitude, latitude: res.latitude } }) } }) } }) index.wxml <!--index.wxml--> <view class="container"> <view bindtap="bindViewTap" class="userinfo"> <image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" background-size="cover"></image> <text class="userinfo-nickname">{{userInfo.nickName}}</text> </view> <view class="usermotto"> <!-- <text class="user-motto">{{motto}}\n</text>--> <text>经度:{{location.longitude}}\n</text> <text>纬度:{{location.latitude}}</text> </view> </view> 运行效果
在之前的版本中我们可用构造函数实现,其实现在的版本也一样,之前来构造连接字符串,现在相似,构造DbContextOptions<T> 代码如下: public SContext(MasterSlave masterSlave) : base(GetOptions(masterSlave)) { // TODO: #639 //ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; } /// <summary> /// 主从关系 /// </summary> /// <param name="writeRead"></param> /// <returns></returns> private static DbContextOptions<SContext> GetOptions(MasterSlave masterSlave) { var optionsBuilder = new DbContextOptionsBuilder<SContext>(); if (masterSlave == MasterSlave.Slave) { optionsBuilder.UseSqlServer(Config.DbConfig.Read); } else { optionsBuilder.UseSqlServer(Config.DbConfig.Write); } return optionsBuilder.Options; }
阅读目录 环境说明 准备你的ASP.NET Core应用程序 安装CentOS7 安装.NET Core SDK for CentOS7。 部署ASP.NET Core应用程序 配置Nginx 配置守护服务(Supervisor) 这段时间在使用Rabbit RPC重构公司的一套系统(微信相关),而最近相关检验(逻辑测试、压力测试)已经完成,接近部署至线上生产环境从而捣鼓了ASP.NET Core应用程序在CentOS上的部署方案,今天就跟大家分享一下如何将ASP.NET Core应用程序以生产的标准部署在CentOS上。 回到目录 环境说明 服务器系统:CentOS 7.2.1511 相关工具:Xshel、Xftp 服务器软件软件:.netcore、nginx、supervisor、policycoreutils-python 回到目录 准备你的ASP.NET Core应用程序 首先将你的应用程序以便携的模式进行发布。 ps:这边我使用一个空的Web项目来进行演示,因为本篇主要介绍生产环境的部署,与应用无关。 命令为:dotnet publish –c release 具体的可以看:拥抱.NET Core,如何开发跨平台的应用并部署至Ubuntu运行,这篇博文介绍了以便携与自宿主方式发布web应用。 确保这份发布应用可以在windows上运行,以减少后续的问题。 为什么不用自宿主的方式进行部署? 自宿主的发布方式进行部署会简单很多,为什么生产环境要使用便携的方式进行发布呢? 原因1:性能比便携式的低(主)。 原因2:微软给出的建议(次)。 口说无凭,有图有真相。 参考地址:https://docs.microsoft.com/zh-cn/dotnet/articles/core/app-types so,既然是用于生产环境的,当然我们要追求更高的性能。 回到目录 安装CentOS7 这个就不细说了,网上教程很多,这边我使用了Hyper-V来虚拟化了CentOS7。 回到目录 安装.NET Core SDK for CentOS7。 sudo yum install libunwind libicu(安装libicu依赖) curl -sSL -o dotnet.tar.gz https://go.microsoft.com/fwlink/?LinkID=809131(下载sdk压缩包) sudo mkdir -p /opt/dotnet && sudo tar zxf dotnet.tar.gz -C /opt/dotnet(解压缩) sudo ln -s /opt/dotnet/dotnet /usr/local/bin(创建链接) 输入 dotnet –info 来查看是否安装成功 如果可以执行则表明.NET Core SDK安装成功。 参考资料:https://www.microsoft.com/net/core#centos 回到目录 部署ASP.NET Core应用程序 上传之前发布的文件夹至/home/wwwroot/。 这边我使用了Xftp进行文件的上传。 检查是否能够运行 命令:dotnet /home/wwwroot/WebApplication1/WebApplication1.dll 如果出现这些信息则表示成功运行。 这时候我们是无法访问到这个页面的,这时候我们需要部署一个web容器来进行转发。 回到目录 配置Nginx 安装Nginx curl -o nginx.rpm http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm rpm -ivh nginx.rpm yum install nginx 安装成功! 输入:systemctl start nginx 来启动nginx。 输入:systemctl enable nginx 来设置nginx的开机启动(linux宕机、重启会自动运行nginx不需要连上去输入命令)。 配置防火墙 命令:firewall-cmd --zone=public --add-port=80/tcp –permanent(开放80端口) 命令:systemctl restart firewalld(重启防火墙以使配置即时生效) 测试nginx是否可以访问。 配置nginx对ASP.NET Core应用的转发 修改 /etc/nginx/conf.d/default.conf 文件。 将文件内容替换为 server { listen 80; location / { proxy_pass http://localhost:5000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; }} 上传至CentOS进行覆盖。 执行:nginx –s reload 使其即时生效 运行ASP.NET Core应用程序 命令:dotnet /home/wwwroot/WebApplication1/WebApplication1.dll 这时候再次尝试访问。 想哭的心都有。。。经过后续了解,这个问题是由于SELinux保护机制所导致,我们需要将nginx添加至SELinux的白名单。 接下来我们通过一些命令解决这个问题。。 yum install policycoreutils-python sudo cat /var/log/audit/audit.log | grep nginx | grep denied | audit2allow -M mynginx sudo semodule -i mynginx.pp 再次尝试访问。 至此基本完成了部署。 回到目录 配置守护服务(Supervisor) 目前存在三个问题 问题1:ASP.NET Core应用程序运行在shell之中,如果关闭shell则会发现ASP.NET Core应用被关闭,从而导致应用无法访问,这种情况当然是我们不想遇到的,而且生产环境对这种情况是零容忍的。 问题2:如果ASP.NET Core进程意外终止那么需要人为连进shell进行再次启动,往往这种操作都不够及时。 问题3:如果服务器宕机或需要重启我们则还是需要连入shell进行启动。 为了解决这个问题,我们需要有一个程序来监听ASP.NET Core 应用程序的状况。在应用程序停止运行的时候立即重新启动。这边我们用到了Supervisor这个工具,Supervisor使用Python开发的。 安装Supervisor yum install python-setuptools easy_install supervisor 配置Supervisor mkdir /etc/supervisor echo_supervisord_conf > /etc/supervisor/supervisord.conf 修改supervisord.conf文件,将文件尾部的配置 修改为 ps:如果服务已启动,修改配置文件可用“supervisorctl reload”命令来使其生效 配置对ASP.NET Core应用的守护 创建一个 WebApplication1.conf文件,内容大致如下 [program:WebApplication1]command=dotnet WebApplication1.dll ; 运行程序的命令directory=/home/wwwroot/WebApplication1/ ; 命令执行的目录autorestart=true ; 程序意外退出是否自动重启stderr_logfile=/var/log/WebApplication1.err.log ; 错误日志文件stdout_logfile=/var/log/WebApplication1.out.log ; 输出日志文件environment=ASPNETCORE_ENVIRONMENT=Production ; 进程环境变量user=root ; 进程执行的用户身份stopsignal=INT 将文件拷贝至:“/etc/supervisor/conf.d/WebApplication1.conf”下 运行supervisord,查看是否生效 supervisord -c /etc/supervisor/supervisord.conf ps -ef | grep WebApplication1 如果存在dotnet WebApplication1.dll 进程则代表运行成功,这时候在使用浏览器进行访问。 至此关于ASP.NET Core应用程序的守护即配置完成。 配置Supervisor开机启动 新建一个“supervisord.service”文件 # dservice for systemd (CentOS 7.0+)# by ET-CS (https://github.com/ET-CS)[Unit]Description=Supervisor daemon [Service]Type=forkingExecStart=/usr/bin/supervisord -c /etc/supervisor/supervisord.confExecStop=/usr/bin/supervisorctl shutdownExecReload=/usr/bin/supervisorctl reloadKillMode=processRestart=on-failureRestartSec=42s [Install]WantedBy=multi-user.target 将文件拷贝至:“/usr/lib/systemd/system/supervisord.service” 执行命令:systemctl enable supervisord 执行命令:systemctl is-enabled supervisord #来验证是否为开机启动 测试 转自:http://www.cnblogs.com/ants/p/5732337.html
开启80端口 1、firewall-cmd --zone=public --add-port=80/tcp --permanent 出现success表明添加成功 命令含义: --zone #作用域 --add-port=80/tcp #添加端口,格式为:端口/通讯协议 --permanent #永久生效,没有此参数重启后失效 重启防火墙 1、systemctl restart firewalld.service 1、运行、停止、禁用firewalld 启动:# systemctl start firewalld 查看状态:# systemctl status firewalld 或者 firewall-cmd --state 停止:# systemctl disable firewalld 禁用:# systemctl stop firewalld 2、配置firewalld 查看版本:$ firewall-cmd --version 查看帮助:$ firewall-cmd --help 查看设置: 显示状态:$ firewall-cmd --state 查看区域信息: $ firewall-cmd --get-active-zones 查看指定接口所属区域:$ firewall-cmd --get-zone-of-interface=eth0 拒绝所有包:# firewall-cmd --panic-on 取消拒绝状态:# firewall-cmd --panic-off 查看是否拒绝:$ firewall-cmd --query-panic 更新防火墙规则:# firewall-cmd --reload # firewall-cmd --complete-reload 两者的区别就是第一个无需断开连接,就是firewalld特性之一动态添加规则,第二个需要断开连接,类似重启服务 将接口添加到区域,默认接口都在public # firewall-cmd --zone=public --add-interface=eth0 永久生效再加上 --permanent 然后reload防火墙 设置默认接口区域 # firewall-cmd --set-default-zone=public 立即生效无需重启 打开端口(貌似这个才最常用) 查看所有打开的端口: # firewall-cmd --zone=dmz --list-ports 加入一个端口到区域: # firewall-cmd --zone=dmz --add-port=8080/tcp 若要永久生效方法同上 打开一个服务,类似于将端口可视化,服务需要在配置文件中添加,/etc/firewalld 目录下有services文件夹,这个不详细说了,详情参考文档 # firewall-cmd --zone=work --add-service=smtp 移除服务 # firewall-cmd --zone=work --remove-service=smtp
前言 Asp.net Core 改变了之前的封闭,现在开源且开放,下面我们来用Redis存储Session来做一个简单的测试,或者叫做中间件(middleware)。 对于Session来说褒贬不一,很多人直接说不要用,也有很多人在用,这个也没有绝对的这义,个人认为只要不影什么且又可以方便实现的东西是可以用的,现在不对可不可用做表态,我们只关心实现。 类库引用 这个相对于之前的.net是方便了不少,需要在project.json中的dependencies节点中添加如下内容: "StackExchange.Redis": "1.1.604-alpha", "Microsoft.AspNetCore.Session": "1.1.0-alpha1-21694" Redis实现 这里并非我实现,而是借用https://github.com/aspnet/Caching/tree/dev/src/Microsoft.Extensions.Caching.Redis代码来实现,不知道为什么之前还有这个类库,而现在NUGET止没有了,为了不影响日后升级我的命名空间也用 Microsoft.Extensions.Caching.Redis 可以看到微软这里有四个类,其实我们只需要三个,第四个拿过来反而会出错: using System; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; using StackExchange.Redis; namespace Microsoft.Extensions.Caching.Redis { public class RedisCache : IDistributedCache, IDisposable { // KEYS[1] = = key // ARGV[1] = absolute-expiration - ticks as long (-1 for none) // ARGV[2] = sliding-expiration - ticks as long (-1 for none) // ARGV[3] = relative-expiration (long, in seconds, -1 for none) - Min(absolute-expiration - Now, sliding-expiration) // ARGV[4] = data - byte[] // this order should not change LUA script depends on it private const string SetScript = (@" redis.call('HMSET', KEYS[1], 'absexp', ARGV[1], 'sldexp', ARGV[2], 'data', ARGV[4]) if ARGV[3] ~= '-1' then redis.call('EXPIRE', KEYS[1], ARGV[3]) end return 1"); private const string AbsoluteExpirationKey = "absexp"; private const string SlidingExpirationKey = "sldexp"; private const string DataKey = "data"; private const long NotPresent = -1; private ConnectionMultiplexer _connection; private IDatabase _cache; private readonly RedisCacheOptions _options; private readonly string _instance; public RedisCache(IOptions<RedisCacheOptions> optionsAccessor) { if (optionsAccessor == null) { throw new ArgumentNullException(nameof(optionsAccessor)); } _options = optionsAccessor.Value; // This allows partitioning a single backend cache for use with multiple apps/services. _instance = _options.InstanceName ?? string.Empty; } public byte[] Get(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } return GetAndRefresh(key, getData: true); } public async Task<byte[]> GetAsync(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } return await GetAndRefreshAsync(key, getData: true); } public void Set(string key, byte[] value, DistributedCacheEntryOptions options) { if (key == null) { throw new ArgumentNullException(nameof(key)); } if (value == null) { throw new ArgumentNullException(nameof(value)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } Connect(); var creationTime = DateTimeOffset.UtcNow; var absoluteExpiration = GetAbsoluteExpiration(creationTime, options); var result = _cache.ScriptEvaluate(SetScript, new RedisKey[] { _instance + key }, new RedisValue[] { absoluteExpiration?.Ticks ?? NotPresent, options.SlidingExpiration?.Ticks ?? NotPresent, GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent, value }); } public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options) { if (key == null) { throw new ArgumentNullException(nameof(key)); } if (value == null) { throw new ArgumentNullException(nameof(value)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } await ConnectAsync(); var creationTime = DateTimeOffset.UtcNow; var absoluteExpiration = GetAbsoluteExpiration(creationTime, options); await _cache.ScriptEvaluateAsync(SetScript, new RedisKey[] { _instance + key }, new RedisValue[] { absoluteExpiration?.Ticks ?? NotPresent, options.SlidingExpiration?.Ticks ?? NotPresent, GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent, value }); } public void Refresh(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } GetAndRefresh(key, getData: false); } public async Task RefreshAsync(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } await GetAndRefreshAsync(key, getData: false); } private void Connect() { if (_connection == null) { _connection = ConnectionMultiplexer.Connect(_options.Configuration); _cache = _connection.GetDatabase(); } } private async Task ConnectAsync() { if (_connection == null) { _connection = await ConnectionMultiplexer.ConnectAsync(_options.Configuration); _cache = _connection.GetDatabase(); } } private byte[] GetAndRefresh(string key, bool getData) { if (key == null) { throw new ArgumentNullException(nameof(key)); } Connect(); // This also resets the LRU status as desired. // TODO: Can this be done in one operation on the server side? Probably, the trick would just be the DateTimeOffset math. RedisValue[] results; if (getData) { results = _cache.HashMemberGet(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey, DataKey); } else { results = _cache.HashMemberGet(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey); } // TODO: Error handling if (results.Length >= 2) { // Note we always get back two results, even if they are all null. // These operations will no-op in the null scenario. DateTimeOffset? absExpr; TimeSpan? sldExpr; MapMetadata(results, out absExpr, out sldExpr); Refresh(key, absExpr, sldExpr); } if (results.Length >= 3 && results[2].HasValue) { return results[2]; } return null; } private async Task<byte[]> GetAndRefreshAsync(string key, bool getData) { if (key == null) { throw new ArgumentNullException(nameof(key)); } await ConnectAsync(); // This also resets the LRU status as desired. // TODO: Can this be done in one operation on the server side? Probably, the trick would just be the DateTimeOffset math. RedisValue[] results; if (getData) { results = await _cache.HashMemberGetAsync(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey, DataKey); } else { results = await _cache.HashMemberGetAsync(_instance + key, AbsoluteExpirationKey, SlidingExpirationKey); } // TODO: Error handling if (results.Length >= 2) { // Note we always get back two results, even if they are all null. // These operations will no-op in the null scenario. DateTimeOffset? absExpr; TimeSpan? sldExpr; MapMetadata(results, out absExpr, out sldExpr); await RefreshAsync(key, absExpr, sldExpr); } if (results.Length >= 3 && results[2].HasValue) { return results[2]; } return null; } public void Remove(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } Connect(); _cache.KeyDelete(_instance + key); // TODO: Error handling } public async Task RemoveAsync(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } await ConnectAsync(); await _cache.KeyDeleteAsync(_instance + key); // TODO: Error handling } private void MapMetadata(RedisValue[] results, out DateTimeOffset? absoluteExpiration, out TimeSpan? slidingExpiration) { absoluteExpiration = null; slidingExpiration = null; var absoluteExpirationTicks = (long?)results[0]; if (absoluteExpirationTicks.HasValue && absoluteExpirationTicks.Value != NotPresent) { absoluteExpiration = new DateTimeOffset(absoluteExpirationTicks.Value, TimeSpan.Zero); } var slidingExpirationTicks = (long?)results[1]; if (slidingExpirationTicks.HasValue && slidingExpirationTicks.Value != NotPresent) { slidingExpiration = new TimeSpan(slidingExpirationTicks.Value); } } private void Refresh(string key, DateTimeOffset? absExpr, TimeSpan? sldExpr) { if (key == null) { throw new ArgumentNullException(nameof(key)); } // Note Refresh has no effect if there is just an absolute expiration (or neither). TimeSpan? expr = null; if (sldExpr.HasValue) { if (absExpr.HasValue) { var relExpr = absExpr.Value - DateTimeOffset.Now; expr = relExpr <= sldExpr.Value ? relExpr : sldExpr; } else { expr = sldExpr; } _cache.KeyExpire(_instance + key, expr); // TODO: Error handling } } private async Task RefreshAsync(string key, DateTimeOffset? absExpr, TimeSpan? sldExpr) { if (key == null) { throw new ArgumentNullException(nameof(key)); } // Note Refresh has no effect if there is just an absolute expiration (or neither). TimeSpan? expr = null; if (sldExpr.HasValue) { if (absExpr.HasValue) { var relExpr = absExpr.Value - DateTimeOffset.Now; expr = relExpr <= sldExpr.Value ? relExpr : sldExpr; } else { expr = sldExpr; } await _cache.KeyExpireAsync(_instance + key, expr); // TODO: Error handling } } private static long? GetExpirationInSeconds(DateTimeOffset creationTime, DateTimeOffset? absoluteExpiration, DistributedCacheEntryOptions options) { if (absoluteExpiration.HasValue && options.SlidingExpiration.HasValue) { return (long)Math.Min( (absoluteExpiration.Value - creationTime).TotalSeconds, options.SlidingExpiration.Value.TotalSeconds); } else if (absoluteExpiration.HasValue) { return (long)(absoluteExpiration.Value - creationTime).TotalSeconds; } else if (options.SlidingExpiration.HasValue) { return (long)options.SlidingExpiration.Value.TotalSeconds; } return null; } private static DateTimeOffset? GetAbsoluteExpiration(DateTimeOffset creationTime, DistributedCacheEntryOptions options) { if (options.AbsoluteExpiration.HasValue && options.AbsoluteExpiration <= creationTime) { throw new ArgumentOutOfRangeException( nameof(DistributedCacheEntryOptions.AbsoluteExpiration), options.AbsoluteExpiration.Value, "The absolute expiration value must be in the future."); } var absoluteExpiration = options.AbsoluteExpiration; if (options.AbsoluteExpirationRelativeToNow.HasValue) { absoluteExpiration = creationTime + options.AbsoluteExpirationRelativeToNow; } return absoluteExpiration; } public void Dispose() { if (_connection != null) { _connection.Close(); } } } } using Microsoft.Extensions.Options; namespace Microsoft.Extensions.Caching.Redis { /// <summary> /// Configuration options for <see cref="RedisCache"/>. /// </summary> public class RedisCacheOptions : IOptions<RedisCacheOptions> { /// <summary> /// The configuration used to connect to Redis. /// </summary> public string Configuration { get; set; } /// <summary> /// The Redis instance name. /// </summary> public string InstanceName { get; set; } RedisCacheOptions IOptions<RedisCacheOptions>.Value { get { return this; } } } } using System.Threading.Tasks; using StackExchange.Redis; namespace Microsoft.Extensions.Caching.Redis { internal static class RedisExtensions { private const string HmGetScript = (@"return redis.call('HMGET', KEYS[1], unpack(ARGV))"); internal static RedisValue[] HashMemberGet(this IDatabase cache, string key, params string[] members) { var result = cache.ScriptEvaluate( HmGetScript, new RedisKey[] { key }, GetRedisMembers(members)); // TODO: Error checking? return (RedisValue[])result; } internal static async Task<RedisValue[]> HashMemberGetAsync( this IDatabase cache, string key, params string[] members) { var result = await cache.ScriptEvaluateAsync( HmGetScript, new RedisKey[] { key }, GetRedisMembers(members)); // TODO: Error checking? return (RedisValue[])result; } private static RedisValue[] GetRedisMembers(params string[] members) { var redisMembers = new RedisValue[members.Length]; for (int i = 0; i < members.Length; i++) { redisMembers[i] = (RedisValue)members[i]; } return redisMembers; } } } 配置启用Session 我们在Startup中ConfigureServices增加 services.AddSingleton<IDistributedCache>( serviceProvider => new RedisCache(new RedisCacheOptions { Configuration = "192.168.178.141:6379", InstanceName = "Sample:" })); services.AddSession(); 在Startup中Configure增加 app.UseSession(new SessionOptions() { IdleTimeout = TimeSpan.FromMinutes(30) }); 到此我们的配置完毕,可以测试一下是否写到了Redis中 验证结果 在Mvc项目中,我们来实现如下代码 if (string.IsNullOrEmpty(HttpContext.Session.GetString("D"))) { var d = DateTime.Now.ToString(); HttpContext.Session.SetString("D", d); HttpContext.Response.ContentType = "text/plain"; await HttpContext.Response.WriteAsync("Hello First timer///" + d); } else { HttpContext.Response.ContentType = "text/plain"; await HttpContext.Response.WriteAsync("Hello old timer///" + HttpContext.Session.GetString("D")); } 运行我们发现第一次出现了Hello First timer字样,刷新后出现了Hello old timer字样,证明Session成功,再查看一下Redis看一下,有值了,这样一个分布式的Session就成功实现了。 对于上面的实例我把源码放在了:https://github.com/hantianwei/Microsoft.Extensions.Caching.Redis 且也在Nuget上上传了一份,方便直接使用,Tianwei.Microsoft.Extensions.Caching.Redis ,只是ID加了Tianwei 空间名还是Microsoft.Extensions.Caching.Redis 从上面的实例我们发现微软这次是真的开放了,这也意味着如果我们使用某些类不顺手或不合适时可以自已写自已扩展
Startup中: public IContainer ApplicationContainer { get; private set; } // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { services.Configure<Data>(Configuration.GetSection("Data")); services.AddDbContext<ShujuContext>(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])); // Add framework services. services.AddMvc(); var builder = new ContainerBuilder(); builder.RegisterModule(new AutofacModule()); builder.Populate(services); this.ApplicationContainer = builder.Build(); return new AutofacServiceProvider(this.ApplicationContainer); } AutoFacModule类 public class AutofacModule : Module { protected override void Load(ContainerBuilder builder) { builder.RegisterType<DiTest>().As<IDiTest>(); } } 使用: private IDiTest diTest { get; } public HomeController(IDiTest _diTest) { diTest = _diTest; }
前言 .net core 出来有一时间了,这段时间也一直在做技术准备,目前想做一个单点登录(SSO)系统,在这之前用.net时我用习惯了machineKey ,也顺手在.net core 中尝试了一上,结果发现不好使了,也不起作用,于是开始了网上学习。 实现方法 功夫不负有心人,网上高人还是多,在github.com上面ISSUES中也有人在讨论此问题,于是找到代码尝试,结果实现了。 直接上代码,我们需要先封装一个XmlRepository,Key的格式如下: <?xml version="1.0" encoding="utf-8"?> <key id="cbb8a41a-9ca4-4a79-a1de-d39c4e307d75" version="1"> <creationDate>2016-07-23T10:09:49.1888876Z</creationDate> <activationDate>2016-07-23T10:09:49.1388521Z</activationDate> <expirationDate>2116-10-21T10:09:49.1388521Z</expirationDate> <descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60"> <descriptor> <encryption algorithm="AES_256_CBC" /> <validation algorithm="HMACSHA256" /> <masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection"> <!-- Warning: the key below is in an unencrypted form. --> <value>WYgZNh/3dOKRYJ1OAhVqs56pWPMHei15Uj44DPLWbYUiCpNVEBwqDfYAUq/4jBKYrNoUbaRkGY5o/NZ6a2NTwA==</value> </masterKey> </descriptor> </descriptor> </key> XmlRepository代码: public class CustomFileXmlRepository : IXmlRepository { private readonly string filePath = @"C:\keys\key.xml"; public virtual IReadOnlyCollection<XElement> GetAllElements() { return GetAllElementsCore().ToList().AsReadOnly(); } private IEnumerable<XElement> GetAllElementsCore() { yield return XElement.Load(filePath); } public virtual void StoreElement(XElement element, string friendlyName) { if (element == null) { throw new ArgumentNullException(nameof(element)); } StoreElementCore(element, friendlyName); } private void StoreElementCore(XElement element, string filename) { } } Startup代码: public class Startup { 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(); Configuration = builder.Build(); } 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.AddSingleton<IXmlRepository, CustomFileXmlRepository>(); services.AddDataProtection(configure => { configure.ApplicationDiscriminator = "Htw.Web"; }); // Add framework services. 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) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseCookieAuthentication(new CookieAuthenticationOptions() { AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme, LoginPath = new PathString("/Account/Unauthorized/"), AccessDeniedPath = new PathString("/Account/Forbidden/"), AutomaticAuthenticate = true, AutomaticChallenge = false, CookieHttpOnly = true, CookieName = "MyCookie", ExpireTimeSpan = TimeSpan.FromHours(2), #if !DEBUG CookieDomain="h.cn", #endif DataProtectionProvider = null }); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } } 登录代码: public async void Login() { if (!HttpContext.User.Identities.Any(identity => identity.IsAuthenticated)) { var user = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "bob") }, CookieAuthenticationDefaults.AuthenticationScheme)); await HttpContext.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, user); HttpContext.Response.ContentType = "text/plain"; await HttpContext.Response.WriteAsync("Hello First timer"); } else { HttpContext.Response.ContentType = "text/plain"; await HttpContext.Response.WriteAsync("Hello old timer"); } } 注意 C:\keys\key.xml 这个文件路径可以更改,还有就是也可用共享目录或数据库来实现统一管理 到此可以登录试一下。
在上篇中用MailKit实现了Asp.net core 邮件发送功能,但一直未解决阿里云邮件推送问题,提交工单一开始的回复不尽如人意,比如您的网络问题,您的用户名密码不正确等,但继续沟通下阿里云客户还是很耐心的。 最终结论,是由于MailKit发送了两次EHLO命令,查看了MailKit源码后竟然发现,里面写了硬编码: if (host != "smtp.strato.de" && host != "smtp.sina.com") Ehlo (cancellationToken); authenticated = true; OnAuthenticated (response.Response); 哈哈,只要把阿里云SMTP地址加上就好:smtpdm.aliyun.com 还好dudu提交了Github已经加进去,等作者发布就好,这位作者还是微软件的员工。 到此阿里云邮件推送完美解决,感谢园长dudu、感谢阿里云、感谢MailKit作者
阿里大鱼还未提供 .net core 版SDK,但提供了相关API,下面是.net core版实现,只是简单发送短信功能: using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Security.Cryptography; using System.Text; using Newtonsoft.Json; namespace ConsoleApp1 { public class SmsHelper { public static string Post(string url, string data, Encoding encoding) { try { HttpWebRequest req = WebRequest.CreateHttp(new Uri(url)); req.ContentType = "application/x-www-form-urlencoded;charset=utf-8"; req.Method = "POST"; req.Accept = "text/xml,text/javascript"; req.ContinueTimeout = 60000; byte[] postData = encoding.GetBytes(data); Stream reqStream = req.GetRequestStreamAsync().Result; reqStream.Write(postData, 0, postData.Length); reqStream.Dispose(); var rsp = (HttpWebResponse)req.GetResponseAsync().Result; var result = GetResponseAsString(rsp, encoding); return result; } catch (Exception ex) { throw; } } public static T Post<T>(string url, string data, Encoding encoding) { try { var result = Post(url, data, encoding); return JsonConvert.DeserializeObject<T>(result); } catch (Exception ex) { return default(T); } } public static string BuildQuery(IDictionary<string, string> parameters) { if (parameters == null || parameters.Count == 0) { return null; } StringBuilder query = new StringBuilder(); bool hasParam = false; foreach (KeyValuePair<string, string> kv in parameters) { string name = kv.Key; string value = kv.Value; // 忽略参数名或参数值为空的参数 if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(value)) { if (hasParam) { query.Append("&"); } query.Append(name); query.Append("="); query.Append(WebUtility.UrlEncode(value)); hasParam = true; } } return query.ToString(); } public static string GetResponseAsString(HttpWebResponse rsp, Encoding encoding) { Stream stream = null; StreamReader reader = null; try { // 以字符流的方式读取HTTP响应 stream = rsp.GetResponseStream(); reader = new StreamReader(stream, encoding); return reader.ReadToEnd(); } finally { // 释放资源 if (reader != null) reader.Dispose(); if (stream != null) stream.Dispose(); if (rsp != null) rsp.Dispose(); } } public static string GetAlidayuSign(IDictionary<string, string> parameters, string secret, string signMethod) { //把字典按Key的字母顺序排序 IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters, StringComparer.Ordinal); //把所有参数名和参数值串在一起 StringBuilder query = new StringBuilder(); if (Constants.SIGN_METHOD_MD5.Equals(signMethod)) { query.Append(secret); } foreach (KeyValuePair<string, string> kv in sortedParams) { if (!string.IsNullOrEmpty(kv.Key) && !string.IsNullOrEmpty(kv.Value)) { query.Append(kv.Key).Append(kv.Value); } } //使用MD5/HMAC加密 if (Constants.SIGN_METHOD_HMAC.Equals(signMethod)) { return Hmac(query.ToString(), secret); } else { query.Append(secret); return Md5(query.ToString()); } } public static string Hmac(string value, string key) { byte[] bytes; using (var hmac = new HMACMD5(Encoding.UTF8.GetBytes(key))) { bytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(value)); } StringBuilder result = new StringBuilder(); foreach (byte t in bytes) { result.Append(t.ToString("X2")); } return result.ToString(); } public static string Md5(string value) { byte[] bytes; using (var md5 = MD5.Create()) { bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(value)); } var result = new StringBuilder(); foreach (byte t in bytes) { result.Append(t.ToString("X2")); } return result.ToString(); } public static SmsResultAli SendSms(string url, string appKey, string appSecret, DateTime timestamp, Dictionary<string, string> parsms) { var txtParams = new SortedDictionary<string, string>(); txtParams.Add(Constants.METHOD, "alibaba.aliqin.fc.sms.num.send"); txtParams.Add(Constants.VERSION, "2.0"); txtParams.Add(Constants.SIGN_METHOD, Constants.SIGN_METHOD_HMAC); txtParams.Add(Constants.APP_KEY, appKey); txtParams.Add(Constants.FORMAT, "json"); txtParams.Add(Constants.TIMESTAMP, timestamp.ToString(Constants.DATE_TIME_FORMAT)); txtParams.Add(Constants.SMS_TYPE, "normal"); foreach (var item in parsms) { txtParams.Add(item.Key,item.Value); } txtParams.Add(Constants.SIGN, GetAlidayuSign(txtParams, appSecret, Constants.SIGN_METHOD_HMAC)); var result = Post<SmsResultAli>(url, BuildQuery(txtParams), Encoding.UTF8); return result; } } public class SmsResultAli { public SmsResponseALi Alibaba_Aliqin_Fc_Sms_Num_Send_Response { get; set; } } public class SmsResponseALi { public string Request_Id { get; set; } public SmsResponseResultAli Result { get; set; } } public class SmsResponseResultAli { public string Err_Code { get; set; } public string Model { get; set; } public bool Success { get; set; } } } 发送短信时: var parms = new Dictionary<string, string>(); parms.Add(Constants.EXTEND, "123456"); parms.Add(Constants.REC_NUM, "138********"); parms.Add(Constants.SMS_FREE_SIGN_NAME, "阿里大鱼"); parms.Add(Constants.SMS_PARAM, "{\"code\":\"1234\",\"product\":\"阿里大鱼\"}"); parms.Add(Constants.SMS_TEMPLATE_CODE, "SMS_10000000"); var req = SmsHelper.SendSms("http://gw.api.taobao.com/router/rest", "appKey", "appSecret", DateTime.Now, parms);
准备将一些项目迁移到 asp.net core 先从封装类库入手,在遇到邮件发送类时发现在 asp.net core 1.0中并示提供SMTP相关类库,于是网上一搜发现了MailKit 好东西一定要试一下,何况是开源,下面是代码可实现SMTP邮件发送: using MailKit.Net.Smtp; using MailKit.Security; using MimeKit; using System.Threading.Tasks; namespace ConsoleApp1 { public class MailHelper { public static void Send(string email, string subject, string message) { var emailMessage = new MimeMessage(); emailMessage.From.Add(new MailboxAddress("tianwei blogs", "mail@hantianwei.cn")); emailMessage.To.Add(new MailboxAddress("mail", email)); emailMessage.Subject = subject; emailMessage.Body = new TextPart("plain") { Text = message }; using (var client = new SmtpClient()) { client.Connect("smtp.hantianwei.cn", 465, true); client.Authenticate("mail@hantianwei.cn", "******"); client.Send(emailMessage); client.Disconnect(true); } } public static async Task SendEmailAsync(string email, string subject, string message) { var emailMessage = new MimeMessage(); emailMessage.From.Add(new MailboxAddress("tianwei blogs", "mail@hantianwei.cn")); emailMessage.To.Add(new MailboxAddress("mail", email)); emailMessage.Subject = subject; emailMessage.Body = new TextPart("plain") { Text = message }; using (var client = new SmtpClient()) { await client.ConnectAsync("smtp.hantianwei.cn", 25, SecureSocketOptions.None).ConfigureAwait(false); await client.AuthenticateAsync("mail@hantianwei.cn", "******"); await client.SendAsync(emailMessage).ConfigureAwait(false); await client.DisconnectAsync(true).ConfigureAwait(false); } } } } 以上代码同步异步都没有问题 注:一般邮箱如腾讯企业邮、163等都可以发送成功,但阿里云邮件推送失败,如果有高手可实现阿里云推送邮件请告诉我一下,非常感谢!
文章前言 之前写了一篇关于模拟登录的文章,自我感觉内容不太丰富,今天的这篇文章,希望在内容上能丰富些。本人缺少写文章的经验,技术上也是新手,但我会努力的,希望大家多多支持小弟。 asp.net core项目静态文件 创建一个asp.net core 项目的静态文件一般是放在项目目录下wwwroot文件夹,文件目录如下。 如何将静态文件注入到项目中 在startup.cs文件的Configure方法中写入: app.UseStaticFiles(); 这方法的默认路径正是上面所说的wwwroot目录。 如何使用自己的文件路径 在startup.cs文件的Configure方法中写入: app.UseStaticFiles(new StaticFileOptions(){ FileProvider = new PhysicalFileProvider( Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")), RequestPath = new PathString("/StaticFiles")}); 这时候我们运行一下然后输入链接看到以下效果 这时候我们知道FileProvider是指定路径,RequestPath是将对外的路径重写。即可用 StaticFiles来访问而不是MyStaticFiles。 我觉得这样地址重写的好处是可以保证项目的结构不被暴露,有一定的安全性吧。 如何浏览目录的文件与文件夹 在出于安全问题默认情况下是不允许浏览目录的文件和文件夹的,但是如果你需要浏览的话可以用以下方法。 首先要在startup.cs文件的ConfigureServices方法中加入: services.AddDirectoryBrowser(); 然后在startup.cs文件的Configure方法中写入: app.UseDirectoryBrowser(new DirectoryBrowserOptions(){ FileProvider = new PhysicalFileProvider( Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")), RequestPath = new PathString("/wwwroot/images")}); 然后可以看到以下效果 这样我们就可以看到该目录下的东西了。一般来说还是使用默认的比较好,毕竟出于安全考虑嘛。 因为本人技术有限,写到这里就结束了。谢谢大家!
提起邮件服务,最早之前一般自建邮件服务器,需要硬软件和运维的支持,稳定性也难保证,随着云服务的普及邮件推送功能的服务化是趋势,也省钱省事省心。 最早开始用过搜狐邮件服务,阿里云生态现在基本都出现了,我们来尝试一下阿里云的邮件推送服务: 一、阿里云控制台操作 首先在阿里云邮件推送控制台添加域名,然后在自己的dns服务器中添加如下的解析(根据控制台中的“配置”): 一个主机名为aliyundm的TXT记录,用于验证域名所有权 一个主机名为空、值为v=spf1 include:spf1.dm.aliyun.com -all的TXT记录,用于防止被当作垃圾邮件 一个MX记录 在mac上可以通过nslookup -query=TXT 域名与nslookup -query=MX 域名命令查看DNS设置是否生效。 接着在阿里云控制台验证一下域名,验证成功之后,添加一下发信地址(发件地址),并设置一下SMTP密码。 然后通过下面的C#代码使用阿里云邮件推送服务的SMTP就可以发送邮件了。 二、在程序中调用 MailMessage mailMsg = new MailMessage(); mailMsg.To.Add(new MailAddress(" 收件人地址")); mailMsg.From = new MailAddress("控制台创建的发信地址", "显示名称"); // 邮件主题 mailMsg.Subject = "测试邮件主题"; // 邮件正文内容 string text = "这是一封通过阿里云邮件服务发送的邮件"; string html = @"欢迎使用<a href=""https://dm.console.aliyun.com"">邮件推送</a>"; mailMsg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(text, null, MediaTypeNames.Text.Plain)); mailMsg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(html, null, MediaTypeNames.Text.Html)); // 添加附件 string file = "D:\\1.bak"; Attachment data = new Attachment(file, MediaTypeNames.Application.Octet); mailMsg.Attachments.Add(data); //邮件推送的SMTP地址和端口 SmtpClient smtpClient = new SmtpClient("smtpdm.aliyun.com", 25); // 使用SMTP用户名和密码进行验证 System.Net.NetworkCredential credentials = new System.Net.NetworkCredential("控制台创建的发信地址", "SMTP密码"); smtpClient.Credentials = credentials; smtpClient.Send(mailMsg); 最终相信阿里云的稳定性,且价格是相当的便宜,还有每天200封的免费量,哈哈!
通过Web.config实现301重定向 IIS7以上可以通过修改Web.config实现IIS设置 现在我们通过Web.config实现301重定向 <system.webServer> <validation validateIntegratedModeConfiguration="false" /> <modules runAllManagedModulesForAllRequests="true" /> <security> <requestFiltering allowDoubleEscaping="true" /> </security> <rewrite> <rules> <rule name="301Redirect" stopProcessing="true"> <match url="(.*)" /> <conditions logicalGrouping="MatchAny"> <add input="{HTTP_HOST}" pattern="^hantianwei\.com$" /> <add input="{HTTP_HOST}" pattern="^www\.hantianwei\.com$" /> </conditions> <action type="Redirect" url="http://www.hantianwei.cn/{R:0}" redirectType="Permanent" /> </rule> </rules> </rewrite> </system.webServer>
本文转自:http://www.cnblogs.com/gossip/p/4898653.html 一、先谈谈单个Redis服务的安装 使用的redis是2.8.17版本,从官网下载解压缩后文件内容为: 安装并启动一个redis服务很简单,步骤如下: 1、配置日志文件目录:只用修改logfile定位到Log文件夹下 2、命令行定位到解压文件的目录 cd /d D:\MasterRedis-2.8.17 (windows 7) cd /d D:\SlaveRedis-2.8.17 (windows server) 3、安装Redis:redis-server --service-install redis.windows.conf --loglevel verbose 4、启动Redis:redis-server --service-start 5、停止Redis:redis-server --service-stop 6、卸载Redis:redis-server --service-uninstall 二、再谈谈结论 经过半天的折腾,结论就是:Success!过程我下面再谈,结论就是在单台windows servers机器上部署主从服务,第二个redis示例始终启动不了(写这篇文章的时候解决了),在两个不同的服务器上部署也可以成功 三、谈谈过程 1、本来想安装部署单个Redis的方式连续操作两遍,才发现第一次操作时已经启动了名称为Redis的服务,第二次操作时服务根本就安装不上去,解决的方法就是给Redis服务命名,命令如下: redis-server --service-install redis6379.windows.conf --loglevel verbose --service-name Redis6379 --service-name是服务名称,坑爹的是我在网上拷贝的命令只有一个横杠(-),半天得不到想要的结果(一个横岗也会创建名称为redis的服务) 2、安装服务的时候加上了servicename,在服务启动、停止、卸载的时候也需要加上servicename参数 1、服务启动:redis-server --service-start --service-name Redis6379 redis6379.windows.conf 2、服务停止:redis-server --service-stop --service-name Redis6379 redis6379.windows.conf 3、服务卸载:redis-server --service-uninstall --service-name Redis6379 redis6379.windows.conf 3、最后在谈谈之前为什么主从服务都可以安装成功,但是启动一个服务后第二个服务始终无法启动的坑 事件查看器的消息如下:(不知所云啊!) 坑就在安装服务的命令: redis-server --service-install --service-name Redis6379 redis6379.windows.conf --loglevel verbose (不OK) redis-server --service-install redis6379.windows.conf --loglevel verbose --service-name Redis6379 (OK) 看清楚了,就在于service-name参数的位置,在配置文件前不行,必须放在最后 五、谈谈配置文件的修改(redis.windows.conf) 1、主Redis服务配置文件不用做修改 可能这个地方需要修改,我没有做测试 2、从Redis服务配置文件修改如下: 1、 端口修改 2、 目前来看这里不用做修改,注释即可 3、 日志文件 4、 指定主Redis服务的IP和端口 五、截个图说明结论(用的工具是RedisDesktopManager) 1、RedisRemoteMaster为远程主机的主Redis 2、RedisRemoteSlaver为远程主机的从Redis (可以看到开始主redis未设置abc时,返回的是null,设置后,返回了新值) 3、RedisLocal为主机的从Redis(同2) 五、调用Redis代码 1、配置主从服务器 <appSettings> <!--Redis写入服务器地址,可以添加多个服务器通过,分隔--> <add key="ReadWriteHosts" value="192.168.1.100:6379" /> <!--Redis读服务器地址,可以添加多个服务器通过,分隔--> <add key="ReadOnlyHosts" value="192.168.1.100:6380,127.0.0.1:6379" /> </appSettings> 2、Redis初始化(RedisConfig.cs) class RedisConfig { public static RedisClient Redis { get { return (RedisClient)reidsPools.GetClient(); } } private static string[] hosts; private static PooledRedisClientManager reidsPools; static RedisConfig() { var readWriteHosts = ConfigurationManager.AppSettings["ReadWriteHosts"].Split(','); var readOnlyHosts = ConfigurationManager.AppSettings["ReadOnlyHosts"].Split(','); reidsPools = new PooledRedisClientManager(readWriteHosts, readOnlyHosts, new RedisClientManagerConfig { MaxWritePoolSize = 100,//“写”链接池链接数 MaxReadPoolSize = 200,//“读”链接池链接数 AutoStart = true, DefaultDb = 0 }); } } 3、Redis操作工具类(RedisHelper.cs) public class RedisHelper { /// <summary> /// 获取值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <returns></returns> public T Get<T>(string key) { using (var redis = RedisConfig.Redis) { return redis.Get<T>(key); } } /// <summary> /// 设置值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="value"></param> /// <returns></returns> public bool Set<T>(string key, T value) { using (var redis = RedisConfig.Redis) { return redis.Set<T>(key, value); } } /// <summary> /// 设置值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="value"></param> /// <returns></returns> public bool Set<T>(string key, T value, DateTime dt) { using (var redis = RedisConfig.Redis) { return redis.Set<T>(key, value, dt); } } } 4、调用代码 class Program { static void Main(string[] args) { var redis = new RedisHelper(); redis.Set<string>("aa", DateTime.Now.ToString()); var d = redis.Get<string>("aa"); } }
前言 在我们的工作中,经常遇到Windows服务的安装和卸载,在之前公司也普写过一个WinForm程序选择安装路径,这次再来个小巧灵活的控制台程序,不用再选择,只需放到需要安装服务的目录中运行就可以实现安装或卸载。 开发思路 1、由于系统的权限限制,在运行程序时需要以管理员身份运行 2、因为需要实现安装和卸载两个功能,在程序运行时提示本次操作是安装还是卸载 需要输入 1 或 2 3、接下来程序会查找当前目录中的可执行文件并过滤程序本身和有时我们复制进来的带有vhost的文件,并列出列表让操作者选择(一般情况下只有一个) 4、根据用户所选进行安装或卸载操作 5、由于可能重复操作,需要递归调用一下 具体实现 首先们要操作服务,需要用 System.ServiceProcess 来封装实现类 1 using System; 2 using System.Collections; 3 using System.Configuration.Install; 4 using System.Linq; 5 using System.ServiceProcess; 6 7 namespace AutoInstallUtil 8 { 9 public class SystemServices 10 { 11 /// <summary> 12 /// 打开系统服务 13 /// </summary> 14 /// <param name="serviceName">系统服务名称</param> 15 /// <returns></returns> 16 public static bool SystemServiceOpen(string serviceName) 17 { 18 try 19 { 20 using (var control = new ServiceController(serviceName)) 21 { 22 if (control.Status != ServiceControllerStatus.Running) 23 { 24 control.Start(); 25 } 26 } 27 return true; 28 } 29 catch 30 { 31 return false; 32 } 33 } 34 35 36 /// <summary> 37 /// 关闭系统服务 38 /// </summary> 39 /// <param name="serviceName">系统服务名称</param> 40 /// <returns></returns> 41 public static bool SystemServiceClose(string serviceName) 42 { 43 try 44 { 45 using (var control = new ServiceController(serviceName)) 46 { 47 48 if (control.Status == ServiceControllerStatus.Running) 49 { 50 control.Stop(); 51 } 52 } 53 return true; 54 } 55 catch 56 { 57 return false; 58 } 59 } 60 61 /// <summary> 62 /// 重启系统服务 63 /// </summary> 64 /// <param name="serviceName">系统服务名称</param> 65 /// <returns></returns> 66 public static bool SystemServiceReStart(string serviceName) 67 { 68 try 69 { 70 using (var control = new ServiceController(serviceName)) 71 { 72 if (control.Status == System.ServiceProcess.ServiceControllerStatus.Running) 73 { 74 control.Continue(); 75 } 76 } 77 return true; 78 } 79 catch 80 { 81 return false; 82 } 83 } 84 85 /// <summary> 86 /// 返回服务状态 87 /// </summary> 88 /// <param name="serviceName">系统服务名称</param> 89 /// <returns>1:服务未运行 2:服务正在启动 3:服务正在停止 4:服务正在运行 5:服务即将继续 6:服务即将暂停 7:服务已暂停 0:未知状态</returns> 90 public static int GetSystemServiceStatus(string serviceName) 91 { 92 try 93 { 94 using (var control = new ServiceController(serviceName)) 95 { 96 return (int)control.Status; 97 } 98 } 99 catch 100 { 101 return 0; 102 } 103 } 104 105 /// <summary> 106 /// 返回服务状态 107 /// </summary> 108 /// <param name="serviceName">系统服务名称</param> 109 /// <returns>1:服务未运行 2:服务正在启动 3:服务正在停止 4:服务正在运行 5:服务即将继续 6:服务即将暂停 7:服务已暂停 0:未知状态</returns> 110 public static string GetSystemServiceStatusString(string serviceName) 111 { 112 try 113 { 114 using (var control = new ServiceController(serviceName)) 115 { 116 var status = string.Empty; 117 switch ((int)control.Status) 118 { 119 case 1: 120 status = "服务未运行"; 121 break; 122 case 2: 123 status = "服务正在启动"; 124 break; 125 case 3: 126 status = "服务正在停止"; 127 break; 128 case 4: 129 status = "服务正在运行"; 130 break; 131 case 5: 132 status = "服务即将继续"; 133 break; 134 case 6: 135 status = "服务即将暂停"; 136 break; 137 case 7: 138 status = "服务已暂停"; 139 break; 140 case 0: 141 status = "未知状态"; 142 break; 143 } 144 return status; 145 } 146 } 147 catch 148 { 149 return "未知状态"; 150 } 151 } 152 153 /// <summary> 154 /// 安装服务 155 /// </summary> 156 /// <param name="stateSaver"></param> 157 /// <param name="filepath"></param> 158 public static void InstallService(IDictionary stateSaver, string filepath) 159 { 160 try 161 { 162 var myAssemblyInstaller = new AssemblyInstaller 163 { 164 UseNewContext = true, 165 Path = filepath 166 }; 167 myAssemblyInstaller.Install(stateSaver); 168 myAssemblyInstaller.Commit(stateSaver); 169 myAssemblyInstaller.Dispose(); 170 } 171 catch (Exception ex) 172 { 173 throw new Exception("installServiceError/n" + ex.Message); 174 } 175 } 176 177 public static bool ServiceIsExisted(string serviceName) 178 { 179 ServiceController[] services = ServiceController.GetServices(); 180 return services.Any(s => s.ServiceName == serviceName); 181 } 182 183 /// <summary> 184 /// 卸载服务 185 /// </summary> 186 /// <param name="filepath">路径和文件名</param> 187 public static void UnInstallService(string filepath) 188 { 189 try 190 { 191 //UnInstall Service 192 var myAssemblyInstaller = new AssemblyInstaller 193 { 194 UseNewContext = true, 195 Path = filepath 196 }; 197 myAssemblyInstaller.Uninstall(null); 198 myAssemblyInstaller.Dispose(); 199 } 200 catch (Exception ex) 201 { 202 throw new Exception("unInstallServiceError/n" + ex.Message); 203 } 204 } 205 } 206 } 接下来我们封装控制台的操作方法为了实现循环监听这里用了递归 1 using System; 2 using System.Diagnostics; 3 using System.IO; 4 using System.Linq; 5 6 namespace AutoInstallUtil 7 { 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 try 13 { 14 ServerAction(); 15 } 16 catch (Exception ex) 17 { 18 Console.WriteLine("发生错误:{0}", ex.Message); 19 } 20 21 Console.ReadKey(); 22 } 23 24 /// <summary> 25 /// 操作 26 /// </summary> 27 private static void ServerAction() 28 { 29 Console.WriteLine("请输入:1安装 2卸载"); 30 var condition = Console.ReadLine(); 31 var currentPath = Environment.CurrentDirectory; 32 var currentFileNameVshost = Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName).ToLower(); 33 var currentFileName = currentFileNameVshost.Replace(".vshost.exe", ".exe"); 34 var files = 35 Directory.GetFiles(currentPath) 36 .Select(o => Path.GetFileName(o).ToLower()) 37 .ToList() 38 .Where( 39 o => 40 o != currentFileNameVshost 41 && o != currentFileName 42 && o.ToLower().EndsWith(".exe") 43 && o != "installutil.exe" 44 && !o.ToLower().EndsWith(".vshost.exe")) 45 .ToList(); 46 if (files.Count == 0) 47 { 48 Console.WriteLine("未找到可执行文件,请确认当前目录有需要安装的服务程序"); 49 } 50 else 51 { 52 Console.WriteLine("找到目录有如下可执行文件,请选择需要安装或卸载的文件序号:"); 53 } 54 int i = 0; 55 foreach (var file in files) 56 { 57 Console.WriteLine("序号:{0} 文件名:{1}", i, file); 58 i++; 59 } 60 var serviceFileIndex = Console.ReadLine(); 61 var servicePathName = currentPath + "\\" + files[Convert.ToInt32(serviceFileIndex)]; 62 if (condition == "1") 63 { 64 SystemServices.InstallService(null, servicePathName); 65 } 66 else 67 { 68 SystemServices.UnInstallService(servicePathName); 69 } 70 Console.WriteLine("**********本次操作完毕**********"); 71 ServerAction(); 72 } 73 } 74 } 到此为止简单的安装程序就写完了,为了醒目我选了个红色的西红柿来做为图标,这样显示些 源码和程序 http://pan.baidu.com/s/1qWqXG4W 提取码:piq4
导言 在日常开发中经常会用到列表,相信用过easyui,Ext等很多,的确很强大,但想修改确实也不容易,我也用了几年的easyui,有时间时会想一下,自已随然没有前端的精湛技术,但可以在有这些技术的开源框架上封装一下组成自已的控件,方便又好上手,扩展容易。 我们经常用Layer弹窗控件,今天也用他家的分页控件layPage和都熟悉的腾讯的模板引擎artTemplate还有老牌Jquery封装一个小的datagrid功能简单够用就好,方便扩展用到功能再加 代码 /* *基于Jquery 和 artTemplate 封装的列表控件 *可实现分页和无分页列表,功能单一欢迎大家补充 * Date: 2015-04-28 datagrid = $("#eTable").datagrid({ //pageIndex: 1, //pageSize: 10, //queryParams: $("#search").serialize(), url: "/Layui/GetJson", pagination: "pagination", scriptHtml: "eTableHtml", table: "eTableRow", isPagination: true, //onLoadSuccess: function (data) { // //alert(data); //} }); */ (function ($) { function init(options, obj) { function getParam() { var param = "pageIndex=" + opts.pageIndex + "&pageSize=" + opts.pageSize; param = param + "&" + opts.queryParams; return param; } function queryForm() { var cells = document.getElementById(opts.table).rows.item(0).cells.length; if (opts.isPagination) { document.getElementById(opts.pagination).innerHTML = ""; } var trStr = "<tr><td colspan=" + cells + " style='text-align:center'>{0}</td></tr>"; obj.html(trStr.replace("{0}", "<img src='/Scripts/datagrid/images/loading.gif'/>数据正在加载中...")); var url = opts.url + "?ts=" + Math.random(); $.post(url, getParam(), function (result) { if (result.list.length == 0 || typeof (result.list.length) == "undefined") { obj.html(trStr.replace("{0}", "<img width='18' src='/Scripts/datagrid/images/smiley_027.png'/>没有查询到您想要的数据")); return; } data.list = result.list; var html = template(opts.scriptHtml, data); obj.html(html); if (result.totalCount > 0 && opts.isPagination) { totalCount = result.totalCount; pageInitialize(opts.pagination, opts.pageIndex, opts.pageSize, result.totalCount); } callbackFun(); }); } function pageInitialize(pageID, pageIndex, pageSize, totalCount) { laypage({ cont: pageID, //容器。值支持id名、原生dom对象,jquery对象。【如该容器为】:<div id="page1"></div> pages: Math.ceil(totalCount / pageSize), //通过后台拿到的总页数 curr: pageIndex, //初始化当前页 jump: function (e, first) { //触发分页后的回调 opts.pageIndex = e.curr; if (!first) { //一定要加此判断,否则初始时会无限刷新 queryForm(); } } }); } function callbackFun() { if (opts.onLoadSuccess != null) { opts.onLoadSuccess(); } } var defaults = { pageSize: 10, pageIndex: 1, queryParams: "", pagination: "", scriptHtml: "", table: "", url: "", isPagination: false, onLoadSuccess: null } var opts = $.extend(defaults, options); var data = new Array(); var totalCount; queryForm(); var method = {}; return method.getPageIndex = function () { return this.pageIndex; },//当前页刷新 method.onReload = function () { queryForm(); },//重新加载 method.onLoad = function () { opts.pageIndex = 0; queryForm(); }, method.getData = function () { return data; }, method.getTotalCount = function () { return totalCount; }, method } $.fn.datagrid = function (options) { return init(options, $(this)); } })(jQuery) 用法 <table class="table table-compress mb20" id="eTableRow" width="800"> <thead> <tr> <th width="50%">ID</th> <th width="50%">Name</th> </tr> </thead> <tbody id="eTable"></tbody> </table> <div class="page" id="pagination"> </div> <script id="eTableHtml" type="text/html"> {{each list as value i}} <tr> <td width="50%" style="text-align:center"><span class="font-arial">{{value.Name}}</span></td> <td width="50%" style="text-align:center">{{value.Address}}</td> </tr> {{/each}} </script> <script type="text/javascript"> $(function () { datagrid = $("#eTable").datagrid({ //pageIndex: 1, //pageSize: 10, //queryParams: $("#search").serialize(), url: "/Layui/GetJson", pagination: "pagination", scriptHtml: "eTableHtml", table: "eTableRow", isPagination: true, //onLoadSuccess: function (data) { // //alert(data); //} }); }); </script> </body> 注:支持单页多列表多分页
TaskScheduler 在日常工作中,大家都会经常遇到Win服务,在我工作的这些年中一直在使用Quartz.Net这个任务统一调度框架,也非常好用,配置简单,但是如果多个项目组的多个服务部署到一台服务器时还是不尽如人意。 这段时间很忙,也一直未更新博客了,赶上今天下班早,就研究了一下,弄了个简单版基于Timer的山寨Quartz,当然了只是实现任务调度,闲话少说直接入主题吧 一、技术准备 其实都是普通的微软技术,一想到这方我们第一想到的可能就是反射,本文用了MEF 二、框架搭建 第一我们建立项目TianWei.TaskScheduler 第二我们会想到给Timer加个参数,这里建了一个 TWTimer来继承Timer,在里面有一个属性为JobDetail(Job详情实本),这样每个TImer我们就可以把任务详情做为参数传入 /// <summary> /// 自定义Timer /// </summary> public class TWTimer : System.Timers.Timer { public JobDetail JobDetail { get; set; } } 第三建立JobDetail /// <summary> /// 作业请情 /// </summary> [XmlRootAttribute("xml", IsNullable = false)] public class JobDetail { /// <summary> /// 作业名称 /// </summary> public string Name { get; set; } /// <summary> /// 作业执行类 /// </summary> public string JobType { get; set; } /// <summary> /// 自定义Cron表达式 /// </summary> public string CronExpression { get; set; } /// <summary> /// 作业类型 /// </summary> [XmlIgnoreAttribute] public WorkType WorkType { get; set; } /// <summary> /// 如果是每周 周几 /// </summary> [XmlIgnoreAttribute] public DayOfWeek Week { get; set; } /// <summary> /// 执行表达式 /// </summary> [XmlIgnoreAttribute] public string ExecuteExpression { get; set; } /// <summary> /// 执行间隔 循环执行有效 /// </summary> [XmlIgnoreAttribute] public int Interval { get; set; } /// <summary> /// 作业状态 停启用 /// </summary> public bool Enabled { get; set; } /// <summary> /// 作业开始工作时间- 可为空 /// </summary> public string StartTime { get; set; } /// <summary> /// 作业结束时间-可为空 /// </summary> public string EndTime { get; set; } /// <summary> /// 作业开始工作时间-默认为最小时间 /// </summary> [XmlIgnoreAttribute] public DateTime JobStartTime { get { DateTime value = DateTime.MinValue; if (StartTime != null) { DateTime.TryParse(StartTime, out value); } return value; } set { } } /// <summary> /// 作业结束工作时间-默认为最大时间 /// </summary> [XmlIgnoreAttribute] public DateTime JobEndTime { get { DateTime value = DateTime.MaxValue; if (EndTime != null) { DateTime.TryParse(EndTime, out value); } return value; } set { } } } 第四建立Job作为根据参数判断执行哪个Job public class Job { public void Execute(JobDetail jobDetail, IJob job) { if (!jobDetail.Enabled) return; if (DateTime.Now < jobDetail.JobStartTime || DateTime.Now > jobDetail.JobEndTime) return; if (jobDetail.WorkType == WorkType.Week) { if (jobDetail.Week == DateTime.Now.DayOfWeek && jobDetail.ExecuteExpression == DateTime.Now.ToString("HHmmss")) { job.Execute(); } } else if (jobDetail.WorkType == WorkType.Yearly) { if (jobDetail.ExecuteExpression == DateTime.Now.ToString("MMddHHmmss")) { job.Execute(); } } else if (jobDetail.WorkType == WorkType.Monthly) { if (jobDetail.ExecuteExpression == DateTime.Now.ToString("ddHHmmss")) { job.Execute(); } } else if (jobDetail.WorkType == WorkType.Daily) { if (jobDetail.ExecuteExpression == DateTime.Now.ToString("HHmmss")) { job.Execute(); } } else if (jobDetail.WorkType == WorkType.Loop) { job.Execute(); } } } 第五建立接口IJob,所有Job都要继承并实现Execute /// <summary> /// 作业接口 /// </summary> public interface IJob { /// <summary> /// 作业需要继承的接口 /// </summary> void Execute(); } 第六建立核心部分调度器,这里用到了MEF的导入和导出 public class Scheduler { [ImportMany(typeof(IJob))] public List<IJob> jobs; public Dictionary<string, IJob> dicJobs; public Dictionary<string, TWTimer> dicTimer; private void Run() { var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly())); catalog.Catalogs.Add(new DirectoryCatalog(Environment.CurrentDirectory)); var container = new CompositionContainer(catalog); container.ComposeParts(this); } public void Execute() { Run(); SetDicJobs(); SetDicTimers(); FileWatcher(); } private void SetDicJobs() { if (jobs != null) { dicJobs = new Dictionary<string, IJob>(); foreach (var job in jobs) { dicJobs.Add(job.ToString(), job); } } } private void SetDicTimers() { dicTimer = new Dictionary<string, TWTimer>(); var jobList = (List<JobDetail>)XmlHelper.XmlDeserialize(typeof(List<JobDetail>), Config.ConfigPath); if (jobList != null) { foreach (var item in jobList) { SetTimer(item); } } } /// <summary> /// Timer /// </summary> /// <param name="jobDetail"></param> private void SetTimer(JobDetail jobDetail) { TWTimer timer = new TWTimer(); timer.JobDetail = CronHelper.SetCron(jobDetail); if (timer.JobDetail.WorkType == WorkType.Loop) { timer.Interval = timer.JobDetail.Interval; } else { timer.Interval = 1000; } timer.AutoReset = true; timer.Enabled = true; timer.Elapsed += new ElapsedEventHandler(OnTimedEvent); dicTimer.Add(timer.JobDetail.Name, timer); } /// <summary> /// Timer事件 /// </summary> /// <param name="source"></param> /// <param name="e"></param> private void OnTimedEvent(object source, ElapsedEventArgs e) { try { var timer = (TWTimer)source; if (dicJobs.Any(o => o.Key == timer.JobDetail.JobType)) { Job job = new Job(); job.Execute(timer.JobDetail, dicJobs[timer.JobDetail.JobType]); } } catch (Exception ex) { //记录日志 } } /// <summary> /// 文件监听 /// </summary> private void FileWatcher() { FileSystemWatcher watcher = new FileSystemWatcher(); watcher.Path = Environment.CurrentDirectory; watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; watcher.Filter = Config.ConfigFile; watcher.Changed += new FileSystemEventHandler(OnChanged); watcher.EnableRaisingEvents = true; } /// <summary> /// 文件改动事件 /// </summary> /// <param name="source"></param> /// <param name="e"></param> private void OnChanged(object source, FileSystemEventArgs e) { var jobList = (List<JobDetail>)XmlHelper.XmlDeserialize(typeof(List<JobDetail>), Config.ConfigPath); if (jobList != null) { foreach (var item in jobList) { if (dicTimer.Any(o => o.Key == item.Name)) { var timer = dicTimer[item.Name]; if (item.JobType != timer.JobDetail.JobType || item.CronExpression != timer.JobDetail.CronExpression) { timer.JobDetail = CronHelper.SetCron(item); if (timer.JobDetail.WorkType == WorkType.Loop) { timer.Interval = timer.JobDetail.Interval; } else { timer.Interval = 1000; } } timer.JobDetail.Enabled = item.Enabled; timer.JobDetail.StartTime = item.StartTime; timer.JobDetail.EndTime = item.EndTime; } else { SetTimer(item); } } } } } 其它辅助类详见源码 三、使用方法 到这里一个任务调度框架的核心就完成了,下面我信介绍怎么使用 第一在我们想要用到的项目要填加引用TianWei.TaskScheduler 第二在想做为任务的类继承IJob并实现Execute方法并在类上面加上[Export(typeof(IJob))] 第三在服务程序或控制台程序中引用相关类(这里以控制台程序测试) 第四增加配置文件在App.config中增加<add key="JobsConfig" value="\Jobs.config"/> 在Jobs.config中增加如下配置一个任务一个JobDetail <?xml version="1.0"?> <ArrayOfJobDetail xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <JobDetail> <Name>Job1</Name> <JobType>TianWei.TaskScheduler.Jobs.Job1</JobType> <CronExpression>1 00 00 * * *</CronExpression> <Enabled>true</Enabled> <StartTime>2015-05-1 12:00:00</StartTime> <EndTime>2015-05-30 12:00:00</EndTime> </JobDetail> <JobDetail> <Name>Job2</Name> <JobType>TianWei.TaskScheduler.Jobs.Job2</JobType> <CronExpression>05 37 14 0 * *</CronExpression> <Enabled>true</Enabled> <StartTime></StartTime> <EndTime>2015-05-30 12:00:00</EndTime> </JobDetail> <JobDetail> <Name>Job3</Name> <JobType>TianWei.TaskScheduler.Jobs.Job3</JobType> <CronExpression>06 36 14 * * 2</CronExpression> <Enabled>true</Enabled> <StartTime>2015-05-20 12:00:00</StartTime> <EndTime>2015-05-30 12:00:00</EndTime> </JobDetail> <JobDetail> <Name>Job4</Name> <JobType>TianWei.TaskScheduler.Jobs.Job3</JobType> <CronExpression>08 35 14 26 05 *</CronExpression> <Enabled>true</Enabled> <StartTime>2015-05-20 12:00:00</StartTime> <EndTime>2015-05-30 12:00:00</EndTime> </JobDetail> </ArrayOfJobDetail> <!--自定义Cron 秒 分 时 日 月 周 当周不为*时 月和日不生效--> 第五增加如下代码来初使化 static void Main(string[] args) { Scheduler sc = new Scheduler(); sc.Execute(); Console.ReadKey(); } 第六运行程序如果Job中有输出就可以看到效果 四、自定义Crom解释 这里的Cron表达式也是个山寨的,自定义的,本想解析Quartz的表达式,但是感觉太复杂了 表达式一共六位组成 第一位:秒 只能是0-59或* 第二位:分 只能是0-59或* 第三位:小时 只能是0-24或* 第四位:日 只能是0-31或* 每天执行为0 第五位:月 只能是0-12或* 第六位:周 只能是0-6或* 注:当第六位不为*时第三四五位失效 例: 5 0 0 * * * 每隔五秒执行 5 2 1 * * * 每隔一小时两分钟五秒执行 5 37 14 0 * * 每天的14:37:5执行 6 36 14 * * 2 每周二的14:36:6执行 6 36 14 20 6 * 每年6月20号14:36:6执行 6 36 14 20 0 * 每月20号14:36:6执行 代码地址:https://github.com/hantianwei/TaskScheduler 如果有好的改动或是意见请反馈给我,代码改动后也回传我一份,谢谢
表: 数据: 查询方法: WITH Tree AS ( SELECT * FROM TableT AS tt WHERE tt.ID=8 UNION ALL SELECT tt.* FROM Tree AS t INNER JOIN TableT AS tt ON t.ID = tt.pCatID ) SELECT * FROM Tree AS t 查出父ID为8的所有节点下的数据
经常用到代码生成器,对于取数据脚本做个记录: #region SQL-SqlServer private string SqlTableList = @"SELECT so.name, Convert( VARCHAR(10), ep.[value]) AS [description] FROM sysobjects so(NOLOCK) LEFT JOIN sys.extended_properties ep(NOLOCK) ON ep.major_id=so.id AND ep.minor_id=0 WHERE so.[type]='U' AND so.name<>'sysdiagrams' ORDER BY so.name"; private string SqlFieldList = @"SELECT c.name, t.name AS [type], c.length AS maxLength, c.isnullable AS isNullable, ( SELECT COUNT(1) FROM sys.identity_columns ic(NOLOCK) WHERE ic.[object_id]=c.id AND ic.column_id=c.colid ) AS isIdentity, ( SELECT VALUE FROM sys.extended_properties ep(NOLOCK) WHERE ep.major_id = c.id AND ep.minor_id=c.colid ) AS [description], [IsPk]=CASE WHEN EXISTS (SELECT 1 FROM sysobjects WHERE xtype='PK' and parent_obj=c.id and name IN ( SELECT name FROM sysindexes WHERE indid in(SELECT indid FROM sysindexkeys WHERE id = c.id AND colid=c.colid))) THEN 1 ELSE 0 END FROM syscolumns c(NOLOCK) INNER JOIN sys.tables ts(NOLOCK) ON ts.[object_id] = c.id INNER JOIN sys.types t(NOLOCK) ON t.system_type_id=c.xtype INNER JOIN systypes st(NOLOCK) ON st.name=t.name AND st.name<>'sysname' INNER JOIN sysusers su(NOLOCK) ON st.uid=su.uid AND su.name='sys' --INNER JOIN syscolumns s(NOLOCK) ON c.[object_id]=s.id WHERE ts.name='{0}' ORDER BY c.id ASC"; #endregion #region SQL-MySql private string SqlTableList_MySql = @"SELECT TABLE_NAME as name, TABLE_COMMENT as description FROM information_schema.TABLES WHERE TABLE_SCHEMA='{0}'"; private string SqlFieldList_MySql = @"SELECT COLUMN_NAME as name, DATA_TYPE as type, IFNULL(CHARACTER_MAXIMUM_LENGTH,0) as maxLength, (CASE IS_NULLABLE WHEN 'NO' THEN 0 ELSE 1 END) as isNullable, 0 as isIdentity, COLUMN_COMMENT as description, (CASE COLUMN_KEY WHEN 'PRI' THEN 1 ELSE 0 END) as IsPk FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='{0}' AND TABLE_NAME='{1}' ORDER BY ORDINAL_POSITION ASC"; #endregion
大家都在用LOG4NET,但这是封装好的,在有时我们找不到原因时会想到是不是发生在里面,比如,配置好了日志记录到数据库(Mysql、Oracle、Sql Server)等,但就是记录不上,又找不到原因时,就在想要是LOG4NET能跟一下好了,在这里我们可能用LOG4NET源码,但还要把源码引用进来较麻烦,其实不用这样,开启LOG4NET内部调试就好,还可以记录到一个文件方便我们查找原因: 在appSettings节点中增加名称为“log4net.Internal.Debug”的节点,并确保值为“true" <configuration> <appSettings> <add key="log4net.Internal.Debug" value="true"/> </appSettings> </configuration> 确认有读写文件的权限 <system.diagnostics> <trace autoflush="true"> <listeners> <add name="textWriterTraceListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="C:\tmp\log4net.txt" /> </listeners> </trace> </system.diagnostics> 这样就可以再”C:\tmp\log4net.txt"中查看系统日志,查找问题原因。
这几天业余时间在玩百度云,百度的云还是不错的,但是对于我这样的.NET程序员,有点不公平,没有.net虚机,不过也不是百度一家没有,基本都没有,有的都是那种开放云,自已在云端来部署安装软件的。 所以也来玩玩JAVA,对于JAVA并不陌生,随不是我主业程序,但也是驾轻就熟,对于Struts Spring hibernate 等也是很熟悉,弄了个SSH来做个小应用,但在URLREWRIT上遇到了困难,怎么没有参数有可以重写,有参数的无法重写,哎,在网上找了半天才发现 一、两个过滤器的顺序放反了,应该URLREWRITE放在前面 <filter-mapping> <filter-name>UrlRewriteFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>StrutsCleanUpFilter</filter-name> <filter-class>org.apache.struts2.dispatcher.ActionContextCleanUp</filter-class> </filter> <filter-mapping> <filter-name>StrutsCleanUpFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> <init-param> <param-name>config</param-name> <param-value>struts-default.xml,struts-plugin.xml,/com/wms/resources/struts.xml</param-value> </init-param> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> </filter-mapping> 二、没有对Struts做处理 <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> </filter-mapping> 这样对有参数的URL也可以重写了,哈哈 非常感谢百度云,从一开始推去云计算我就在使用,随然现在开始收费了,但百度的费还是草根站长可以支付的起的,也是学习。
基于ASP.NET MVC 4/5 Razor的模块化/插件式架构实现 概述 在日常开发中, 我们经常谈起模块化/插件化架构,这样可既可以提高开效率,又可以实现良好的扩展性,尤其对于产品化的系统有更好的实用性。 架构 我们采用的是MVC5(本文中介绍的方法对于MVC4也是适用的),如下图,解决方案中有四个项目,其中 WeDiscuss 为前端,WeDiscuss.Plugin.Framework 为插件公共类库 WeDiscuss.Plugin.Album 为插件(相册) WeDiscuss.Plugin.News 为插件(新闻),本文只是讲解决插件的实现方式,就不多做其它如果业务逻辑、数据访问层等 注;每个插件都有自已的(M、V、C),内部实现和常用MVC没有区别,这样可以方便的开发,没有其它新知识的引入。 其中,插件层可以在主项目中引用,也可以不引用,或是放到其它目录下(如把插件DLL单独放到“Plugins”目录中),如果不引用就采用在编译完成时复制 下面讲解编译完成复制方法,如想复制到“Plugins”目录中请修改BIN为“Plugins”: 在如下图加入: copy /Y "$(TargetDir)$(ProjectName).dll" "$(SolutionDir)Wediscuss\Bin\" 如何让ASP.NET加载BIN目录之外的路径的Assembly 我们把各个模块编译出来的assembly和各个模块的配置文件自动放到一个bin平级的plugin目录,然后web应用启动的时候自动扫描这个plugin目录并加载各个模块plugin,这个怎么做到的?大家也许知道,ASP.NET只允许读取Bin目录下的assbmely,不可以读取其他路径,包括Bin\abc等,即使在web.config这样配置probing也不行:(不信你可以试一下) <configuration> Element <runtime> Element <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="bin;plugins;"/> </assemblyBinding> </runtime> </configuration> 如何注入菜单 插件能用了,但也想动态注入菜单,这样才实现了自动化,要不还是人工进行菜单注入永远是半自动化,这和我们开发的思想是不想符的,下面就来说一下菜单的注入 1、首称在WeDiscuss.Plugin.Framework 为插件公共类库中建实体类PluginMenu 和PluginMenus /* * ------------------------------------------------------------------------------- * 功能描述: * * 创建人: JunHan(俊涵) * 创建日期: 2013/12/15 21:59:16 * 创建说明: * * 修改人: * 修改日期: * 修改说明: * * ------------------------------------------------------------------------------- */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.Mvc; namespace WeDiscuss.Plugin.Framework { public class PluginMenus { public List<PluginMenu> MenuList { get; set; } public string CssClass { get; set; } public int MenuType { get; set; } public string Html { get { StringBuilder stringBuilder = new StringBuilder(); foreach (var menu in MenuList) { TagBuilder tagBuilder = new TagBuilder("a"); tagBuilder.MergeAttribute("href", menu.MenuUrl); tagBuilder.InnerHtml = menu.MenuText; tagBuilder.MergeAttribute("class", CssClass); stringBuilder.Append(tagBuilder.ToString(TagRenderMode.Normal) + "\r\n"); } return stringBuilder.ToString(); } } public List<PluginMenu> AvailableList { get { if (MenuList == null) { return new List<PluginMenu>(); } if (MenuType == 0) { return MenuList; } if (!MenuList.Any(o => o.MenuType == MenuType)) { return new List<PluginMenu>(); } return MenuList.Where(o => o.MenuType == MenuType).ToList(); } } } public class PluginMenu { public string MenuText { get; set; } public string MenuUrl { get; set; } public int MenuType { get; set; } public int MenuOrder { get; set; } public bool Visible { get; set; } } } 这样我们就实现了菜单的结构,接下来就是采单的生成或注入方法: 新建 AppPlugin 和 PluginApplication来实现菜单的初使化方法,并将生成好的菜单存放在静态变量中。 /* * ------------------------------------------------------------------------------- * 功能描述: * * 创建人: JunHan(俊涵) * 创建日期: 2013/12/15 23:29:58 * 创建说明: * * 修改人: * 修改日期: * 修改说明: * * ------------------------------------------------------------------------------- */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WeDiscuss.Plugin.Framework { public class PluginApplication : BaseMvcPluginApplication { #region Plugin Menu Support public static PluginMenus PluginMenus = new PluginMenus(); public void RegisterMenuItem(PluginMenu menu) { lock (PluginMenus) { if (PluginMenus.MenuList == null) PluginMenus.MenuList = new List<PluginMenu>(); PluginMenus.MenuList.Add(new PluginMenu() { MenuText = menu.MenuText, MenuUrl = menu.MenuUrl, MenuType = menu.MenuType, MenuOrder = menu.MenuOrder }); PluginMenus.MenuList = PluginMenus.MenuList.OrderBy(o => o.MenuOrder).ToList(); } } #endregion public static new PluginApplication Instance { get { return BaseMvcPluginApplication.Instance as PluginApplication; } set { BaseMvcPluginApplication.Instance = value; } } protected override bool ShouldIncludeResourceCore(BaseMvcPluginApplication.ResourceTypes type, IMvcPlugin plugin) { return ShouldIncludeResource(plugin, null); } protected virtual bool ShouldIncludeResource(IMvcPlugin plugin, object resource) { bool should = true; if (plugin != null) { if ((should = plugin.Enabled) && plugin is AppPlugin) should = ((AppPlugin)plugin).ShouldIncludeResource(resource); } return should; } protected override void AddAdditionalRazorViewLocationsCore(List<string> lst) { lst.Add("~/Plugins/PluginDemo/Views.{1}.{0}.cshtml"); } public static PluginApplication SetupApplication(object bundles, object routes) { PluginApplication me = new PluginApplication(bundles, routes); return me; } protected PluginApplication(object bundles, object routes) : base(bundles, routes) { } } public class AppPlugin : BaseMvcPlugin { public PluginApplication _App { get { return (PluginApplication)App; } } public AppPlugin(bool ensureStandardViewLocation = true) : base(ensureStandardViewLocation) { } public void DefineMenuItem(PluginMenu item) { _App.RegisterMenuItem(item); } public virtual bool ShouldIncludeResource(object content) { return true; } } } 2、菜单初使化 在每个插件项目中新建一类,并继承AppPlugin,重写方法:SetupExtensions 调用DefineMenuItem 实现菜单初使化,在菜单的结果中我们看到有MenuType类型,这里我们自定义,一般会用枚举来实现,可以定义为前台或后台等,一个插件可以拥有多个菜单,可以注入多个地方 using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; using System.Text; using System.Threading.Tasks; using WeDiscuss.Plugin.Framework; namespace WeDiscuss.Plugin.Album { [Export(typeof(IMvcPlugin))] [MvcPluginMetadata("AlbumPlugin", null, "Demo App Site Album", "")] class AlbumPlugin : AppPlugin { public override void SetupExtensions(IMvcPluginApplication app) { base.SetupExtensions(app); DefineMenuItem(new PluginMenu { MenuText = "相册", MenuUrl = "/Album", MenuType = 1, MenuOrder = 1 }); DefineMenuItem(new PluginMenu { MenuText = "相册管理", MenuUrl = "/ManageAlbum", MenuType = 2, MenuOrder = 1 }); } } } 3、菜单调用 在需要出现插件菜单的地方,我们用以下方法实现菜单的注入并呈现 @using WeDiscuss.Plugin.Framework @{ var menu = PluginApplication.PluginMenus; menu.MenuType = 1; } <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title - 我的 ASP.NET 应用程序</title> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") </head> <body> <div class="navbar navbar-inverse navbar-fixed-top"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> @Html.ActionLink("应用程序名称", "Index", "Home", null, new { @class = "navbar-brand" }) </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li>@Html.ActionLink("主页", "Index", "Home")</li> <li>@Html.ActionLink("关于", "About", "Home")</li> <li>@Html.ActionLink("联系方式", "Contact", "Home")</li> @{ foreach (var item in menu.AvailableList) { <li><a href="@item.MenuUrl">@item.MenuText</a></li> } } </ul> @Html.Partial("_LoginPartial") </div> </div> </div> <div class="container body-content"> @RenderBody() <hr /> <footer> <p>&copy; @DateTime.Now.Year - 我的 ASP.NET 应用程序</p> </footer> </div> @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/bootstrap") @RenderSection("scripts", required: false) </body> </html> 小结 本文主要讲解了实现方法,并没有讲内部结构是如何实现的,以后的分享中我们会慢慢讲解内部实现逻辑和产品化中的应用和配置。大这有问题或是好的建议想法可以发邮件给我 junhan@wediscuss.cn 如果您修改了代码以实现更好的功能,也烦请转发我一份谢谢! 源码下载 我们站在前辈的肩膀上成长,感谢所有帮助WD成长的人. 源码中没有加入packages,请大家自行加载,源码下载 ,没有密码全部开放!
在网上查了很长时间没有找到解决方法,干脆自已查看一下代码来解决吧,随是压缩过的,不过也还是好查的,工夫不负有心人,终于解决了,方法如下: 一、我们先让表格自适应宽度 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Basic DataGrid - jQuery EasyUI Demo</title> <link rel="stylesheet" type="text/css" href="../../themes/bootstrap/easyui.css"> <link rel="stylesheet" type="text/css" href="../../themes/icon.css"> <link rel="stylesheet" type="text/css" href="../demo.css"> <script type="text/javascript" src="../../jquery.min.js"></script> <script type="text/javascript" src="../../jquery.easyui.min.js"></script> </head> <body> <h2>Basic DataGrid</h2> <div class="demo-info"> <div class="demo-tip icon-tip"></div> <div>The DataGrid is created from markup, no JavaScript code needed.</div> </div> <div style="margin:10px 0;"></div> <table class="easyui-datagrid" data-options="singleSelect:true,collapsible:true,url:'datagrid_data1.json',method:'get',fitColumns:'true'"> <thead> <tr> <th data-options="field:'itemid',width:300">Item ID</th> <th data-options="field:'productid',width:300">Product</th> <th data-options="field:'listprice',width:300,align:'right'">List Price</th> <th data-options="field:'unitcost',width:300,align:'right'">Unit Cost</th> <th data-options="field:'attr1',width:300">Attribute</th> <th data-options="field:'status',width:300,align:'center'">Status</th> </tr> </thead> </table> <script> $(window).resize(function () { $('#tt').datagrid('resize'); }); </script> </body> </html> 二、修改jquery.easyui.min.js这个文件来解决滚动条问题 function _4bf(_4c0){ var opts=$.data(_4c0,"datagrid").options; var dc=$.data(_4c0,"datagrid").dc; var wrap=$.data(_4c0,"datagrid").panel; var _4c1=wrap.width()+20; var _4c2=wrap.height(); var view=dc.view; var _4c3=dc.view1; var _4c4=dc.view2; var _4c5=_4c3.children("div.datagrid-header"); var _4c6=_4c4.children("div.datagrid-header"); var _4c7=_4c5.find("table"); var _4c8=_4c6.find("table"); view.width(_4c1); var _4c9=_4c5.children("div.datagrid-header-inner").show(); _4c3.width(_4c9.find("table").width()); if(!opts.showHeader){ _4c9.hide(); } 可以看到我在 7001 行处加上了一个+20,这样让宽度正好覆盖滚动条 大家如果有更好的方法留言给我呀
一,简介Signal 是微软支持的一个运行在 Dot NET 平台上的 html websocket 框架。它出现的主要目的是实现服务器主动推送(Push)消息到客户端页面,这样客户端就不必重新发送请求或使用轮询技术来获取消息。 二,实现机制SignalR 的实现机制与 .NET WCF 或 Remoting 是相似的,都是使用远程代理来实现。在具体使用上,有两种不同目的的接口:PersistentConnection 和 Hubs,其中 PersistentConnection 是实现了长时间的 Javascript 轮询(类似于 Comet),Hub 是用来解决实时信息交换问题,它是利用 Javascript 动态载入执行方法实现的。SignalR 将整个连接,信息交换过程封装得非常漂亮,客户端与服务器端全部使用 JSON 来交换数据。 下面就 Hubs 接口的使用来讲讲整个流程: 1,在服务器端定义对应的 hub class; 2,在客户端定义 hub class 所对应的 proxy 类; 3,在客户端与服务器端建立连接(connection); 4,然后客户端就可以调用 proxy 对象的方法来调用服务器端的方法,也就是发送 request 给服务器端; 5,服务器端接收到 request 之后,可以针对某个/组客户端或所有客户端(广播)发送消息。 三,Hub 示例教程1,工具准备SignalR 运行在 .NET 4.5 平台上,所以需要安装 .NET 4.5。为了方便演示,本示例使用 ASP.NET MVC 在 Win 7 系统来实现。这需要安装 ASP.NET MVC 3 或 ASP.NET MVC 4。 2,建立工程打开 VS2010/VS2012 新建名为 SignalRTutorial 的 ASP.NET MVC 3 Web Application 工程,选择 Internet Application 模板, Razor 视图引擎以及勾选 Use HTMl 5 标签。 3,安装 SignalR打开 NuGet 的 package manager console(Tools->Library package manager),输入:install-package SignalR.Sample,回车安装。如图所示: 4,实现 Hub 服务器端代码向工程中新建 SignalR 目录,在其中添加 ChatHub.cs 文件,内容如下: namespace SignalTutorial.SignalR { [HubName("chat")] public class Chat : Hub { public void Send(string clientName, string message) { //var toSelfinfo = "You had sent message " + message; //Caller.addSomeMessage(clientName, toSelfinfo); // Call the addMessage method on all clients Clients.addSomeMessage(clientName, message); //Clients[Context.ConnectionId].addSomeMessage(clientName, data); } } } 在上面的代码中: 1),HubName 这个特性是为了让客户端知道如何建立与服务器端对应服务的代理对象,如果没有设定该属性,则以服务器端的服务类名字作为 HubName 的缺省值; 2),Chat 继承自 Hub,从下面 Hub 的接口图可以看出:Hub 支持向发起请求者(Caller),所有客户端(Clients),特定组(Group) 推送消息。 3),public void Send(string clientName, string message) 这个接口是被客户端通过代理对象调用的; 4),Clients 是 Hub 的属性,表示所有链接的客户端页面,它和 Caller 一样是 dynamic,因为要直接对应到 Javascript 对象; 5),Clients.addSomeMessage(clientName, message); 表示服务器端调用客户端的 addSomeMessage 方法,这是一个 Javascript 方法,从而给客户端推送消息。 6),总结:这里实现的服务很简单,就是当一个客户端调用 Send 方法向服务器发送 message 后,服务器端负责将该 message 广播给所有的客户端(也可以给特定组或特定客户端,见屏蔽代码),以实现聊天室的功能。 5,实现 Hub 客户端代码1),引用 SignalR Javascript 为了简化引用 SignalR 脚本操作,我直接在 View/Shared/_Layout.cshtml 中引入 SignalR 及其他脚本: <head> <meta charset="utf-8" /> <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery-1.6.4.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery-ui-1.8.24.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.signalR-0.5.3.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/signalr/hubs")" type="text/javascript"></script> </head> 注意:signalR 依赖于 jquery,所以 signalR 必须放在 jquery 之后,而 hubs 又必须放在 signalR 之后。 然后在 body 部分加入 HubChat Tab: <li>@Html.ActionLink("HubChat", "HubChat", "Home")</li> 2),生成访问页面 在 HomeController 中添加如下方法: public ActionResult HubChat() { ViewBag.ClientName = "用户-" + Rnd.Next(10000, 99999); return View(); } 这里由服务器根据随机数来设定客户端的名字,不够严谨,因为随机数生成的名字不是唯一的的,在这里仅为简化演示,实际应用中应该使用 GUID 。 然后生成对应的 View:HubChat.cshtml @model dynamic @{ ViewBag.Title = "title"; } <script src="@Url.Content("~/Scripts/hubDemo.js")" type="text/javascript"></script> <script type="text/javascript"> $(document).ready(function () { }); </script> <h2>Hub Chat</h2> <div> <input type="text" id="Placeholder" value="@ViewBag.ClientName" hidden="true"/> <input type="text" id="msg" /> <input type="button" id="broadcast" value="广播" /> <br /> <br /> <h3> 消息记录: (你是:<span id="MyClientName">@ViewBag.ClientName</span>): </h3> <ul id="messages"> </ul> </div> 在上面的页面代码中,我添加了名为 hubDemo.js 的脚本,这将在下面介绍;此外还有一个id 为 Placeholder 的隐藏 input 控件,这是为了向 Javascript 中传递客户端的名字。 3),编写 Javascript 向 Scripts 目录添加新的 Javescript 脚本:hubDemo.js。其内容如下: $(function () { var myClientName = $('#Placeholder').val(); // Proxy created on the fly var chat = $.connection.chat; // Declare a function on the chat hub so the server can invoke it chat.addSomeMessage = function (clientName, message) { writeEvent('<b>' + clientName + '</b> 对大家说: ' + message, 'event-message'); }; $("#broadcast").click(function () { // Call the chat method on the server chat.send(myClientName, $('#msg').val()) .done(function () { console.log('Sent message success!'); }) .fail(function (e) { console.warn(e); }); }); // Start the connection $.connection.hub.start(); //A function to write events to the page function writeEvent(eventLog, logClass) { var now = new Date(); var nowStr = now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds(); $('#messages').prepend('<li class=" + logClass + "><b>' + nowStr + '</b> ' + eventLog + '.</li>'); } }); 上面代码有详细的注释,下面再讲讲关键之处: 1,首先获取客户端页面的名字; 2,然后通过 $.connection.chat 建立对应服务器端 Hub 类的代理对象 chat; 3,定义客户端的 Javascript 方法 addSomeMessage ,服务器通过 dynamic 方式调用客户端的该方法以实现推送功能。在这里每当收到服务器推送来的消息,就在客户端页面的 messages 列表表头插入该消息。 4,当点击广播按钮时,客户端通过代理对象调用服务器端的 send 方法以实现向服务器发送消息。 5,通过 $.connection.hub.start(); 语句打开链接。 6),编译运行 Hub 示例在多个浏览器窗口打开页面,效果如下: 四,Persistent Connection 示例教程1,实现服务器端代码1),编写服务器 PersistentConnection 代码向工程中 SignalR 目录中添加 PersistentConnection.cs 文件,内容如下: using System; using System.Collections.Generic; using System.Threading.Tasks; using SignalR; namespace SignalTutorial.SignalR { public class MyConnection : PersistentConnection { protected override Task OnConnectedAsync(IRequest request, string connectionId) { return Connection.Broadcast("Connection " + connectionId + " connected"); } protected override Task OnReconnectedAsync(IRequest request, IEnumerable<string> groups, string clientId) { return Connection.Broadcast("Client " + clientId + " re-connected"); } protected override Task OnReceivedAsync(IRequest request, string connectionId, string data) { var info = data + ". ConnectionId is [" + connectionId + "]"; // return Connection.Send(connectionId, info); // Broadcast data to all clients return Connection.Broadcast(info); } protected override Task OnDisconnectAsync(string connectionId) { return Connection.Broadcast("Connection " + connectionId + " disconncted"); } protected override Task OnErrorAsync(Exception error) { return Connection.Broadcast("Error ocurred " + error); } } } 在上面的代码中: 1,MyConnection 继承自 PersistentConnection,这样我们就能在客户端连接,重连接,断开连接,发送消息以及连接出错的情况下进行相关的处理。从下面的 PersistentConnection 接口中可以看到,PersistentConnection 同样支持组进行推送。 2,推送消息由 PersistentConnection 的属性 Connection 来提供,它继承自 IConnection 接口,该接口提供两个函数来实现对特定客户端的推送和广播功能。 System.Threading.Tasks.Task Send(string signal, object value) System.Threading.Tasks.Task Broadcast(object value) 2),配置访问路由为了支持客户端访问,需要在路由表中进行配置。打开 Global.asax.cs ,修改 Application_Start() 函数如下: protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RouteTable.Routes.MapConnection<MyConnection>("echo", "echo/{*operation}"); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); // Make connections wait 50s maximum for any response. After // 50s are up, trigger a timeout command and make the client reconnect. GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(50); //DisconnectTimeout //HeartBeatInterval //KeepAlive } 在上面的代码中,我将 echo 及其子路径的访问映射到 MyConnection 上,并设置连接超时时间为 50 s。在这里还可以设置其他的一些参数,如断连超时时间,心跳间隔等。 2,实现客户端代码1),生成访问页面 在前面三 Hub 示例教程的基础上,我们向该工程加入使用 Persistent Connection 的演示。和前面一样,向 _Layout.cshtml 中加入 PersistentChat Tab: <li>@Html.ActionLink("PersistentChat", "PersistentChat", "Home")</li> 然后在 HomeController 中添加如下方法: public ActionResult PersistentChat() { ViewBag.ClientName = "用户-" + Rnd.Next(10000, 99999); return View(); } 这里由服务器根据随机数来设定客户端的名字,不够严谨,因为随机数生成的名字不是唯一的的,在这里仅为简化演示,实际应用中应该使用 GUID 。 然后生成对应的 页面: PersistentChat.cshtml: @model dynamic @{ ViewBag.Title = "title"; } <script src="@Url.Content("~/Scripts/persistent.js")" type="text/javascript"></script> <h2>Persistent Chat</h2> <div> <input type="text" id="Placeholder" value="@ViewBag.ClientName" hidden="true"/> <input type="text" id="msg" /> <input type="button" id="broadcast" value="广播" /> <br /> <br /> <h3> 消息记录: (你是:<span id="MyClientName">@ViewBag.ClientName</span>): </h3> <ul id="messages"> </ul> </div> 在上面的页面代码中,我添加了名为 persistent.js 的脚本,这将在下面介绍;此外还有一个id 为 Placeholder 的隐藏 input 控件,这是为了向 Javascript 中传递客户端的名字。 2),编写 Javascript 向 Scripts 目录添加新的 Javescript 脚本:persistent.js。其内容如下: $(function () { var myClientName = $('#Placeholder').val(); var connection = $.connection('/echo'); connection.received(function (data) { var msg = new String(data); var index = msg.indexOf("#"); var clientName = msg.substring(0, index); var content = msg.substring(index + 1); if (clientName == null || clientName == "") { writeEvent('<b>' + "系统消息" + '</b>: ' + content, 'event-message'); } else { writeEvent('<b>' + clientName + '</b> 对大家说: ' + content, 'event-message'); } }); connection.start(); $("#broadcast").click(function () { var msg = myClientName + "#" + $('#msg').val(); connection.send(msg); }); //A function to write events to the page function writeEvent(eventLog, logClass) { var now = new Date(); var nowStr = now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds(); $('#messages').prepend('<li class=" + logClass + "><b>' + nowStr + '</b> ' + eventLog + '.</li>'); } }); 上面的代码基本与前面的 Hub 实现相同,在此就不再一一讲述。有两点值得说明: 1,创建连接时,指定路径为 "/echo",该路径在服务器端的路由映射表被映射为 MyConnection,因而这个连接就被指向前面提供的 MyConnection。 2,将 clientName 信息放入 message 中,并用 # 将 clientName 和消息内容连接成一个 msg。 3,编译运行 Persistent 示例
/// <summary> /// 备注特性 /// </summary> public class RemarkAttribute : Attribute { /// <summary> /// 备注 /// </summary> public string Remark { get; set; } public RemarkAttribute(string remark) { this.Remark = remark; } } /// <summary> /// 枚举扩展类 /// </summary> public static class EnumExtension { /// <summary> /// 获取枚举的备注信息 /// </summary> /// <param name="em"></param> /// <returns></returns> public static string GetRemark(this Enum value) { FieldInfo fi = value.GetType().GetField(value.ToString()); if (fi == null) { return value.ToString(); } object[] attributes = fi.GetCustomAttributes(typeof(RemarkAttribute), false); if (attributes.Length > 0) { return ((RemarkAttribute)attributes[0]).Remark; } else { return value.ToString(); } } public static string GetEnumDescription(this Enum value) { FieldInfo fi = value.GetType().GetField(value.ToString()); DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); if (attributes.Length > 0) { return attributes[0].Description; } else { return value.ToString(); } } } var aaa = UserType.Type1.GetRemark(); var aab = UserType.Type2.GetEnumDescription();
一、示例演示 1、用C# 建立数据库 CRL 项目 public partial class MyClr{ [Microsoft.SqlServer.Server.SqlFunction] public static SqlBoolean CLRFBitContains(string MyBigIntValue, int Row) { // 在此处放置代码 return (new MyBigInt(MyBigIntValue) & MyBigInt.CreateByBitPositon(Convert.ToUInt32(Row))) == MyBigInt.Zero; }} 2、数据库初始化 exec sp_configure 'show advanced options', '1';goreconfigure;goexec sp_configure 'clr enabled', '1'goreconfigure;exec sp_configure 'show advanced options', '1'; go ALTER DATABASE DB_Name set TRUSTWORTHY on;go 3、数据库添加程序集 create assembly MyClrfrom 'R:\MyApp\MyClr\MyClr\bin\Debug\MyClr.dll'WITH permission_set = Safe; 4、数据库添加函数映射 create function ClrFBitContains( @val as nvarchar(4000) , @RowIndex as int )returns bitas external name MyClr.MyClr.CLRFBitContains ; TestSql:select dbo.ClrFBitContains('F0F',11) 二、在sqlserver中如果要使用一个程序集一般有如下注意事项 1、打开sqlserver 的CLR支持,即在Sql Server中执行这段代码可以开启CLR exec sp_configure 'show advanced options', '1';goreconfigure;goexec sp_configure 'clr enabled', '1'goreconfigure;exec sp_configure 'show advanced options', '1'; go 2、是否需要访问外部资源,如果需要访问外部资源还需要执行下面的,即打开数据库的TRUSTWORTHY 属性 ALTER DATABASE DB_Name set TRUSTWORTHY on; 三、在sqlserver中如果要使用一个程序集一般有两种方法 方法一:通过T-SQL手动将该程序集放入到SQL Server 其步骤如下 1、创建将要使用的程序集.dll 程序上没什么稀奇的,跟普通的ado.net的程序差不多主要就是方法上加了一个属性 (<System.Data.Sql.SqlProcedure(name:="WriteHashedPassword")>) 2、部署程序集到sqlserver a、打开sqlserver中clr中相关的支持(方法如上) b、添加程序集到sqlserver Create assembly SqlServerProject1 authorization dbo from 'E:\test\dotnet\SqlServerProject1\SqlServerProject1\bin\SqlServerProject1.dll' with permission_set=external_access c、添加存储过程 Create Procuce [dbo].[存储过程名称] @from [nvarchar](50), -- 参数列表 @to [nvarchar](50) WITH EXECUTE AS CALLER AS EXTERNAL NAME [程序集的名称].[StoredProcedures].[存储过程名称] d、执行存储过程 exec CLRSPTest @sql='select * from t2',@path='e:\a\11.txt' 方法二:通过Visual Studio 2005创建供并部署SQL Server 2005用的Assembly 1、打开Visual Studio 2005新建项目类型为数据库的sqlserver项目 2、创建所需要的存储过程,函数等 3、编译该项目 4、部署该项目(此时我们打开sqlserver相应的数据库可以看到存储过程目录及程序集等目录下有相应创建的对象了)
因为输入法或浏览器的问题,在输入中文后并没有触发自动完成,要再按多一下键盘才触发,查看发现它是用keydown来实现.bind("keydown.autocomplete", function(event) {....})可以对其input事件进行监听来解决问题,在上面的代码后绑定一个input事件 .bind("input.autocomplete",function(event){ if(suppressKeyPress){ suppressKeyPress = false; event.preventDefault(); } clearTimeout(self.searching); self.searching = setTimeout(function(){ if(self.term != self.element.val()){ self.selectedItem = null; self.search(null, event); } }, self.options.delay); }) 其内容参照 keydown的default处理Min版修改 //找到 default:clearTimeout(b.searching),b.searching=setTimeout(function(){b.term!=b.element.val()&&(b.selectedItem=null,b.search(null,c))},b.options.delay)}}}) //在这后加上 .bind("input.autocomplete",function(a){clearTimeout(b.searching),b.searching=setTimeout(function(){b.term!=b.element.val()&&(b.selectedItem=null,b.search(null,c))},b.options.delay)}) //接上后面的 .bind("keypress.autocomplete",function(a){
今天看到SQL数据类型和C#数据类型间的转换,前人留下的。 <?xml version="1.0" encoding="utf-8" ?> <Languages> <Language From="SQL" To="C#"> <Type From="bigint" To="long" /> <Type From="binary" To="object" /> <Type From="bit" To="bool" /> <Type From="char" To="string" /> <Type From="datetime" To="DateTime" /> <Type From="decimal" To="decimal" /> <Type From="float" To="double" /> <Type From="image" To="byte[]" /> <Type From="int" To="int" /> <Type From="money" To="decimal" /> <Type From="nchar" To="string" /> <Type From="ntext" To="string" /> <Type From="numeric" To="decimal" /> <Type From="nvarchar" To="string" /> <Type From="real" To="float" /> <Type From="smalldatetime" To="DateTime" /> <Type From="smallint" To="short" /> <Type From="smallmoney" To="decimal" /> <Type From="text" To="string" /> <Type From="timestamp" To="byte[]" /> <Type From="tinyint" To="byte" /> <Type From="uniqueidentifier" To="Guid" /> <Type From="varbinary" To="byte[]" /> <Type From="varchar" To="string" /> <Type From="xml" To="string" /> <Type From="sql_variant" To="object" /> </Language> <Language From="SQL" To="C# System Types"> <Type From="bigint" To="System.Int64" /> <Type From="binary" To="System.Object" /> <Type From="bit" To="System.Boolean" /> <Type From="char" To="System.String" /> <Type From="datetime" To="System.DateTime" /> <Type From="decimal" To="System.Decimal" /> <Type From="float" To="System.Double" /> <Type From="image" To="System.Byte[]" /> <Type From="int" To="System.Int32" /> <Type From="money" To="System.Decimal" /> <Type From="nchar" To="System.String" /> <Type From="ntext" To="System.String" /> <Type From="numeric" To="System.Decimal" /> <Type From="nvarchar" To="System.String" /> <Type From="real" To="System.Single" /> <Type From="smalldatetime" To="System.DateTime" /> <Type From="smallint" To="System.Int16" /> <Type From="smallmoney" To="System.Decimal" /> <Type From="text" To="System.String" /> <Type From="timestamp" To="System.Byte[]" /> <Type From="tinyint" To="System.Byte" /> <Type From="uniqueidentifier" To="System.Guid" /> <Type From="varbinary" To="System.Byte[]" /> <Type From="varchar" To="System.String" /> <Type From="xml" To="System.String" /> <Type From="sql_variant" To="System.Object" /> </Language> <DbTarget From="SQL" To="SqlClient"> <Type From="bigint" To="SqlDbType.BigInt" /> <Type From="binary" To="SqlDbType.Binary" /> <Type From="bit" To="SqlDbType.Bit" /> <Type From="char" To="SqlDbType.Char" /> <Type From="datetime" To="SqlDbType.DateTime" /> <Type From="decimal" To="SqlDbType.Decimal" /> <Type From="float" To="SqlDbType.Float" /> <Type From="image" To="SqlDbType.Image" /> <Type From="int" To="SqlDbType.Int" /> <Type From="money" To="SqlDbType.Money" /> <Type From="nchar" To="SqlDbType.NChar" /> <Type From="ntext" To="SqlDbType.NText" /> <Type From="numeric" To="SqlDbType.Decimal" /> <Type From="nvarchar" To="SqlDbType.NVarChar" /> <Type From="real" To="SqlDbType.Real" /> <Type From="smalldatetime" To="SqlDbType.SmallDateTime" /> <Type From="smallint" To="SqlDbType.SmallInt" /> <Type From="smallmoney" To="SqlDbType.SmallMoney" /> <Type From="text" To="SqlDbType.Text" /> <Type From="timestamp" To="SqlDbType.Timestamp" /> <Type From="tinyint" To="SqlDbType.TinyInt" /> <Type From="uniqueidentifier" To="SqlDbType.UniqueIdentifier" /> <Type From="varbinary" To="SqlDbType.VarBinary" /> <Type From="varchar" To="SqlDbType.VarChar" /> <Type From="xml" To="SqlDbType.Xml" /> <Type From="sql_variant" To="SqlDbType.Variant" /> </DbTarget> <DbTarget From="SQLCE" To="SqlServerCe"> <Type From="bigint" To="SqlDbType.BigInt" /> <Type From="binary" To="SqlDbType.Binary" /> <Type From="bit" To="SqlDbType.Bit" /> <Type From="char" To="SqlDbType.Char" /> <Type From="datetime" To="SqlDbType.DateTime" /> <Type From="decimal" To="SqlDbType.Decimal" /> <Type From="float" To="SqlDbType.Float" /> <Type From="image" To="SqlDbType.Image" /> <Type From="int" To="SqlDbType.Int" /> <Type From="money" To="SqlDbType.Money" /> <Type From="nchar" To="SqlDbType.NChar" /> <Type From="ntext" To="SqlDbType.NText" /> <Type From="numeric" To="SqlDbType.Decimal" /> <Type From="nvarchar" To="SqlDbType.NVarChar" /> <Type From="real" To="SqlDbType.Real" /> <Type From="smalldatetime" To="SqlDbType.SmallDateTime" /> <Type From="smallint" To="SqlDbType.SmallInt" /> <Type From="smallmoney" To="SqlDbType.SmallMoney" /> <Type From="text" To="SqlDbType.Text" /> <Type From="timestamp" To="SqlDbType.Timestamp" /> <Type From="tinyint" To="SqlDbType.TinyInt" /> <Type From="uniqueidentifier" To="SqlDbType.UniqueIdentifier" /> <Type From="varbinary" To="SqlDbType.VarBinary" /> <Type From="varchar" To="SqlDbType.VarChar" /> <Type From="xml" To="SqlDbType.Xml" /> <Type From="sql_variant" To="SqlDbType.Variant" /> </DbTarget> </Languages> 读取方法 static Dictionary<string, string> list = new Dictionary<string, string>(); static void Main(string[] args) { XElement root = XElement.Load("Languages.xml"); var custs = (from c in root.Elements("DbTarget") where c.Attribute("From").Value.Equals("SQL") && c.Attribute("To").Value.Equals("SqlClient") select c).ToList(); foreach (XElement node in custs.Elements("Type")) { list.Add(node.Attribute("From").Value, node.Attribute("To").Value); } Console.ReadKey(); }
转自:http://www.cnblogs.com/jake1/archive/2013/04/25/3043664.html 我发现现在有不少博友,都反对使用EF框架,说它性能低.其实只要你用的好,性能不是问题,经过测试,它也会接近ado.net的访问了. 当然如果对EF不了解,随便乱用,确实会引来性能问题.因为EF的查询语句都是自己生成的.如果不注意,它会多次查询数据库,或用效率不高的语句去查询. 下面我就把我们在项目中遇到的问题,现我把他总结出来.以供大家参考.当然还有一些没有列出来的,希望各网友也一起提供一下,以避免大家少走弯路. 分页的时候,尽量在数据库里面去分页.在我实际中的项目,我就发现我同事由于他不了解EF属性,它的分页都是做在内存中分页.下面请看他的代码. queryToList().Skip((pageInfo.PageIndex - 1) * pageInfo.PageSize).Take(pageInfo.PageSize); 像上面的语句,他会先把数据从数据库中查出来,然后才分页. 正确的写法应如下: query.Skip((pageInfo.PageIndex - 1) * pageInfo.PageSize).Take(pageInfo.PageSize).ToList();这样才会在数据库中分页. 2.尽量禁用延迟加载,尽量使用预加载和显式加载查询. Vs 默认生成的代码,是启用了延迟加载的.这样往往是有些开发人员在不了解的情况下,进行了多次往数据库查询.如下的代码. using (SchoolContainer db = new SchoolContainer()) { var list = db.People.Where(ent => ent.PersonID < 30).ToList(); foreach (var people in list) { foreach (var ent in people.StudentGrades) { Console.WriteLine(ent.Grade); } } } 如果启用延迟加载,这样会造成多次往返数据库查询的.势必造成性能低下. 因此我们在这里应该使用预加载.代码如下: var list = db.People.Include("StudentGrades").Where(ent => ent.PersonID < 30).ToList(); 当然使用了预加载,延迟加载也不会查询多次.但怕在程序员写代码时,忘了要预加载,如果启用了延迟加载,那么就会多次查询数据库.如果不启用,那么程序员就获取不了数据,这样他就马上明白,要进行预加载了. 当然任何事情不是绝对的.如果被查询的对象,过于复杂,就要慎用预加载 3.注意事务的简短性. 在使用事务时,我们要尽量把查询语句或者其他响事务外的语句移在事务外执行.不然让一个事务的时间太长了,就容易引起资源死锁的问题.我上次就优化我一个同事的代码,他代码里就把所所有不相关的东西都放在事务里执行,这样就造成事务的时间太长,当用压力测时,马上就出现资源被锁的错误. 4.查询出来的实体,如果不考虑删除和修改,请用NoTracking 关于Notracking 的使用方法,请看. http://www.cnblogs.com/LingzhiSun/archive/2011/04/27/EF_Trick4.html 5.批量删除和修改,不要用先把实体查询出来,然后再逐个删除和修改.这样会产生大量的语句,效率肯定会低. 对于这个解决方式一是直接用sql语句执行,二是可以用自己写个扩展方法来操作.虽然有不少人反对这方法.但我个人是倾向于自己写个扩展方法.这样,有2个好处.一是给开发人员使用简单.二是方便管理. 虽然也有网友提出用ObjectStateManager.ChangeObjectState 来实现批量删除,但我是没有找到相关的批量删除方法.而且这个也经常会出异常. 6.使用已编译的查询,虽然到EF5.0, LINQ 查询是自动缓存的.但使用编译查询会比自动缓存的效率高. 使用编译查询,请参照http://msdn.microsoft.com/zh-cn/library/bb896297.aspx. 7.预生成视图, 具体操作如下: http://blogs.msdn.com/b/appfabriccat/archive/2010/08/06/isolating-performance-with-precompiled-pre-generated-views-in-the-entity-framework-4.aspx 8.还有一点,就是对于复杂的查询,我们要随时监控生成的查询语句. 毕竟EF生成的语句,往往比我们生成的语句更加复杂,这个时候我们就要考虑是否通过其他方式来提高性能. 9.更多EF性能优化,请参考http://msdn.microsoft.com/zh-cn/library/cc853327.aspx
直接上代码: static void Main(string[] args) { Database.SetInitializer<BaseDB>(null); BaseDB context = new BaseDB(); var book = new Book { ISBN = "isbn002", Title = "title004" }; context.BookEntity.Attach(book); var stateEntry = ((IObjectContextAdapter)context).ObjectContext. ObjectStateManager.GetObjectStateEntry(book); stateEntry.SetModifiedProperty("Title"); context.SaveChanges(); Console.ReadKey(); } 生成SQL exec sp_executesql N'update [dbo].[Book] set [Title] = @0 where ([ISBN] = @1) ',N'@0 nvarchar(max) ,@1 nvarchar(128)',@0=N'title004',@1=N'isbn002'
和前同事在谈论MVC局部刷新问是,在之前一般都采用Jquery拼JS来实现,不过MVC提供了强大的功能来实现这个,以后可以更简单了。 做了个小示例,备份一下。 http://files.cnblogs.com/hantianwei/MvcApp.zip
网盘现在很流行,比较好且稳定性强的个人感觉有 百度云网盘、360云盘、金山快盘、腾讯微云,我个人常用的是 这四家的,大公司安全性好,稳定性强,速度快,其它就不多说了 我这里用百度云网盘,并用VS2012新建项目进行演示 昨天晚上刚在服务器上面安装了SVN,今天在网上就学到了可以通过网盘来实现版本管理,这样还好,不用再用自已 服务器上面的带宽了。 下面介绍操作细节 1、先下载Git GUI 下载地址:http://code.google.com/p/msysgit/downloads/list 再下载百度云网盘 下载地址:http://pan.baidu.com 接下来就是安装这两个软件了,安装大家都会,不介绍2、安装完网盘后设置网盘的同步目录,因为网盘中存有几十G的文件,不可能到了一台电脑就全部同步,那样就惨 了,在网盘中新建目录"Git"。进入网盘设置界面,选择高级:如下图所示: 3、打开VS2012新建项目 GitTest,并写上一句代码。如图所示: 4、我们在项目GitTest根目录下右键,选择“Git Init Here”,我们发现目录下多了一个目录“.git”接下来我们需要修改GIT设置来排除一些文件或目录不让它们在版控制内,用记事本打开“.git”目录下 的"info"目录下面的“exclude”文件,在下面加入如下几行: bin/ obj/ *.suo *.cache,因为这里是用的VS开发,如果是其它请按需要进行修改 修改完后,我们再次右键选择“Git Gui” 进入相关界面 ,"编辑->选项" ,在全局所有版本输入用户 名Email地址后确定,我们再这里先点“缓存改动”,再输入描述后点“提交”,这里我们只是提交到本地版里面了 5、我们现在克隆一份到网盘中的Git目录下面,来实现远端版本,还在根目录下右键选择“Git Bash”,输入克隆 指令:git clone --bare . D:/htw/baiduyunpan/git/GitTest.git 6、再次“Git GUI”界面中“远端(remote)->Add” ,名字可以随便写,这里我填写“GitTest.git”,Location 填写我们的远端目录地址我的是:D:\htw\BaiDuYunPan\git\GitTest.git,点Add后提示成功 ,接下来再点" 上传" 再点“上传”提示成功 ,到此我们已经完成了远端设置。 7、接下来我们就来看一下怎么样实现异地获取版本,并实现提交版本。在桌面新建一目录“test”之后在目录下面右键选“Git Bash” 输入:git clone D:/htw/baiduyunpan/git/GitTest.git在“test”目录下面就出现在我们的项目“GitTest”打开一看里面就是我们的项目,这时我们需要重复前面第4步 里面修改“exclude”,实现过滤文件或目录打开新下下来的项目,我们把刚才的那名修改一下,再次打开“Git Gui” 界面,我们看到有+和-这里 意味着我们修改了,重复前面的操作,“缓存改动”,填写描述,提交,这里我们提交到了本地版本,我们需要提 交到远端版库,点上传再点上传,提示成功,这样就提交到远端了,当然我们可以多次提交到本地后再提交到远端的。 8、查看改动,我们在用VS建的项目目录里面打开“Git Gui” 界面,“远端(remote)->从..获取(fetch)- >GitGtest.git”提示成功,这时并没有更新我们本地版本,只是下到本地了,我们再选择“合并(merge)-> 本地合并”再点“合并”提示成功,我们到我们新建的VS里面看一下刚修改的地方是否改动了,已经改动了 ,我们再次确认一下,在项目目录右键“Git History”界面,我们看到确实实现了版本控制。 到些简单的版本控制就完成了,对于个人来说我们有了稳定和超大的存储空间来存储我们的代码并实现版本管理。
搭建SVN服务,有效的管理代码,以下三步可以快速搞定。1、安装 #yum install subversion 判断是否安装成功 #subversion -v svnserve, version 1.6.11 (r934486) 出现上面的提示,说明安装成功。 有了SVN软件后还需要建立SVN库。 #mkdir /opt/svn/repos #svnadmin create /opt/svn/repos 执行上面的命令后,自动在repos下建立多个文件, 分别是conf, db,format,hooks, locks, README.txt。 2、配置 上面的操作很简单,几个命令就搞定, 下面的操作也不难。 进入上面生成的文件夹conf下,进行配置, 有以下几个文件authz, passwd, svnserve.conf 其中authz 是权限控制,可以设置哪些用户可以访问哪些目录, passwd是设置用户和密码的, svnserve是设置svn相关的操作。2.1先设置passwd [users] # harry = harryssecret # sally = sallyssecret hello=123 用户名=密码 这样我们就建立了hello用户, 123密码 2.2 再设置权限authz [/] hello= rw 意思是hello用户对所有的目录有读写权限,当然也可以限定。 如果是自己用,就直接是读写吧。 2.3最后设定snvserv.conf anon-access = none # 使非授权用户无法访问 auth-access = write # 使授权用户有写权限 password-db = password authz-db = authz # 访问控制文件 realm = /opt/svn/repos # 认证命名空间,subversion会在认证提示里显示,并且作为凭证缓存的关键字。 采用默认配置. 以上语句都必须顶格写, 左侧不能留空格, 否则会出错. 好了,通过以上配置,你的svn就可以了。3、连接 启动svn: svnserve -d -r /opt/svn/repos 如果已经有svn在运行,可以换一个端口运行 svnserve -d -r /opt/svn/repos --listen-port 3391 这样同一台服务器可以运行多个svnserver 好了,启动成功后,就可以使用了。 建议采用TortoiseSVN, 连接地址为: svn://your server address (如果指定端口需要添加端口 :端口号) 连接后可以上传本地的文件,有效的管理你的代码。 通过以上三步,可以快速的搭建起svn