月度归档:2019年09月

单点登录CAS服务端登录页添加验证码

 原理

啰嗦一句:这年头验证码一般用来防止帐号被暴力破解,若系统走专线(也就是说放在内网),那完全没必要搞验证码

1、由于CAS使用了SpringWebFlow,所以我们想在登录页表单中增加属性就直接找\WEB-INF\login-webflow.xml

2、在第84行<view-state id="viewLoginForm">中找到表单的两个属性,我们加一个

同样该标签中会发现model=”credential”配置,所以我们就在该文件找credential对应的实体类配置

发现是在第27行设置的,其值为org.jasig.cas.authentication.UsernamePasswordCredential

这是一个用来接收前台表单参数的JavaBean,我们这里要在表单上加一个参数captcha,所以继承它就行了

3、创建com.jadyer.sso.model.UsernamePasswordCaptchaCredential extends UsernamePasswordCredential

再加上captcha属性,及其对应的setter和getter

再修改login-webflow.xml第27行credential对应实体类为com.jadyer.sso.model.UsernamePasswordCaptchaCredential

4、接下来添加校验验证码的流程

继续看,这里我们会发现表单实际的提交等动作是由authenticationViaFormAction处理的

authenticationViaFormAction是被配置在cas-servlet.xml中的第233行

我们要在原有表单处理逻辑的基础上增加验证码,所以就扩展authenticationViaFormAction

创建com.jadyer.sso.authentication.AuthenticationViaCaptchaFormAction extends AuthenticationViaFormAction

在AuthenticationViaCaptchaFormAction中增加一个validateCaptcha()方法用来校验验证码

然后将cas-servlet.xml中的authenticationViaFormAction改为新扩展的AuthenticationViaCaptchaFormAction

同样login-webflow.xml中的三处authenticationViaFormAction改为新扩展的AuthenticationViaCaptchaFormAction

5、最后把messages.properties的一些提示文字改为中文

required.username=必须输入帐号

required.password=必须输入密码

required.captcha=必须输入验证码

error.authentication.captcha.bad=验证码不正确

authenticationFailure.AccountNotFoundException=登录失败–帐号不正确

authenticationFailure.FailedLoginException=登录失败–密码不正确

authenticationFailure.UNKNOWN=未知错误

代码

本文源码下载:(下面两个地址的文件的内容,都是一样的)

Github:https://github.com/v5java/demo-cas-server-web

CSDN下载:http://download.csdn.net/detail/jadyer/8906831

下面是login-webflow.xml中的改动部分

<!-- <var name="credential" class="org.jasig.cas.authentication.UsernamePasswordCredential"/> -->
<!-- 新加的用于接收前台表单验证码字段captcha的JavaBean -->
<var name="credential" class="com.jadyer.sso.model.UsernamePasswordCaptchaCredential"/>

<view-state id="viewLoginForm" view="casLoginView" model="credential">
    <binder>
        <binding property="username"/>
        <binding property="password"/>
        <!-- 前台添加表单添加验证码字段captcha -->
        <binding property="captcha"/>
    </binder>
    <on-entry>
        <set name="viewScope.commandName" value="'credential'"/>
    </on-entry>
    <transition on="submit" bind="true" validate="true" to="validateCaptcha">
        <evaluate expression="authenticationViaCaptchaFormAction.doBind(flowRequestContext, flowScope.credential)"/>
    </transition>
</view-state>

<!-- 新添加的校验验证码 -->
<action-state id="validateCaptcha">
    <evaluate expression="authenticationViaCaptchaFormAction.validateCaptcha(flowRequestContext, flowScope.credential, messageContext)"/>
    <transition on="error" to="generateLoginTicket"/>
    <transition on="success" to="realSubmit"/>
</action-state>

<action-state id="realSubmit">
    <evaluate expression="authenticationViaCaptchaFormAction.submit(flowRequestContext, flowScope.credential, messageContext)"/>
    <transition on="warn" to="warn"/>
    <transition on="success" to="sendTicketGrantingTicket"/>
    <transition on="successWithWarnings" to="showMessages"/>
    <transition on="authenticationFailure" to="handleAuthenticationFailure"/>
    <transition on="error" to="generateLoginTicket"/>
</action-state>

下面是扩展的UsernamePasswordCaptchaCredential.java

package com.jadyer.sso.model;
import org.jasig.cas.authentication.UsernamePasswordCredential;

/**
 * 自定义的接收登录验证码的实体类
 * Created by 玄玉<https://jadyer.cn/> on 2015/07/14 16:28.
 */
public class UsernamePasswordCaptchaCredential extends UsernamePasswordCredential {
    private static final long serialVersionUID = 8317889802836113837L;
    private String captcha;
    /*-- setter和getter略 --*/
}

下面是扩展的AuthenticationViaCaptchaFormAction.java

package com.jadyer.sso.authentication;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.jasig.cas.authentication.Credential;
import org.jasig.cas.web.flow.AuthenticationViaFormAction;
import org.jasig.cas.web.support.WebUtils;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.util.StringUtils;
import org.springframework.webflow.execution.RequestContext;
import com.jadyer.sso.model.UsernamePasswordCaptchaCredential;

/**
 * 自定义的处理验证码登录逻辑的Action
 * Created by 玄玉<https://jadyer.cn/> on 2015/07/14 16:28.
 */
public class AuthenticationViaCaptchaFormAction extends AuthenticationViaFormAction {
    public final String validateCaptcha(final RequestContext context, final Credential credential, final MessageContext messageContext){
        final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
        HttpSession session = request.getSession();
        String rand = (String)session.getAttribute("rand");
        session.removeAttribute("rand");
        UsernamePasswordCaptchaCredential upc = (UsernamePasswordCaptchaCredential)credential;
        String captcha = upc.getCaptcha();
        System.out.println("获取Session验证码-->" + rand);
        System.out.println("获取表单输入验证码-->" + captcha);
        if(!StringUtils.hasText(rand) || !StringUtils.hasText(captcha)){
            messageContext.addMessage(new MessageBuilder().error().code("required.captcha").build());
            return "error";
        }
        if(captcha.equals(rand)){
            return "success";
        }
        //这段网上这么写的messageContext.addMessage(new MessageBuilder().code("required.captcha").build());
        //实际上这么写是org.springframework.binding.message.INFO级别的,这会导致前台表单无法显示这里的错误信息
        messageContext.addMessage(new MessageBuilder().error().code("error.authentication.captcha.bad").build());
        return "error";
    }
}

下面是cas-servlet.xml中的改动部分

<!--
<bean id="authenticationViaFormAction" class="org.jasig.cas.web.flow.AuthenticationViaFormAction"
        p:centralAuthenticationService-ref="centralAuthenticationService"
        p:warnCookieGenerator-ref="warnCookieGenerator"
        p:ticketRegistry-ref="ticketRegistry"/>
 -->
<!-- 新添加的用于校验验证码的Action -->
<bean id="authenticationViaCaptchaFormAction" class="com.jadyer.sso.authentication.AuthenticationViaCaptchaFormAction"
        p:centralAuthenticationService-ref="centralAuthenticationService"
        p:warnCookieGenerator-ref="warnCookieGenerator"
        p:ticketRegistry-ref="ticketRegistry"/>

下面是我的登录成功页\WEB-INF\view\jsp\jadyer\ui\casGenericSuccess.jsp

<%@ page pageEncoding="UTF-8"%>
<body style="background-color:#CBE0C9;">
    <span style="color:red; font-size:64px; font-weight:bold;">登录成功</span>
</body>

下面是我的登录页\WEB-INF\view\jsp\jadyer\ui\casLoginView.jsp

<%@ page pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

<c:set var="ctx" value="${pageContext.request.contextPath}" scope="session"/>

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>CAS单点登录系统</title>
    <link rel="icon" type="image/x-icon" href="${ctx}/favicon.ico"/>
    <script type="text/javascript" src="${ctx}/js/jquery-1.10.2.min.js"></script>
    <script type="text/javascript" src="${ctx}/js/jquery-ui-1.10.2.min.js"></script>
    <script type="text/javascript" src="${ctx}/js/cas.js"></script>
    <!--[if lt IE 9]>
        <script src="${ctx}/js/html5shiv-3.7.2.min.js" type="text/javascript"></script>
    <![endif]-->
</head>

<style>
body {background-color: #CBE0C9;}
#msg {padding:20px; margin-bottom:10px;}
#msg.errors {border:1px dotted #BB0000; color:#BB0000; padding-left:100px; background:url(${ctx}/images/error.gif) no-repeat 20px center;}
</style>

<body>
<c:if test="${not pageContext.request.secure}">
    <div id="msg" class="errors">
        <h2>Non-secure Connection</h2>
        <p>You are currently accessing CAS over a non-secure connection.  Single Sign On WILL NOT WORK.  In order to have single sign on work, you MUST log in over HTTPS.</p>
    </div>
</c:if>
<form:form method="post" commandName="${commandName}" htmlEscape="true">
    <!--
    cssClass用于指定表单元素CSS样式名,相当于HTML元素的class属性
    cssStyle用于指定表单元素样式,相当于HTML元素的style属性
    cssErrorClass用于指定表单元素发生错误时对应的样式
    path属性用于绑定表单对象的属性值,它支持级联属性,比如path="user.userName"将调用表单对象getUser.getUserName()绑定表单对象的属性值
     -->
    <form:errors path="*" id="msg" cssClass="errors" element="div" htmlEscape="false"/>
    <input type="hidden" name="lt" value="${loginTicket}"/>
    <input type="hidden" name="execution" value="${flowExecutionKey}"/>
    <input type="hidden" name="_eventId" value="submit"/>
    <table border="9">
        <tr>
            <td>
                <c:if test="${not empty sessionScope.openIdLocalId}">
                    <strong>${sessionScope.openIdLocalId}</strong>
                    <input type="hidden" name="username" value="${sessionScope.openIdLocalId}"/>
                </c:if>
                <c:if test="${empty sessionScope.openIdLocalId}">
                    <form:input tabindex="1" path="username" placeholder="帐号" htmlEscape="true" maxlength="16" size="25"/>
                </c:if>
            </td>
        </tr>
        <tr>
            <td>
                <form:password tabindex="2" path="password" placeholder="密码" htmlEscape="true" maxlength="16" size="25"/>
            </td>
        </tr>
        <tr>
            <td>
                <form:input tabindex="3" path="captcha" placeholder="验证码" htmlEscape="true" maxlength="4" size="15"/>
                <img style="cursor:pointer; vertical-align:middle;" src="captcha.jsp" onClick="this.src='captcha.jsp?time'+Math.random();">
            </td>
        </tr>
        <%--
        <tr>
            <td>
                <input type="checkbox" tabindex="3" name="warn" value="true"/>
                <label for="warn">转向其他站点前提示我</label>
            </td>
        </tr>
        --%>
        <tr>
            <td>
                <input type="submit" tabindex="3" value="登录"/>
            </td>
        </tr>
    </table>
</form:form>
</body>
</html>

最后是用到的用于生成验证码的\WebRoot\captcha.jsp

<%@ page contentType="image/jpeg; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.awt.Color"%>
<%@ page import="java.util.Random"%>
<%@ page import="java.awt.image.BufferedImage"%>
<%@ page import="java.awt.Graphics"%>
<%@ page import="java.awt.Font"%>
<%@ page import="javax.imageio.ImageIO"%>

<%--
这是一个用于生成随机验证码图片的JSP文件
这里contentType="image/jpeg"用来告诉容器:该JSP文件的输出格式为图片格式
登录网站时,通常要求输入随机生成的验证码,这是为了防止有些软件会自动生成破解密码
这些验证码一般都是通过图片显示出来的,并且图片上有很多不规则的线条或者图案来干扰,使得软件不容易识别图案上的验证码
--%>

<%!
/**
 * 定义验证码类型(1--纯数字,2--纯汉字)
 * 这里也支持数字和英文字母组合,但考虑到不好辨认,故注释了这部分代码,详见第69行
 */
int captchaType = 1;

/**
 * 生成给定范围内的随机颜色
 */
Color getRandColor(Random random, int fc, int bc){
    if(fc>255) fc = 255;
    if(bc>255) bc = 255;
    int r = fc + random.nextInt(bc-fc);
    int g = fc + random.nextInt(bc-fc);
    int b = fc + random.nextInt(bc-fc);
    return new Color(r, g, b);
}
%>

<%
//设置页面不缓存
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);

//创建随机类实例
Random random = new Random();
//定义图片尺寸
int width=60*this.captchaType, height=(this.captchaType==1)?20:30;
//创建内存图像
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
//获取图形上下文
Graphics g = image.getGraphics();
//设定背景色
g.setColor(this.getRandColor(random, 200, 250));
//设定图形的矩形坐标及尺寸
g.fillRect(0, 0, width, height);

String sRand = "";
if(this.captchaType == 1){
    //图片背景随机产生50条干扰线作为噪点
    g.setColor(this.getRandColor(random, 160, 200));
    g.setFont(new Font("Times New Roman", Font.PLAIN, 18));
    for(int i=0; i<50; i++){
        int x11 = random.nextInt(width);
        int y11 = random.nextInt(height);
        int x22 = random.nextInt(width);
        int y22 = random.nextInt(height);
        g.drawLine(x11, y11, x11+x22, y11+y22);
    }
    //取随机产生的4个数字作为验证码
    //String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    //String str = "abcdefghkmnpqrstwxyABCDEFGHJKLMNPRSTWXYZ123456789";
    for(int i=0; i<4; i++){
        //String rand = String.valueOf(str.charAt(random.nextInt(62)));
        //String rand = String.valueOf(str.charAt(random.nextInt(49)));
        String rand = String.valueOf(random.nextInt(10));
        sRand += rand;
        g.setColor(this.getRandColor(random, 10, 150));
        //将此数字画到图片上
        g.drawString(rand, 13*i+6, 16);
    }
}else{
    //设定备选汉字
    String base = "\u7684\u4e00\u4e86\u662f\u6211\u4e0d\u5728\u4eba\u4eec\u6709\u6765\u4ed6\u8fd9\u4e0a\u7740" +
                  "\u4e2a\u5730\u5230\u5927\u91cc\u8bf4\u5c31\u53bb\u5b50\u5f97\u4e5f\u548c\u90a3\u8981\u4e0b" +
                  "\u770b\u5929\u65f6\u8fc7\u51fa\u5c0f\u4e48\u8d77\u4f60\u90fd\u628a\u597d\u8fd8\u591a\u6ca1" +
                  "\u4e3a\u53c8\u53ef\u5bb6\u5b66\u53ea\u4ee5\u4e3b\u4f1a\u6837\u5e74\u60f3\u751f\u540c\u8001" +
                  "\u4e2d\u5341\u4ece\u81ea\u9762\u524d\u5934\u9053\u5b83\u540e\u7136\u8d70\u5f88\u50cf\u89c1" +
                  "\u4e24\u7528\u5979\u56fd\u52a8\u8fdb\u6210\u56de\u4ec0\u8fb9\u4f5c\u5bf9\u5f00\u800c\u5df1" +
                  "\u4e9b\u73b0\u5c71\u6c11\u5019\u7ecf\u53d1\u5de5\u5411\u4e8b\u547d\u7ed9\u957f\u6c34\u51e0" +
                  "\u4e49\u4e09\u58f0\u4e8e\u9ad8\u624b\u77e5\u7406\u773c\u5fd7\u70b9\u5fc3\u6218\u4e8c\u95ee" +
                  "\u4f46\u8eab\u65b9\u5b9e\u5403\u505a\u53eb\u5f53\u4f4f\u542c\u9769\u6253\u5462\u771f\u5168" +
                  "\u624d\u56db\u5df2\u6240\u654c\u4e4b\u6700\u5149\u4ea7\u60c5\u8def\u5206\u603b\u6761\u767d" +
                  "\u8bdd\u4e1c\u5e2d\u6b21\u4eb2\u5982\u88ab\u82b1\u53e3\u653e\u513f\u5e38\u6c14\u4e94\u7b2c" +
                  "\u4f7f\u5199\u519b\u5427\u6587\u8fd0\u518d\u679c\u600e\u5b9a\u8bb8\u5feb\u660e\u884c\u56e0" +
                  "\u522b\u98de\u5916\u6811\u7269\u6d3b\u90e8\u95e8\u65e0\u5f80\u8239\u671b\u65b0\u5e26\u961f" +
                  "\u5148\u529b\u5b8c\u5374\u7ad9\u4ee3\u5458\u673a\u66f4\u4e5d\u60a8\u6bcf\u98ce\u7ea7\u8ddf" +
                  "\u7b11\u554a\u5b69\u4e07\u5c11\u76f4\u610f\u591c\u6bd4\u9636\u8fde\u8f66\u91cd\u4fbf\u6597" +
                  "\u9a6c\u54ea\u5316\u592a\u6307\u53d8\u793e\u4f3c\u58eb\u8005\u5e72\u77f3\u6ee1\u65e5\u51b3" +
                  "\u767e\u539f\u62ff\u7fa4\u7a76\u5404\u516d\u672c\u601d\u89e3\u7acb\u6cb3\u6751\u516b\u96be" +
                  "\u65e9\u8bba\u5417\u6839\u5171\u8ba9\u76f8\u7814\u4eca\u5176\u4e66\u5750\u63a5\u5e94\u5173" +
                  "\u4fe1\u89c9\u6b65\u53cd\u5904\u8bb0\u5c06\u5343\u627e\u4e89\u9886\u6216\u5e08\u7ed3\u5757" +
                  "\u8dd1\u8c01\u8349\u8d8a\u5b57\u52a0\u811a\u7d27\u7231\u7b49\u4e60\u9635\u6015\u6708\u9752" +
                  "\u534a\u706b\u6cd5\u9898\u5efa\u8d76\u4f4d\u5531\u6d77\u4e03\u5973\u4efb\u4ef6\u611f\u51c6" +
                  "\u5f20\u56e2\u5c4b\u79bb\u8272\u8138\u7247\u79d1\u5012\u775b\u5229\u4e16\u521a\u4e14\u7531" +
                  "\u9001\u5207\u661f\u5bfc\u665a\u8868\u591f\u6574\u8ba4\u54cd\u96ea\u6d41\u672a\u573a\u8be5" +
                  "\u5e76\u5e95\u6df1\u523b\u5e73\u4f1f\u5fd9\u63d0\u786e\u8fd1\u4eae\u8f7b\u8bb2\u519c\u53e4" +
                  "\u9ed1\u544a\u754c\u62c9\u540d\u5440\u571f\u6e05\u9633\u7167\u529e\u53f2\u6539\u5386\u8f6c" +
                  "\u753b\u9020\u5634\u6b64\u6cbb\u5317\u5fc5\u670d\u96e8\u7a7f\u5185\u8bc6\u9a8c\u4f20\u4e1a" +
                  "\u83dc\u722c\u7761\u5174\u5f62\u91cf\u54b1\u89c2\u82e6\u4f53\u4f17\u901a\u51b2\u5408\u7834" +
                  "\u53cb\u5ea6\u672f\u996d\u516c\u65c1\u623f\u6781\u5357\u67aa\u8bfb\u6c99\u5c81\u7ebf\u91ce" +
                  "\u575a\u7a7a\u6536\u7b97\u81f3\u653f\u57ce\u52b3\u843d\u94b1\u7279\u56f4\u5f1f\u80dc\u6559" +
                  "\u70ed\u5c55\u5305\u6b4c\u7c7b\u6e10\u5f3a\u6570\u4e61\u547c\u6027\u97f3\u7b54\u54e5\u9645" +
                  "\u65e7\u795e\u5ea7\u7ae0\u5e2e\u5566\u53d7\u7cfb\u4ee4\u8df3\u975e\u4f55\u725b\u53d6\u5165" +
                  "\u5cb8\u6562\u6389\u5ffd\u79cd\u88c5\u9876\u6025\u6797\u505c\u606f\u53e5\u533a\u8863\u822c" +
                  "\u62a5\u53f6\u538b\u6162\u53d4\u80cc\u7ec6";
    //图片背景增加噪点
    g.setColor(this.getRandColor(random, 160, 200));
    g.setFont(new Font("Times New Roman", Font.PLAIN, 14));
    for(int i=0; i<6; i++){
        g.drawString("*********************************************", 0, 5*(i+2));
    }
    //设定验证码汉字的备选字体{"宋体", "新宋体", "黑体", "楷体", "隶书"}
    String[] fontTypes = {"\u5b8b\u4f53", "\u65b0\u5b8b\u4f53", "\u9ed1\u4f53", "\u6977\u4f53", "\u96b6\u4e66"};
    //取随机产生的4个汉字作为验证码
    for(int i=0; i<4; i++){
        int start = random.nextInt(base.length());
        String rand = base.substring(start, start+1);
        sRand += rand;
        g.setColor(this.getRandColor(random, 10, 150));
        g.setFont(new Font(fontTypes[random.nextInt(fontTypes.length)], Font.BOLD, 18+random.nextInt(4)));
        //将此汉字画到图片上
        g.drawString(rand, 24*i+10+random.nextInt(8), 24);
    }
}

//将验证码存入SESSION
session.setAttribute("rand", sRand);

//图像生效
g.dispose();
//输出图像到页面
ImageIO.write(image, "PNG", response.getOutputStream());

//若无下面两行代码,则每次请求生成验证码图片时
//尽管不会影响到图片的生成以及验证码的校验,但控制台都会滚动下面的异常
//java.lang.IllegalStateException: getOutputStream() has already been called for this response
out.clear();
out = pageContext.pushBody();
%>

效果图

 

来源:https://jadyer.cn/2015/07/16/sso-cas-login-captcha/

 

排除以war方式依赖的第三方工程中某jar或资源的方法

项目以war包方式依赖了公司统一开发平台,但平台中引入druid版本较低,业务中需要更高版本的druid,需要先排除掉平台中druid包,然后引入需要的版本,实现方式记录如下:

  • 1.先在Pom中正常排除掉低版本Jar
  • 2.在maven-war-plugin中排除掉对应的jar包
  • 3.引入所需jar包

需要说明是的第二步,之前以war包方式依赖的项目较少,排除方法不太了解,经指点要通过以下方式:

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>3.2.2</version>
        <configuration>
          <overlays>
            <overlay>
              <groupId>com.example.projects</groupId>
              <artifactId>documentedprojectdependency</artifactId>
              <excludes>
                <exclude>WEB-INF/lib/druid-1.0.9.jar</exclude>
              </excludes>
            </overlay>
          </overlays>
        </configuration>
      </plugin>
    </plugins>
  </build>

可排除引入war中的某个jar,上例中排除druid-1.0.9.jar,通过此方式还可以排除war中其他资源。
官网示例排除图片

<exclude>WEB-INF/classes/images/sampleimage-dependency.jpg</exclude>

更多详情见官网中关于overlays的说明,链接如下:

http://maven.apache.org/plugins/maven-war-plugin/overlays.html

-----

背景说明:

我现在要同时推进开发两个应用,分别是代码工程myportal和代码工程info-base。其中myportal工程的设计目标是做通用化组件,而info-base工程是具体的业务应用,下来我想让info-base工程开发只关注具体的业务应用,而门户、安全、日志等交给myportal来开发,那么如何实现并行开发而又无缝整合呢?

进行下面两步配置即可,完整配置参考附件。

1、依赖配置:

Java代码 收藏代码
<dependency>
<groupId>org.waddy</groupId>
<artifactId>myportal</artifactId>
<version>1.2.0-SNAPSHOT</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
注意需要依赖类型为war.

2、覆盖配置:

Java代码 收藏代码
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<archiveClasses>${war.archiveClasses}</archiveClasses>
<overlays>
<overlay>
<groupId>org.waddy</groupId>
<artifactId>myportal</artifactId>
<excludes>
<exclude>**/product.properties</exclude>
</excludes>
</overlay>
</overlays>
</configuration>
</plugin>
其中<excludes>是排除选项标签,将你不想引入的内容放在这里即可。

简单免费的文档中心—dokuWiki搭建指南

引言

如果说知识就是力量,那么知识库就是弹药库了。不管是一个技术型企业,还是一个热衷技术的个人,如果能有一个能够方便记录、保存以及检索的知识库,想必是极好的。如果恰好你也并没有充足的预算去购买相应的商用解决方案,那么这篇文章兴许对你有点帮助。

Dokuwiki作为一个wiki引擎,软件主体十分小巧但功能非常强大而又灵活。它语法简单,为易写性、易读性提供保障。它提供权限管理和安全策略,能够保证信息库的安全性。它又是开源的,这给熟悉PHP的开发者留下了无限的可能。它还有一个十分活跃的社区论坛,在那里能找到很多关于安装和使用的有用信息,另外,非常丰富的扩展插件也诞生于此。如果有兴趣,你也可以注册一个账号,在社区论坛里提出需求和解决别人提出的需求。
如果提供一个中小团队或者个人使用,那么Dokuwiki非常合适。

最近因为项目需要,团队需要一个轻量级的文档中心。调研了一下,方案有以下几种:

  • Confluence:最有名,最强大,最通用,最老牌,Atlassian,公司级的wiki就是这货。缺点是团队超过10人用时需要付费,而且价格不菲。:( 忍痛Pass
  • Jekyll:著名开源博客工具,大有取代WordPress的趋势,部署相对简单,插件强大,完全免费。缺点是貌似只能够通过和Git交互来新建和更新文章,没有自带的编辑页面。
  • Dokuwiki:简单,轻量,支持PHP即可使用,插件丰富,权限控制强大,完全免费,有编辑页面,不懂代码也可以使用。缺点是缺乏对Markdown的原生支持(即使装了插件,也无法完全解析Markdown)

因此对比一番,果断选择Dokuwiki作为文档中心。

安装

首先去官网下载页面下载最新版本的Dokuwiki,根据自己需要动态打包,不需要安装:

下载页面

开启web容器

笔者环境是Mac,因此下文以mac为例。其它系统参考Dokuwiki的install页面.

Mac系统由于自带了apache,所以打开即可。先输入

sudo apachectl -v

查看系统apache版本,确认apache确实存在:

apache

确定apache确实存在之后,启动apache服务器:

sudo apachectl start

怎样验证apache服务器确实启动了呢?在浏览器输入“http://localhost”,如果发现“It Works!”的字眼,则说明系统成功启动。

apache index page

如何修改服务端口? 打开Finder,进入/etc/apache2/httpd.conf目录(cmd+Shift+G),编辑httpd.conf文件,找到

Listen 80

一行,改成你想要的端口即可。

至此,web 容器启动完成。

部署

Mac下Apach的DocumentRoot目录是/Library/WebServer/Documents,将dokuwiki解压缩后的文件夹拷入。

在termial输入php -v查看php版本。如果没有,请参照PHP安装教程安装PHP

在确定PHP已经安装的前提下,修改PHP文件配置:打开/etc/apache2/httpd.conf文件,查找#LoadModule php5_module,去掉前面的注释(#号),保存,退出。 重启Apache

sudo apachectl stop
sudo apachectl start

至此部署过程完成

配置

打开浏览器,输入http://localhost/dokuwiki/install.php,进入dokuwiki的配置页面。 如果遇到页面提示说dokuwiki权限存在问题,如下图所示:

Dokuwiki存在权限问题的提示

则需要修改dokuwiki的文件夹权限,修改方法为,在terminl中输入:

sudo chmod -R 777 dokuwiki所在目录(包含dokuwiki)

权限设置好后,重新刷新浏览器页面,如果出现如下界面,说明部署dokuwiki成功:

dokuwiki

配置项根据自己需要填写即可。 install页面输入完成后,即可正式进入dokuwiki页面了。 浏览器中输入http://localhost/dokuwiki/即可正式开启Dokuwiki之旅。

Dokuwiki

迁移时可能遇到的问题

如果迁移Dokuwiki时遇到了

The datadir ('pages') at ./data/pages is not found, isn't accessible or writable.

的问题,修改dokuwiki相应目录权限即可。

如果在Ubuntu下运行时发现打开install.php显示的确实php源码,说明php运行环境没有准备好,这时请查看Ubuntu版本,并且参考Dokuwiki的install指导进行安装配置。笔者这里是Ubuntu 16.04, PHP7,因此需要安装

sudo apt-get install apache2 libapache2-mod-php7.0

下一次可以写一篇有关Dokuwiki的plugin和部署到其他系统的文章。 EOF

https://www.dokuwiki.org/dokuwiki

来源: https://cloud.tencent.com/developer/article/1337420

---------------

上面主要是mac上的安装方法等, 下面是从网络上搜索到的, 其他方法供参考

只需要一台安装了Web server的机器即可。如果访问量较小,哪怕是一台旧电脑都能胜任。
如果仅仅是想体验一下这套软件,那么你甚至可以在Dokuwiki的官方下载页面,选择下载版本的时候,勾选“Include Web-Server”的选项,这样就可以得到一个仅10MB左右的绿色版本MicroApache(仅供windows系统使用)。解压后,随意放在任何一个windows系统中,都可以直接运行以提供Web服务,哪怕放U盘里都是可以的。
如果你想要能够提供更多人访问,而且更稳定的Web服务,那么就需要安装全套的web server了。如果你是web开发或部署的专业人士,那么这一步对你来说真是再简单不过了。如果你不熟悉这个领域,也不必慌张,因为现在有一些集成的可以使用,一次安装即可完成所有相关软件的部署。
 Linux系统中推荐使用XAMPP。
 Windows系统中推荐使用WAMP,因为XAMPP只有32位版本供Windows使用。
安装好Web server就已经成功大半了。因为接下来的事情就很简单了。

下载Dokuwiki软件包,推荐使用最新的稳定版。官方下载在此:https://www.dokuwiki.org/dokuwiki
解压软件包,得到的目录放在XAMPP(假设你安装的是XAMPP)安装目录中的“htdocs”目录下。
启动web server。
用浏览器访问:http://IP地址:端口号/dokuwiki/install.php
完成Dokuwiki的初始化配置。
好了,一个崭新的wiki系统新鲜出炉。
开源即是为所欲为

开源让个性化定制更简单,大致有三种方法:

系统配置。Dokuwiki提供了大量的配置项目,关于页面布局、颜色等等相关的个性化选项。
扩展插件。Dokuwiki有庞大的插件库以供选择,选择合适的插件可以满足更广更深度的定制化需求。
源码定制。熟悉PHP语言开发者的专属技能包,为所欲为吧。
4?怎么还会有第4种方法?如果你看了第3条,有点小情绪,那么这一条就是来安慰你的。找到你的dokuwiki安装目录。
a) dokuwiki/lib/tpl/dokuwiki/images/
i. 替换logo.png来更换界面的Logo。
ii. 替换favicon.ico来更换浏览器标签上的图标。
b) dokuwiki/lib/tpl/dokuwiki/tpl_footer.php
i. 修改页脚的代码都在这里,超链接和图片都能改。
c) 增加/删除上传文件类型
i. 用户自定义类型:dokuwiki/conf/mime.local.conf
ii. 系统默认支持类型:dokuwiki/conf/mime.conf
d) 修改时区:dokuwiki/inc/init.php, date_default_timezone_set(‘PRC’)
e) Linux环境中修改上传文件大小限制
i. /etc/php.ini文件中的upload_max_filesize
f) Windows环境中尽管会提示上传文件过大,但好像实际并不会限制上传。
如果你是个充满好奇心且动手能力还过得去的人,这时候再看看你的wiki界面,是不是有了大变样。如果还不能达到满意,也不必急于一时,因为这些并不影响Dokuwiki的使用,可以放在以后慢慢来。
功能很强大,使用很简单

官方使用手册,您最好的朋友:https://www.dokuwiki.org/manual
Dokuwiki本身的功能再加上数量庞大的插件,可以说非常强大了。那么问题来了,单看每一个功能都很好很强大,上手使用也很简单,不管是官方文档还是插件说明,都非常详尽完备。但面对这一切,好像有点迷茫,甚至有点慌。为了缓解这尴尬的场面,让我们来划一划重点。了解了这些,你就可以从容的构建自己的wiki知识库,至于其他的方面,可以慢慢补充。但如果重点没把握好,以后要面对的可能就不是慢慢补充了,也许是安全隐患,或者是推倒重来。

给每个wiki使用者创建一个账号,并将其添加到相应的群组当中。

给页面设置适当的名字空间,不同的部门或知识模块应该有不同的名字空间,不同的名字空间中页面可以重名。

按名字空间设置访问权限。给不同的群组适当的访问权限设定。
关于插件,Dokuwiki发行版本集成了一些好用且常用的,另外还有一些也推荐使用,真的是不用不知道,一用离不掉,谁用谁知道,不是开玩笑。

Columns Plugin:这个插件可以容易的让一个页面有多个列,这样页面空间利用的就更加紧凑,设计得当的话也会更加美观。

imgpaste plugin:更容易的上传图片。

Move plugin:重命名页面的同时,也可以修改它的名字空间,而且同时自动修改所有引用此页面的超链接。

ToDo:如果有个页面用来记录项目的开发目标、相关时限以及进展状态,那么这个插件也许能帮到你,值得关注。

Bookcreator Plugin:导出wiki的页面到文件(pdf或者text)。需要配合其他插件使用。
a) 导出pdf文件需要同时安装”dw2pkf”插件。
b) 导出odt文件需要同时安装“OpenDocument Export”插件。
c) 导出text文件需要同时安装“text”插件。
d) 详见:https://www.dokuwiki.org/plugin:bookcreator

为什么一个锐角确定了三角函数值就确定了

为什么一个锐角确定了,三角函数值就确定了,今天老师告诉你答案

为什么一个锐角确定了,三角函数值就确定了,今天老师告诉你答案

很多同学在学数学的时候基础知识掌握不扎实,这时候家长采取的措施是打骂或让其回去看书的措施,那老师和家长有没有分析孩子们掌握不扎实的原因,分析原因才是最主要的,其实有很大一部分原因是孩子对基础知识不理解,如果整天是死记硬背得到的知识,他们很快就会忘记,并且他们也不敢兴趣,所以一定要把基础知识的原理搞明白,自然而然孩子的基础知识就很快记住了,并且基础知识也扎实了,他们也乐意学数学。今天老师就来给你们讲一个基础知识的原理:为什么一个锐角确定了,三角函数值就确定了,希望今天老师总结的知识对你有用。

首先我们来看一下下面的几个实例,得出原理。

通过上面的实例,我们知道当一个锐角是30度或45度时,它的对边比斜边一定是一个定值。那么对于锐角A的每一个确定的值,其对边与斜边的比值也是惟一确定的吗?接下来我们继续来研究。

通过上图中的研究我们知道:在直角三角形中,当锐角A的度数一定时,不管三角形的大小如何,∠A的对边与斜边的比也是一个固定值.在这里我们还引入了一个新的定义:正弦。在Rt△ABC中 ∠C=90 ,我们把锐角A的对边与斜边的比叫做∠ A的正弦,记作sinA。

刚才我们研究了对边比斜边,那接下来我们继续研究在直角三角形ABC中,∠C=90°,当∠A确定时,∠A的对边与斜边的比随之确定,此时,邻边与斜边的比及对边与邻边的比是否随之确定呢?为什么?请看下面的讲解:

通过上面的讲解,我们知道:在Rt△ABC中,在直角三角形中,当锐角A的度数一定时,不管三角形的大小如何, ∠A的∠A的邻边与斜边的比、 ∠A的对边与邻边的比是一个固定值.同时接下来,我们要引入新的两个定义:正切和余弦。

通过上面的讲解大家明白了为什么一个锐角确定了,三角函数值就确定了吧!其实一般的结论是根据相似得到的。请同学们认真看上面的讲解,把这个基础点理解,这对以后高中我们继续研究三角函数有着很重要的作用,讲到这里同学们可以课下做一下笔记,把证明三角函数的确定性整理一下,那这个知识点就明白了。最后希望老师今天分享的这个知识点对爱研究的你有所帮助。

 

来源: http://baijiahao.baidu.com/s?id=1640264048194315416

边边边定理证明

边边边定理

本词条由“科普中国”科学百科词条编写与应用工作项目 审核 。
边边边定理,简称SSS,是平面几何中的重要定理之一。边边边定理的内容是:有三边对应相等的两个三角形全等。它用于证明两个三角形全等。该定理最早由欧几里得证明。
此外,全等三角形判定定理还有 “边角边"(SAS)、 “角边角"(ASA)、“角角边”(AAS)等。
中文名
边边边定理
外文名
Theorem of edges and edges
作    用
证明两个三角形全等
解    释
有三边对应相等的两个三角形全等
简    称
SSS
证明者
欧几里德

定义

边边边定理,简称SSS,是平面几何中的重要定理之一。边边边定理的内容是:有三边对应相等的两个三角形全等。它用于证明两个三角形全等。该定理最早由欧几里得证明。
《义务教育数学课程标准》(2011 版)将判定三角形全等中的“边边边”列为基本事实,即作为证明推理的出发点,并不要求证明。同时,为了帮助学生发现并理解这条基本事实的合理性,现行教材大都沿袭传统做法,即通过尺规作图,根据已知三边的长度作出一个三角形,再将作出的三角形与原三角形放在一起,看是否重合来得到“边边边”的合理性。以上做法是实验几何的方法,并没有证明“边边边”的成立。

证明方法

编辑

欧几里得的《几何原本》,其中命题 8 证明了三边分别相等的两个三角形,则夹在等边中间的角也相等。书中采用的是反证法,并且借助书中另外一条定理(即命题 7)来完成证明。有了这条定理做保证,就可以继续借助“边角边”完成“边边边”的证明。

方法1

设:在三角形ABC和三角形DEF中,AB等于DE,AC等于DF,即AB是

DE的对应边,AC是DF的对应边。BC等于EF。

那么说:三角形ABC全等于三角形DEF。
将点B替换成点E,线段BC替线段EF,因为BC等于EF,所以点C与点F重合,那么BA、AC分别于ED、DF重合。
如果底边BC与底边EF重合,而BA、AC两边与ED、DF两边不重合,形成了新的两边与EG、FG重合,那么从一条线段的两个末端引出的两条线段相交于一点,同一条线段的两个末端引出的另外两条线段交与另一点,两组对应边不能相等。所以:假设不能成立。
所以三角形ABC和三角形DEF可以重合,所以三角形ABC和三角形DEF全等。

方法2

一般证明“边边边”常采用以下方法

图1图1

在△ABC和△A'B'C'中,已知AB=A'B',AC=A'C',BC=B'C'。求证:△ABC ≌ △ A'B'C'。
证明:将△ABC通过平移、旋转和轴反射,使BC的像与B'C'重合(并使A的像与A'在BC的两旁),连接AA',得到图1。
因为AB=A'B',AC=A'C',
在△ABC和△A‘B'C'中,AB=A‘B',角BAC=角B'A'C',AC=A'C',所以△ABC ≌ △ A'B'C' ( SAS )。
由此得到以下基本事实:三边对应相等的两个三角形全等。注意到上述证明过程中,借助了“等腰三角形等边对等角”这一定理,尽管课标对“边边边”不作证明要求,但从几何定理的编排顺序以及数学教材的严谨性来讲,在呈现“边边边”这一基本事实之前,最好先证明等腰三角形的相关性质,再介绍“边边边”,那么一切将顺理成章。

定理的证明

借助“边角边”证明“等边对等角”:
(1)欧几里得的《几何原本》的命题 5 也是证明等腰三角形“等边对等角”。已知AB=AC,延长AB到D,延长AC到E,使AD=AE。由《几何原本》命题 4 的“边角边”,可证明△ADC≌△AEB,从而BE=CD,角BDC=角BEC,又BD=CE,再用“边角边”可证得△DBC≌△ECB,进 而得 到角DBC=角ECB,于是角ABC=角ACB。
这个证明过程要用到两次全等证明,这对于初学者来说很难。因此该定理戏称为“笨蛋的难关(Asses' Bridge)”,照原文直译即“驴桥”,意思是学完该定理的证明,学习者就基本掌握证明的方法了。
(2)还有一种证明方法是证明“自己与自己全等”。如图 2,

图2图2

已知△ABC,AB=AC。因为AB=AC,角A=角A,AC=AB,所以△ABC≌△ACB,所以角B=角C。这种证明方法巧妙,但一般的人很难接受这种证明方式。

以上这些证明方式都是利用“边角边”完成证明,也就是说教材可以先讲“边角边”,再证明等腰三角形的相关性质,接着就可以推出“边边边”了。考虑到等腰三角形的知识点比较多(含性质和判定),从教材编写的角度考虑,单独将“等腰三角形”做一小节是合适的 [1]
词条图册更多图册
词条图片
词条图片(5)