数据学院
外观模式 外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。 这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。 介绍 意图: 为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 主要解决: 降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。 何时使用: 1、客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可。 2、定义系统的入口。 如何解决: 客户端不与系统耦合,外观类与系统耦合。 关键代码: 在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。 应用实例: 1、去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。 2、JAVA 的三层开发模式。 优点: 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性。 缺点: 不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。 使用场景: 1、为复杂的模块或子系统提供外界访问的模块。 2、子系统相对独立。 3、预防低水平人员带来的风险。 注意事项: 在层次化结构中,可以使用外观模式定义系统中每一层的入口。 实现 写出一个编译器工作的几个阶段 class Scanner { public: void Scan() { cout<<"词法分析"<
项目管理
2018-06-03 21:42:06
在 Confluence 的设置安装向导中,将会指导你 Confluence 如何连接到你的数据库。请确定选择 " My own database "。 使用 JDBC 连接(默认) JDBC 是推荐的连接你的 Confluence 到数据库中的方式。 Confluence 的安装向导将会提供给你下面 2 种连接选项: 简单 —— 这个是最简单的方式来连接你的 Confluence 到数据库中。 通过连接字符串 —— 使用这个选项,你需要配置特定的参数同时你还需要具有数据库连接方面的一些只是来构建正确的连接 URL。 基于你设置的类型,你需要提供下面的一些参数。 简单(Simple) 主机名(Hostname) T你数据库服务器使用的主机名或者 IP 地址。 简单(Simple) 端口( Port) 这个是 MySQL 端口。如果你在安装 PostgreSQL 的时候没有修改的话,默认端口是 5432 . 简单(Simple) 数据库名(Database name) 这是你 Confluence 数据库的名称,按照上面的例子,名称为 confluence 通过连接字符串(By connection string) 数据库 URL(Database URL) 数据库 URL 使用的下面的格式: jdbc:postgresql://:/ 例如: jdbc: postgresql://localhost:5432/confluence 如果你需要使用 SSL 连接到数据库, ssl=true 参数将会提供到数据库 URL 中。例如: jdbc: postgresql://localhost:5432/confluence?ssl=true 同时需要(Both) 同时需要(Both) 用户名(Username) 用户密码(Password) 你连接数据库需要使用的用户名,在上面的示例中,用户名是 confluenceuser 。 你连接数据库需要使用的用户名密码。 使用 JNDI 数据源 如果你希望使用 JDNI数据源,请参考 Configuring a datasource connection 中的内容来进行配置。你需要在启动 Confluence 之前进行配置,在设置向导中,Confluence 只使用在你 Tomcat 配置中提供的选项。 https://www.cwiki.us/display/CONFLUENCEWIKI/Database+Setup+for+PostgreSQL
项目管理
2018-06-02 01:08:00
【来源:OSCHINA】RobotFramework之Process
Process Library version: 3.0.4 Library scope: global Named arguments: supported Introduction Robot Framework test library for running processes. This library utilizes Python's subprocess module and its Popen class. The library has following main usages: Running processes in system and waiting for their completion using Run Process keyword. Starting processes on background using Start Process . Waiting started process to complete using Wait For Process or stopping them with Terminate Process or Terminate All Processes . This library is new in Robot Framework 2.8. Table of contents Specifying command and arguments Process configuration Active process Result object Boolean arguments Example Shortcuts Keywords Specifying command and arguments Both Run Process and Start Process accept the command to execute and all arguments passed to the command as separate arguments. This makes usage convenient and also allows these keywords to automatically escape possible spaces and other special characters in commands and arguments. Notice that if a command accepts options that themselves accept values, these options and their values must be given as separate arguments. When running processes in shell , it is also possible to give the whole command to execute as a single string. The command can then contain multiple commands to be run together. When using this approach, the caller is responsible on escaping. Examples: Run Process Run Process Run Process ${tools}${/}prog.py java prog.py "one arg" && tool.sh argument -jar shell=yes second arg with spaces ${jars}${/}example.jar cwd=${tools} --option value Starting from Robot Framework 2.8.6, possible non-string arguments are converted to strings automatically. Process configuration Run Process and Start Process keywords can be configured using optional **configuration keyword arguments. Configuration arguments must be given after other arguments passed to these keywords and must use syntax like name=value . Available configuration arguments are listed below and discussed further in sections afterwards. Name Explanation shell Specifies whether to run the command in shell or not. cwd Specifies the working directory. env Specifies environment variables given to the process. env: Overrides the named environment variable(s) only. stdout Path of a file where to write standard output. stderr Path of a file where to write standard error. output_encoding alias Encoding to use when reading command outputs. Alias given to the process. Note that because **configuration is passed using name=value syntax, possible equal signs in other arguments passed to Run Process and Start Process must be escaped with a backslash like name\=value . See Run Process for an example. Running processes in shell The shell argument specifies whether to run the process in a shell or not. By default shell is not used, which means that shell specific commands, like copy and dir on Windows, are not available. You can, however, run shell scripts and batch files without using a shell. Giving the shell argument any non-false value, such as shell=True , changes the program to be executed in a shell. It allows using the shell capabilities, but can also make the process invocation operating system dependent. Having a shell between the actually started process and this library can also interfere communication with the process such as stopping it and reading its outputs. Because of these problems, it is recommended to use the shell only when absolutely necessary. When using a shell it is possible to give the whole command to execute as a single string. See Specifying command and arguments section for examples and more details in general. Current working directory By default the child process will be executed in the same directory as the parent process, the process running tests, is executed. This can be changed by giving an alternative location using the cwd argument. Forward slashes in the given path are automatically converted to backslashes on Windows. Standard output and error streams , when redirected to files, are also relative to the current working directory possibly set using the cwd argument. Example: Run Process prog.exe cwd=${ROOT}/directory stdout=stdout.txt Environment variables By default the child process will get a copy of the parent process's environment variables. The env argument can be used to give the child a custom environment as a Python dictionary. If there is a need to specify only certain environment variable, it is possible to use the env:= format to set or override only that named variables. It is also possible to use these two approaches together. Examples: Run Process Run Process Run Process program program program env=${environ} env:http_proxy=10.144.1.10:8080 env=${environ} env:PATH=%{PATH}${:}${PROGDIR} env:EXTRA=value Standard output and error streams By default processes are run so that their standard output and standard error streams are kept in the memory. This works fine normally, but if there is a lot of output, the output buffers may get full and the program can hang. Additionally on Jython, everything written to these in-memory buffers can be lost if the process is terminated. To avoid the above mentioned problems, it is possible to use stdout and stderr arguments to specify files on the file system where to redirect the outputs. This can also be useful if other processes or other keywords need to read or manipulate the outputs somehow. Given stdout and stderr paths are relative to the current working directory . Forward slashes in the given paths are automatically converted to backslashes on Windows. As a special feature, it is possible to redirect the standard error to the standard output by using stderr=STDOUT . Regardless are outputs redirected to files or not, they are accessible through the result object returned when the process ends. Commands are expected to write outputs using the console encoding, but output encoding can be configured using the output_encoding argument if needed. Examples: ${result} = Log Many ${result} = Log Run Process stdout: ${result.stdout} Run Process all output: ${result.stdout} program stderr: ${result.stderr} program stdout=${TEMPDIR}/stdout.txt stderr=STDOUT stderr=${TEMPDIR}/stderr.txt Note that the created output files are not automatically removed after the test run. The user is responsible to remove them if needed. Output encoding Executed commands are, by default, expected to write outputs to the standard output and error streams using the encoding used by the system console. If the command uses some other encoding, that can be configured using the output_encoding argument. This is especially useful on Windows where the console uses a different encoding than rest of the system, and many commands use the general system encoding instead of the console encoding. The value used with the output_encoding argument must be a valid encoding and must match the encoding actually used by the command. As a convenience, it is possible to use strings CONSOLE and SYSTEM to specify that the console or system encoding is used, respectively. If produced outputs use different encoding then configured, values got through the result object will be invalid. Examples: Start Process Run Process program program output_encoding=UTF-8 stdout=${path} output_encoding=SYSTEM The support to set output encoding is new in Robot Framework 3.0. Alias A custom name given to the process that can be used when selecting the active process . Examples: Start Process Run Process program python alias=example -c print 'hello' alias=hello Active process The test library keeps record which of the started processes is currently active. By default it is latest process started with Start Process , but Switch Process can be used to select a different one. Using Run Process does not affect the active process. The keywords that operate on started processes will use the active process by default, but it is possible to explicitly select a different process using the handle argument. The handle can be the identifier returned by Start Process or an alias explicitly given to Start Process or Run Process . Result object Run Process , Wait For Process and Terminate Process keywords return a result object that contains information about the process execution as its attributes. The same result object, or some of its attributes, can also be get using Get Process Result keyword. Attributes available in the object are documented in the table below. Attribute Explanation rc Return code of the process as an integer. stdout Contents of the standard output stream. stderr Contents of the standard error stream. stdout_path stderr_path Path where stdout was redirected or None if not redirected. Path where stderr was redirected or None if not redirected. Example: ${result} = Run Process program Should Be Equal As Integers ${result.rc} 0 Should Match ${result.stdout} Some t?xt* Should Be Empty ${result.stderr} ${stdout} = Get File ${result.stdout_path} Should Be Equal File Should Be Empty ${stdout} ${result.stderr_path} ${result.stdout} Boolean arguments Some keywords accept arguments that are handled as Boolean values true or false. If such an argument is given as a string, it is considered false if it is either an empty string or case-insensitively equal to false , none or no . Other strings are considered true regardless their value, and other argument types are tested using the same rules as in Python . True examples: Terminate Process kill=True # Strings are generally true. Terminate Process kill=yes # Same as the above. Terminate Process Terminate Process kill=${TRUE} kill=${42} # Python True is true. # Numbers other than 0 are true. False examples: Terminate Process kill=False # String false is false. Terminate Process kill=no # Also string no is false. Terminate Process Terminate Process kill=${EMPTY} kill=${FALSE} # Empty string is false. # Python False is false. Prior to Robot Framework 2.9, all non-empty strings, including false and no , were considered to be true. Considering none false is new in Robot Framework 3.0.3. Example *** Settings *** Library Process Suite Teardown Terminate All Processes kill=True *** Test Cases *** Example Start Process program arg1 arg2 alias=First ${handle} = Start Process command.sh arg | command2.sh shell=True cwd=/path ${result} = Run Process ${CURDIR}/script.py Should Not Contain ${result.stdout} FAIL Terminate Process ${handle} ${result} = Wait For Process First Should Be Equal As Integers ${result.rc} 0 Shortcuts Get Process Id · Get Process Object · Get Process Result · Is Process Running · Join Command Line · Process Should Be Running · Process Should Be Stopped · Run Process · Send Signal To Process · Split Command Line · Start Process · Switch Process · Terminate All Processes · Terminate Process · Wait For Process Keywords Keyword Arguments Documentation Get Process Id handle=None Returns the process ID (pid) of the process as an integer. If handle is not given, uses the current active process . Notice that the pid is not the same as the handle returned by Start Process that is used internally by this library. Get Process Object handle=None Return the underlying subprocess.Popen object. If handle is not given, uses the current active process . Get Process Result handle=None, rc=False,stdout=False, stderr=False,stdout_path=False,stderr_path=False Returns the specified result object or some of its attributes. The given handle specifies the process whose results should be returned. If no handle is given, results of the current active process are returned. In either case, the process must have been finishes before this keyword can be used. In practice this means that processes started with Start Process must be finished either with Wait For Process or Terminate Process before using this keyword. If no other arguments than the optional handle are given, a whole result object is returned. If one or more of the other arguments are given any true value, only the specified attributes of the result object are returned. These attributes are always returned in the same order as arguments are specified in the keyword signature. See Boolean arguments section for more details about true and false values. Examples: Run Process python -c print 'Hello, world!' alias=myproc # Get result object ${result} = Get Process Result myproc Should Be Equal ${result.rc} ${0} Should Be Equal ${result.stdout} Hello, world! Should Be Empty ${result.stderr} # Get one attribute ${stdout} = Get Process Result myproc stdout=true Should Be Equal # Multiple attributes ${stdout} Should Be Equal Should Be Empty ${stdout} ${stderr} = ${stdout} ${stderr} Hello, world! Get Process Result Hello, world! myproc stdout=yes stderr=yes Although getting results of a previously executed process can be handy in general, the main use case for this keyword is returning results over the remote library interface. The remote interface does not support returning the whole result object, but individual attributes can be returned without problems. New in Robot Framework 2.8.2. Is Process Running handle=None Checks is the process running or not. If handle is not given, uses the current active process . Returns True if the process is still running and False otherwise. Join Command Line *args Joins arguments into one command line string. In resulting command line string arguments are delimited with a space, arguments containing spaces are surrounded with quotes, and possible quotes are escaped with a backslash. If this keyword is given only one argument and that is a list like object, then the values of that list are joined instead. Example: ${cmd} = Should Be Equal Join Command Line ${cmd} --option --option "value with spaces" value with spaces New in Robot Framework 2.9.2. Process Should Be Running handle=None,error_message=Process is not running. Verifies that the process is running. If handle is not given, uses the current active process . Fails if the process has stopped. Process Should Be Stopped handle=None,error_message=Process is running. Verifies that the process is not running. If handle is not given, uses the current active process . Fails if the process is still running. Run Process command, *arguments,**configuration Runs a process and waits for it to complete. command and *arguments specify the command to execute and arguments passed to it. See Specifying command and arguments for more details. **configuration contains additional configuration related to starting processes and waiting for them to finish. See Process configuration for more details about configuration related to starting processes. Configuration related to waiting for processes consists of timeout and on_timeout arguments that have same semantics as with Wait For Process keyword. By default there is no timeout, and if timeout is defined the default action on timeout is terminate . Returns a result object containing information about the execution. Note that possible equal signs in *arguments must be escaped with a backslash (e.g. name\=value ) to avoid them to be passed in as **configuration . Examples: ${result} = Run Process python -c print 'Hello, world!' Should Be Equal ${result} = ${result} = ${result} = ${result.stdout} Run Process Run Process Run Process Hello, world! ${command} ${command} java -Dname\=value Example stderr=STDOUT timeout=1min shell=True timeout=10s on_timeout=continue cwd=${EXAMPLE} This keyword does not change the active process . timeout and on_timeout arguments are new in Robot Framework 2.8.4. Send Signal To Process signal, handle=None,group=False Sends the given signal to the specified process. If handle is not given, uses the current active process . Signal can be specified either as an integer as a signal name. In the latter case it is possible to give the name both with or without SIG prefix, but names are case-sensitive. For example, all the examples below send signal INT (2) : Send Signal To Process Send Signal To Process Send Signal To Process 2 INT SIGINT myproc # Send to active process # Send to named process This keyword is only supported on Unix-like machines, not on Windows. What signals are supported depends on the system. For a list of existing signals on your system, see the Unix man pages related to signal handling (typically man signal or man 7 signal ). By default sends the signal only to the parent process, not to possible child processes started by it. Notice that when running processes in shell , the shell is the parent process and it depends on the system does the shell propagate the signal to the actual started process. To send the signal to the whole process group, group argument can be set to any true value (see Boolean arguments ). This is not supported by Jython, however. New in Robot Framework 2.8.2. Support for group argument is new in Robot Framework 2.8.5. Split Command Line args, escaping=False Splits command line string into a list of arguments. String is split from spaces, but argument surrounded in quotes may contain spaces in them. If escaping is given a true value, then backslash is treated as an escape character. It can escape unquoted spaces, quotes inside quotes, and so on, but it also requires using double backslashes when using Windows paths. Examples: @{cmd} = Should Be True Split Command Line $cmd == ['--option', 'value with spaces'] --option "value with spaces" New in Robot Framework 2.9.2. Start Process command, *arguments,**configuration Starts a new process on background. See Specifying command and arguments and Process configuration for more information about the arguments, and Run Process keyword for related examples. Makes the started process new active process . Returns an identifier that can be used as a handle to activate the started process if needed. Starting from Robot Framework 2.8.5, processes are started so that they create a new process group. This allows sending signals to and terminating also possible child processes. This is not supported by Jython in general nor by Python versions prior to 2.7 on Windows. Switch Process handle Makes the specified process the current active process . The handle can be an identifier returned by Start Process or the alias given to it explicitly. Example: Start Process prog1 alias=process1 Start Process prog2 alias=process2 # currently active process is process2 Switch Process # now active process is process1 process1 Terminate All Processes kill=False Terminates all still running processes started by this library. This keyword can be used in suite teardown or elsewhere to make sure that all processes are stopped, By default tries to terminate processes gracefully, but can be configured to forcefully kill them immediately. See Terminate Process that this keyword uses internally for more details. Terminate Process Wait For Process handle=None, kill=False handle=None, timeout=None,on_timeout=continue Stops the process gracefully or forcefully. If handle is not given, uses the current active process . By default first tries to stop the process gracefully. If the process does not stop in 30 seconds, or kill argument is given a true value, (see Boolean arguments ) kills the process forcefully. Stops also all the child processes of the originally started process. Waits for the process to stop after terminating it. Returns a result object containing information about the execution similarly as Wait For Process . On Unix-like machines graceful termination is done using TERM (15) signal and killing using KILL (9) . Use Send Signal To Process instead if you just want to send either of these signals without waiting for the process to stop. On Windows graceful termination is done using CTRL_BREAK_EVENT event and killing using Win32 API function TerminateProcess() . Examples: ${result} = Should Be Equal As Integers Terminate Process Terminate Process ${result.rc} myproc -15 kill=true # On Unixes Limitations: Graceful termination is not supported on Windows by Jython nor by Python versions prior to 2.7. Process is killed instead. Stopping the whole process group is not supported by Jython at all nor by Python versions prior to 2.7 on Windows. On Windows forceful kill only stops the main process, not possible child processes. Automatically killing the process if termination fails as well as returning a result object are new features in Robot Framework 2.8.2. Terminating also possible child processes, including using CTRL_BREAK_EVENT on Windows, is new in Robot Framework 2.8.5. Waits for the process to complete or to reach the given timeout. The process to wait for must have been started earlier with Start Process . If handle is not given, uses the current active process . timeout defines the maximum time to wait for the process. It can be given in various time formats supported by Robot Framework, for example, 42 , 42 s , or 1 minute 30 seconds . on_timeout defines what to do if the timeout occurs. Possible values and corresponding actions are explained in the table below. Notice that reaching the timeout never fails the test. Value Action continue The process is left running (default). terminate kill The process is gracefully terminated. The process is forcefully stopped. See Terminate Process keyword for more details how processes are terminated and killed. If the process ends before the timeout or it is terminated or killed, this keyword returns a result object containing information about the execution. If the process is left running, Python None is returned instead. Examples: # Process ends cleanly ${result} = Wait For Process example Process Should Be Stopped example Should Be Equal As Integers ${result.rc} 0 # Process does not end ${result} = Wait For Process timeout=42 secs Process Should Be Running Should Be Equal ${result} ${NONE} # Kill non-ending process ${result} = Process Should Be Stopped Should Be Equal As Integers Wait For Process ${result.rc} timeout=1min 30s -9 on_timeout=kill timeout and on_timeout are new in Robot Framework 2.8.2. Altogether 15 keywords. Generated by Libdoc on 2018-04-25 23:41:29. 处理 图书馆版本: 3.0.4 图书馆范围: 全球 命名参数: 支持的 介绍 用于运行进程的Robot Framework测试库。 该库使用Python的 子进程 模块及其 Popen 类。 该图书馆有以下主要用途: 使用 Run Process 关键字在系统中运行进程并等待其完成。 使用“ 启动过程” 在后台 启动进程 。 等待启动过程以完成使用 等待进程 或使用 终止进程 或 终止所有进程 停止它们。 这个库是Robot Framework 2.8中的新增功能。 目录 指定命令和参数 流程配置 积极的过程 结果对象 布尔参数 例 快捷键 关键词 指定命令和参数 无论 运行过程 和 启动过程 接受执行命令,并传递给命令作为独立参数,所有参数。这使得使用方便,并且还允许这些关键字自动转义命令和参数中的可能空格和其他特殊字符。请注意,如果命令接受自己接受值的选项,则必须将这些选项及其值作为单独的参数给出。 当 运行在壳进程 ,它也可以给整个命令来执行作为单个字符串。然后,该命令可以包含多个要一起运行的命令。使用此方法时,调用者负责转义。 例子: 运行流程 运行流程 运行流程 $ {工具} $ {/} prog.py java的 prog.py“one arg”&& tool.sh 论据 -罐 壳= YES 第二个arg有空格 $ {罐} $ {/} example.jar CWD = $ {}工具 - 选项 值 从Robot Framework 2.8.6开始,可能的非字符串参数会自动转换为字符串。 流程配置 可以使用可选的关键字参数配置 Run Process 和 Start Process 关键字 **configuration 。必须在传递给这些关键字的其他参数之后给出配置参数,并且必须使用类似语法 name=value 。下面列出了可用的配置参数,然后在后面的章节中进一步讨论。 名称 说明 贝壳 指定是否在shell中运行命令。 CWD 指定工作目录。 ENV 指定为进程指定的环境变量。 ENV:<名称> 仅覆盖指定的环境变量。 标准输出 文件的路径写入标准输出的位置。 标准错误 文件的路径在哪里写标准错误。 output_encoding 别号 在读取命令输出时使用的编码。 别名给予该过程。 请注意,因为 **configuration 使用 name=value 语法传递,所以传递给 Run Process 和 Start Process的 其他参数中的可能等号必须使用反斜杠转义 name\=value 。有关示例,请参阅 运行过程 。 在shell中运行进程 该 shell 参数指定是否在外壳或无法运行的过程。默认情况下,外壳没有使用,这意味着外壳特定命令,就像 copy 和 dir 在Windows,不可用。但是,您可以在不使用shell的情况下运行shell脚本和批处理文件。 为 shell 参数赋予任何非假值,例如 shell=True ,更改要在shell中执行的程序。它允许使用shell功能,但也可以使进程调用操作系统依赖。在实际启动的进程和此库之间具有shell也可能干扰与进程的通信,例如停止它并读取其输出。由于这些问题,建议仅在绝对必要时才使用shell。 使用shell时,可以将整个命令作为单个字符串执行。有关示例和更多详细信息,请参阅 指定命令和参数 部分。 当前的工作目录 默认情况下,子进程将在与父进程相同的目录中执行,执行运行测试的进程。这可以通过使用 cwd 参数提供替代位置来更改。在给定路径中的正斜杠自动转换为Windows上的反斜杠。 重定向到文件时, 标准输出和错误流 也与可能使用 cwd 参数设置的当前工作目录相关。 例: 运行流程 prog.exe CWD = $ {ROOT} /目录 标准输出= stdout.txt 环境变量 默认情况下,子进程将获取父进程的环境变量的副本。该 env 参数可用于为子项提供自定义环境作为Python字典。如果只需要指定某个环境变量,则可以使用该 env:= 格式来设置或仅覆盖该命名变量。也可以将这两种方法结合使用。 例子: 运行流程 运行流程 运行流程 程序 程序 程序 ENV = $ {} ENVIRON ENV:HTTP_PROXY = 10.144.1.10:8080 ENV = $ {} ENVIRON ENV:PATH =%{PATH} $ {:} $ {} PROGDIR ENV:EXTRA =值 标准输出和错误流 默认情况下,运行进程以使其标准输出和标准错误流保留在内存中。这通常工作正常,但如果有很多输出,输出缓冲区可能会满,程序可能会挂起。此外,在Jython上,如果进程终止,写入这些内存缓冲区的所有内容都可能会丢失。 为了避免上述问题,可以使用 stdout 和 stderr 参数来指定文件系统上重定向输出的文件。如果其他进程或其他关键字需要以某种方式读取或操作输出,这也很有用。 给定 stdout 和 stderr 路径相对于 当前工作目录 。在给定路径中的正斜杠自动转换为Windows上的反斜杠。 作为一项特殊功能,可以使用标准错误将标准错误重定向到标准输出 stderr=STDOUT 。 无论是否重定向到文件的输出,都可以通过进程结束时返回的 结果对象 访问它们。命令期望使用控制台编码写入输出,但是如果需要,可以使用参数配置 输出编码 output_encoding 。 例子: $ {result} = 记录很多 $ {result} = 日志 运行流程 stdout:$ {result.stdout} 运行流程 所有输出:$ {result.stdout} 程序 stderr:$ {result.stderr} 程序 标准输出= $ {TEMPDIR} /stdout.txt 标准错误= STDOUT 标准错误= $ {TEMPDIR} /stderr.txt 请注意,测试运行后不会自动删除创建的输出文件。如果需要,用户有责任将其删除。 输出编码 默认情况下,执行的命令希望使用系统控制台使用的编码将输出写入 标准输出和错误流 。如果该命令使用某些其他编码,则可以使用该 output_encoding 参数进行配置。这在Windows上使用与系统其他部分不同的编码时非常有用,并且许多命令使用通用系统编码而不是控制台编码。 与 output_encoding 参数一起使用的值必须是有效编码,并且必须与命令实际使用的编码匹配。为方便起见,可以使用字符串 CONSOLE 并 SYSTEM 分别指定使用控制台或系统编码。如果生成的输出使用随后配置的不同编码,则通过 结果对象 获得的值将无效。 例子: 开始流程 运行流程 程序 程序 output_encoding = UTF-8 标准输出= $ {路径} output_encoding = SYSTEM 设置输出编码的支持是Robot Framework 3.0中的新增功能。 别号 选择 活动进程 时可以使用的自定义名称。 例子: 开始流程 运行流程 程序 蟒蛇 别名=示例 -C 打印'你好' 别名=你好 积极的过程 测试库记录哪些已启动的进程当前处于活动状态。默认情况下,它是使用 Start Process启动的 最新 流程 ,但 Switch Process 可用于选择其他 流程 。使用 “运行进程 ”不会影响活动进程。 默认情况下,对已启动进程执行操作的关键字将使用活动进程,但可以使用该 handle 参数显式选择其他进程。该手柄可以是该标识符由返回 开始处理 或 alias 显式地提供给 启动过程 或 运行过程 。 结果对象 “运行进程” ,“ 等待进程” 和“ 终止进程” 关键字返回一个结果对象,该对象包含有关进程执行的信息作为其属性。使用 Get Process Result 关键字也可以获得相同的结果对象或其某些属性。对象中可用的属性记录在下表中。 属性 说明 RC 将进程的代码作为整数返回。 标准输出 标准输出流的内容。 标准错误 标准错误流的内容。 stdout_path stderr_path stdout被重定向或未重定向的路径 None 。 重定向stderr或未重定向的路径 None 。 例: $ {result} = 运行流程 程序 应该与整数相等 $ {} result.rc 0 应该匹配 $ {} result.stdout 一些t?xt * 应该是空的 $ {} result.stderr $ {stdout} = 获取文件 $ {} result.stdout_path 应该是平等的 文件应该是空的 $ {}标准输出 $ {} result.stderr_path $ {} result.stdout 布尔参数 某些关键字接受以布尔值true或false处理的参数。如果这样的参数以字符串形式给出,则如果它是空字符串或不区分大小写,则被视为false false , none 或 no 。无论其值如何,其他字符串都被视为true,其他参数类型使用与 Python 相同的 规则 进行测试。 真实的例子: 终止流程 杀=真 #字符串通常是正确的。 终止流程 杀= YES #与上述相同。 终止流程 终止流程 杀= $ {TRUE} 杀= $ {42} #Pcthon True 是真的。 #0以外的数字为真。 错误的例子: 终止流程 杀=假 #String false 为false。 终止流程 杀=无 #字符串 no 也是false。 终止流程 终止流程 杀= $ {EMPTY} 杀= $ {FALSE} #Empty字符串为false。 #Python False 是假的。 在Robot Framework 2.9之前,所有非空字符串(包括 false 和 no )都被认为是真的。 none 在Robot Framework 3.0.3中考虑false是新的。 例 *** 设置 *** 库进程 套件拆解 终止所有进程 kill = True *** 测试用例 *** 示例 启动进程 程序arg1 arg2 alias = First $ {handle} = 启动进程 command.sh arg | command2.sh shell = True cwd = / path $ {result} = 运行流程 $ {CURDIR} /script.py 不应包含 $ {result.stdout} FAIL 终止流程 $ {handle} $ {result} = 等待流程 优先 应该与整数相等 $ {result.rc} 0 快捷键 获取进程ID · 获取进程对象 · 获取进程结果 · 进程正在运行 · 加入命令行 · 进程应该运行 · 进程应该停止 · 运行进程 · 发送信号进行处理 · 拆分命令行 · 启动进程 · 切换进程 · 终止所有进程 · 终止进程 · 等待进程 关键词 关键词 参数 文档 获取进程ID 手柄=无 以整数形式返回进程的进程ID(pid)。 如果 handle 未给出,则使用当前 活动进程 。 请注意,pid与此库内部使用的 Start Process 返回的句柄不同。 获取流程对象 手柄=无 返回底层 subprocess.Popen 对象。 如果 handle 未给出,则使用当前 活动进程 。 获取流程结果 handle = None , rc = False , stdout = False , stderr = False , stdout_path = False , stderr_path = False 返回指定的 结果对象 或其某些属性。 给定 handle 指定应返回其结果的进程。如果不 handle 给出,则返回当前 活动进程的 结果。在任何一种情况下,都必须在使用此关键字之前完成该过程。实际上,这意味着在使用此关键字之前,必须使用“ 等待进程” 或“ 终止进程” 完成以“ 启动进程”启动的进程 。 如果没有 handle 给出除可选之外的其他参数,则返回整个 结果对象 。如果一个或多个其他参数被赋予任何真值,则仅返回 结果对象 的指定属性。始终以与关键字签名中指定的参数相同的顺序返回这些属性。有关true和false值的更多详细信息,请参阅 布尔参数 部分。 例子: 运行流程 蟒蛇 -C 打印'你好,世界!' 别名= myproc的 #获取结果对象 $ {result} = 获取流程结果 在myproc 应该是平等的 $ {} result.rc $ {0} 应该是平等的 $ {} result.stdout 你好,世界! 应该是空的 $ {} result.stderr #获取一个属性 $ {stdout} = 获取流程结果 在myproc 标准输出=真 应该是平等的 #多个属性 $ {}标准输出 应该是平等的 应该是空的 $ {}标准输出 $ {stderr} = $ {}标准输出 $ {}标准错误 你好,世界! 获取流程结果 你好,世界! 在myproc 标准输出= YES 标准错误= YES 虽然获取先前执行的进程的结果通常很方便,但此关键字的主要用例是通过远程库接口返回结果。远程接口不支持返回整个结果对象,但可以毫无问题地返回各个属性。 机器人框架2.8.2中的新功能。 流程正在运行 手柄=无 检查是否正在运行。 如果 handle 未给出,则使用当前 活动进程 。 True 如果进程仍在运行, False 则返回。 加入命令行 * ARGS 将参数连接到一个命令行字符串。 在结果命令行中,字符串参数用空格分隔,包含空格的参数用引号括起来,可能的引号用反斜杠转义。 如果此关键字只给出一个参数,并且是一个像object这样的列表,那么将加入该列表的值。 例: $ {cmd} = 应该是平等的 加入命令行 $ {cmd},在 - 选项 - 选择“带空格的价值” 空格的价值 Robot Framework 2.9.2中的新功能。 流程应该运行 handle = None , error_message =进程未运行。 验证进程是否正在运行。 如果 handle 未给出,则使用当前 活动进程 。 如果进程已停止,则会失败。 流程应该停止 handle = None , error_message =进程正在运行。 验证进程未运行。 如果 handle 未给出,则使用当前 活动进程 。 如果进程仍在运行,则会失败。 运行流程 命令 , *参数 , **配置 运行一个进程并等待它完成。 command 并 *arguments 指定要执行的命令和传递给它的参数。有关更多详细信息,请参阅 指定命令和参数 。 **configuration 包含与启动进程和等待它们完成相关的其他配置。有关与启动进程相关的 配置 的详细信息,请参阅 进程配置 。有关等待进程配置包括 timeout 与 on_timeout 具有相同的语义与参数 等待过程 的关键字。默认情况下没有超时,如果定义了超时,则超时的默认操作为 terminate 。 返回包含有关执行信息的 结果对象 。 请注意, *arguments 必须使用反斜杠(例如 name\=value )转义可能的等号,以避免它们被传入 **configuration 。 例子: $ {result} = 运行流程 蟒蛇 -C 打印'你好,世界!' 应该是平等的 $ {result} = $ {result} = $ {result} = $ {} result.stdout 运行流程 运行流程 运行流程 你好,世界! $ {}命令 $ {}命令 java -Dname \ = value示例 标准错误= STDOUT 超时= 1分钟 壳=真 超时= 10S on_timeout =继续 CWD = $ {实施例} 此关键字不会更改 活动进程 。 timeout 和 on_timeout 参数是Robot Framework 2.8.4中的新功能。 发送信号进行处理 signal , handle = None , group = False 将给定发送 signal 到指定的进程。 如果 handle 未给出,则使用当前 活动进程 。 可以将信号指定为整数作为信号名称。在后一种情况下,可以给出带或不带 SIG 前缀的名称,但名称区分大小写。例如,以下所有示例都发送信号 INT (2) : 发送信号进行处理 发送信号进行处理 发送信号进行处理 2 INT SIGINT 在myproc #发送到活动进程 #发送到命名进程 此关键字仅在类Unix机器上支持,而不在Windows上支持。支持哪些信号取决于系统。有关系统上现有信号的列表,请参阅与信号处理相关的Unix手册页(通常 man signal 或 man 7 signal )。 默认情况下,仅将信号发送到父进程,而不是发送给它启动的子进程。请注意, 在shell中运行进程时 ,shell是父进程,它取决于系统shell是否将信号传播到实际启动的进程。 要将信号发送到整个进程组, group 可以将参数设置为任何真值(请参阅 布尔参数 )。但是,Jython不支持此功能。 机器人框架2.8.2中的新功能。支持 group 参数是Robot Framework 2.8.5中的新功能。 拆分命令行 args , escaping = False 将命令行字符串拆分为参数列表。 字符串从空格中拆分,但用引号括起来的参数可能包含空格。如果 escaping 给出一个真值,则反斜杠被视为转义字符。它可以转义不带引号的空格,引号内的引号等,但在使用Windows路径时也需要使用双反斜杠。 例子: @ {cmd} = 应该是真的 拆分命令行 $ cmd == [' - 选项','带空格的值'] - 选择“带空格的价值” Robot Framework 2.9.2中的新功能。 开始流程 命令 , *参数 , **配置 在后台启动新流程。 有关 参数 的详细信息,请参阅 指定命令和参数 以及 进程配置 ;有关相关示例,请参阅 Run Process 关键字。 使启动的进程成为新的 活动进程 。返回一个标识符,该标识符可用作句柄以在需要时激活已启动的进程。 从Robot Framework 2.8.5开始,启动进程以便创建新的进程组。这允许向可能的子进程发送信号并终止它们。Jython一般也不支持这种方法,也不支持Windows上2.7之前的Python版本。 切换过程 处理 使指定的进程成为当前 活动进程 。 该手柄可以是由返回的标识符 开始处理 或 alias 明确地给它。 例: 开始流程 PROG1 别名=过程1 开始流程 PROG2 别名=过程2 #当前活动的进程是process2 切换过程 #now active进程是process1 过程1 终止所有进程 杀=假 终止此库启动的所有仍在运行的进程。 此关键字可以在套件拆解或其他地方使用,以确保所有进程都已停止, 默认情况下尝试正常终止进程,但可以配置为立即强制终止它们。有关详细信息,请参阅此关键字在内部使用的 终止进程 。 终止流程 等待过程 handle = None , kill = False handle = None , timeout = None , on_timeout = continue 优雅地或强制地停止该过程。 如果 handle 未给出,则使用当前 活动进程 。 默认情况下,首先尝试正常停止该过程。如果进程在30秒内没有停止,或者 kill 参数被赋予真值,则(参见 布尔参数 )强制终止进程。停止最初启动的进程的所有子进程。 在终止进程后等待进程停止。返回一个 结果对象, 其中包含与 Wait For Process 类似的执行信息。 在类似Unix的机器上,使用 TERM (15) 信号和kill进行优雅终止 KILL (9) 。如果您只想发送这些信号中的任何一个而不等待进程停止,请使用 发送信号来处理 。 在Windows上 CTRL_BREAK_EVENT ,使用Win32 API函数使用事件和查杀完成正常终止 TerminateProcess() 。 例子: $ {result} = 应该与整数相等 终止流程 终止流程 $ {} result.rc 在myproc -15 杀=真 #在Unix上 限制: Windows上不支持Jython的优雅终止,也不支持2.7之前的Python版本。进程被杀死了。 Jython根本不支持停止整个进程组,也不支持Windows上2.7之前的Python版本。 在Windows上强制kill只会停止主进程,而不是子进程。 如果终止失败以及返回结果对象,则自动终止进程是Robot Framework 2.8.2中的新功能。终止可能的子进程,包括 CTRL_BREAK_EVENT 在Windows上使用,是Robot Framework 2.8.5中的新功能。 等待进程完成或达到给定的超时。 必须先使用“ 启动过程” 启动等待 过程 。如果 handle 未给出,则使用当前 活动进程 。 timeout 定义等待进程的最长时间。它可以被赋予 不同的时间格式 由机器人框架的支持,例如 42 , 42 s 或 1 minute 30 seconds 。 on_timeout 定义超时发生时要执行的操作。可能的值和相应的操作在下表中说明。请注意,达到超时永远不会失败测试。 值 行动 继续 该过程保持运行(默认)。 终止 杀 该过程优雅地终止。 该过程被强制停止。 有关如何终止和 终止进程 的详细信息,请参阅 Terminate Process 关键字。 如果进程在超时之前结束或者终止或终止,则此关键字返回包含有关执行信息的 结果对象 。如果进程保持运行, None 则返回Python 。 例子: #流程彻底结束 $ {result} = 等待过程 例 流程应该停止 例 应该与整数相等 $ {} result.rc 0 #进程没有结束 $ {result} = 等待过程 超时= 42秒 流程应该运行 应该是平等的 $ {}结果 $ {无} #终止不结束的过程 $ {result} = 流程应该停止 应该与整数相等 等待过程 $ {} result.rc 超时= 1分30秒 -9 on_timeout =杀 timeout 并且 on_timeout 是Robot Framework 2.8.2中的新功能。 共有15个关键字。 由 Libdoc 于2018-04-25 23:41:29 生成。
项目管理
2018-08-15 09:31:00
【来源:OSCHINA】用REDIS实现分布式缓存
第一:Redis 是什么? Redis是基于内存、可持久化的日志型、Key-Value数据库 高性能存储系统,并提供多种语言的API. 第二:出现背景 数据结构(Data Structure)需求越来越多, 但memcache中没有, 影响开发效率 性能需求, 随着读操作的量的上升需要解决,经历的过程有: 数据库读写分离(M/S)–>数据库使用多个Slave–>增加Cache (memcache)–>转到Redis 解决写的问题: 水平拆分,对表的拆分,将有的用户放在这个表,有的用户放在另外一个表; 可靠性需求 Cache的"雪崩"问题让人纠结 Cache面临着快速恢复的挑战 开发成本需求 Cache和DB的一致性维护成本越来越高(先清理DB, 再清理缓存, 不行啊, 太慢了!) 开发需要跟上不断涌入的产品需求 硬件成本最贵的就是数据库层面的机器,基本上比前端的机器要贵几倍,主要是IO密集型,很耗硬件; 维护性复杂 一致性维护成本越来越高; BerkeleyDB使用B树,会一直写新的,内部不会有文件重新组织;这样会导致文件越来越大;大的时候需要进行文件归档,归档的操作要定期做; 这样,就需要有一定的down time; 基于以上考虑, 选择了Redis 第三:Redis 在新浪微博中的应用 Redis简介 1. 支持5种数据结构 支持strings, hashes, lists, sets, sorted sets string是很好的存储方式,用来做计数存储。sets用于建立索引库非常棒; 2. K-V 存储 vs K-V 缓存 新浪微博目前使用的98%都是持久化的应用,2%的是缓存,用到了600+服务器 Redis中持久化的应用和非持久化的方式不会差别很大: 非持久化的为8-9万tps,那么持久化在7-8万tps左右; 当使用持久化时,需要考虑到持久化和写性能的配比,也就是要考虑redis使用的内存大小和硬盘写的速率的比例计算; 3. 社区活跃 Redis目前有3万多行代码, 代码写的精简,有很多巧妙的实现,作者有技术洁癖 Redis的社区活跃度很高,这是衡量开源软件质量的重要指标,开源软件的初期一般都没有商业技术服务支持,如果没有活跃社区做支撑,一旦发生问题都无处求救; Redis基本原理 redis持久化(aof) append online file: 写log(aof), 到一定程度再和内存合并. 追加再追加, 顺序写磁盘, 对性能影响非常小 1. 单实例单进程 Redis使用的是单进程,所以在配置时,一个实例只会用到一个CPU; 在配置时,如果需要让CPU使用率最大化,可以配置Redis实例数对应CPU数, Redis实例数对应端口数(8核Cpu, 8个实例, 8个端口), 以提高并发: 单机测试时, 单条数据在200字节, 测试的结果为8~9万tps; 2. Replication 过程: 数据写到master–>master存储到slave的rdb中–>slave加载rdb到内存。 存储点(save point): 当网络中断了, 连上之后, 继续传. Master-slave下第一次同步是全传,后面是增量同步;、 3. 数据一致性 长期运行后多个结点之间存在不一致的可能性; 开发两个工具程序: 1.对于数据量大的数据,会周期性的全量检查; 2.实时的检查增量数据,是否具有一致性; 对于主库未及时同步从库导致的不一致,称之为延时问题; 对于一致性要求不是那么严格的场景,我们只需要要保证最终一致性即可; 对于延时问题,需要根据业务场景特点分析,从应用层面增加策略来解决这个问题; 例如: 1.新注册的用户,必须先查询主库; 2.注册成功之后,需要等待3s之后跳转,后台此时就是在做数据同步。 第四:分布式缓存的架构设计 1.架构设计 由于redis是单点,项目中需要使用,必须自己实现分布式。基本架构图如下所示: 2.分布式实现 通过key做一致性哈希,实现key对应redis结点的分布。 一致性哈希的实现: hash值计算:通过支持MD5与MurmurHash两种计算方式,默认是采用MurmurHash,高效的hash计算。 一致性的实现:通过java的TreeMap来模拟环状结构,实现均匀分布 3.client的选择 对于jedis修改的主要是分区模块的修改,使其支持了跟据BufferKey进行分区,跟据不同的redis结点信息,可以初始化不同的 ShardInfo,同时也修改了JedisPool的底层实现,使其连接pool池支持跟据key,value的构造方法,跟据不同 ShardInfos,创建不同的jedis连接客户端,达到分区的效果,供应用层调用 4.模块的说明 脏数据处理模块,处理失败执行的缓存操作。 屏蔽监控模块,对于jedis操作的异常监控,当某结点出现异常可控制redis结点的切除等操作。 整个分布式模块通过hornetq,来切除异常redis结点。对于新结点的增加,也可以通过reload方法实现增加。(此模块对于新增结点也可以很方便实现)。 对于以上分布式架构的实现满足了项目的需求。另外使用中对于一些比较重要用途的缓存数据可以单独设置一些redis结点,设定特定的优先级。另外对 于缓存接口的设计,也可以跟据需求,实现基本接口与一些特殊逻辑接口。对于cas相关操作,以及一些事物操作可以通过其watch机制来实现。
项目管理
2018-08-15 09:07:00
【来源:OSCHINA】RobotFramework之Collections
RobotFramework之Collections 背景 继续学习RobotFramework,这次看的是Collections的源码。 Collections库是RobotFramework用来处理列表和字典的库, 官方文档 是这么介绍的 A test library providing keywords for handling lists and dictionaries. Append To List 示例代码 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${l1} create list a b append to list ${l1} 1 2 log ${l1} 执行结果: KEYWORD BuiltIn . Log ${l1} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170422 19:11:52.624 / 20170422 19:11:52.624 / 00:00:00.000 19:11:52.624 INFO [u'a', u'b', u'1', u'2'] 源代码 def append_to_list(self, list_, *values): for value in values: list_.append(value) Combine List 示例代码 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${l1} create list a b ${l2} create list 1 2 ${l} combine lists ${l1} ${l2} log ${l} 执行结果 KEYWORD ${l} = Collections . Combine Lists ${l1}, ${l2} Documentation: Combines the given lists together and returns the result. Start / End / Elapsed: 20170422 19:15:52.086 / 20170422 19:15:52.087 / 00:00:00.001 19:15:52.087 INFO ${l} = [u'a', u'b', u'1', u'2'] 源代码 def combine_lists(self, *lists): ret = [] for item in lists: ret.extend(item) return ret 说明 源代码中的extend方法和append方法是不一样的,append方法是追加一个元素,extend方法是追加一个列表的内容,如果用append方法,则结果会变成如下情况: l1 = [1,2,3] l2 = [4,5,6] l1.append(l2) # ==>[1,2,3,[4,5,6]] l1.extend(l2) # ==>[1,2,3,,4,5,6] 所以合并列表的时候使用 extend 而不是 append Count Values In List 计算某一个值在列表中重复的次数 示例 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${l1} create list 1 2 3 4 3 ${count} count values in list ${l1} 3 log ${count} 执行结果 KEYWORD ${int} = Collections . Count Values In List ${l1}, 3 Documentation: Returns the number of occurrences of the given value in list. Start / End / Elapsed: 20170422 19:30:33.807 / 20170422 19:30:33.807 / 00:00:00.000 19:30:33.807 INFO ${int} = 2 源代码 def count_values_in_list(self, list_, value, start=0, end=None): return self.get_slice_from_list(list_, start, end).count(value) def get_slice_from_list(self, list_, start=0, end=None): start = self._index_to_int(start, True) if end is not None: end = self._index_to_int(end) return list_[start:end] def _index_to_int(self, index, empty_to_zero=False): if empty_to_zero and not index: return 0 try: return int(index) except ValueError: raise ValueError("Cannot convert index '%s' to an integer." % index) 说明 这里方法是默认从第一位开始计算,如果有需要,可以输入一个区间,比如指定起始位置和结束位置,比如如下代码 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${l1} create list 1 2 3 4 3 3 ${count} count values in list ${l1} 3 3 5 log ${count} 执行结果就是1 Dictionaries Should Be Equal 示例代码 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${dict1} create dictionary a=1 b=2 ${dict2} create dictionary a=1 b=3 dictionaries should be equal ${dict1} ${dict2} 执行结果 KEYWORD Collections . Dictionaries Should Be Equal ${dict1}, ${dict2} Documentation: Fails if the given dictionaries are not equal. Start / End / Elapsed: 20170422 19:49:36.865 / 20170422 19:49:36.866 / 00:00:00.001 19:49:36.866 FAIL Following keys have different values: Key b: 2 != 3 源代码 def dictionaries_should_be_equal(self, dict1, dict2, msg=None, values=True): keys = self._keys_should_be_equal(dict1, dict2, msg, values) self._key_values_should_be_equal(keys, dict1, dict2, msg, values) def _keys_should_be_equal(self, dict1, dict2, msg, values): keys1 = self.get_dictionary_keys(dict1) keys2 = self.get_dictionary_keys(dict2) miss1 = [unic(k) for k in keys2 if k not in dict1] miss2 = [unic(k) for k in keys1 if k not in dict2] error = [] if miss1: error += ['Following keys missing from first dictionary: %s' % ', '.join(miss1)] if miss2: error += ['Following keys missing from second dictionary: %s' % ', '.join(miss2)] _verify_condition(not error, '\n'.join(error), msg, values) return keys1 def _key_values_should_be_equal(self, keys, dict1, dict2, msg, values): diffs = list(self._yield_dict_diffs(keys, dict1, dict2)) default = 'Following keys have different values:\n' + '\n'.join(diffs) _verify_condition(not diffs, default, msg, values) def _yield_dict_diffs(self, keys, dict1, dict2): for key in keys: try: assert_equal(dict1[key], dict2[key], msg='Key %s' % (key,)) except AssertionError as err: yield unic(err) 说明 本来Python中比较两个字典是否相等只要用 == 就行了,这里为了能够精确的报错,遍历了所有的key和value来做比较。 Dictionary Should Contain Item 示例代码 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${dict1} create dictionary a=1 b=2 dictionary should contain item ${dict1} a 1 结果 KEYWORD Collections . Dictionary Should Contain Item ${dict1}, a, 1 Documentation: An item of key``/``value must be found in a `dictionary`. Start / End / Elapsed: 20170422 19:58:37.086 / 20170422 19:58:37.087 / 00:00:00.001 源代码 def dictionary_should_contain_item(self, dictionary, key, value, msg=None): self.dictionary_should_contain_key(dictionary, key, msg) actual, expected = unic(dictionary[key]), unic(value) default = "Value of dictionary key '%s' does not match: %s != %s" % (key, actual, expected) _verify_condition(actual == expected, default, msg) def dictionary_should_contain_key(self, dictionary, key, msg=None): default = "Dictionary does not contain key '%s'." % key _verify_condition(key in dictionary, default, msg) 说明 判断一个k-v是否在一个字典里,同样是通过遍历原有字典的方式来处理,只不过这里传值必须是按照示例代码中那样传递,不能传入a=1或者一个字典。 Dictionary Should Contain Sub Dictionary 示例 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${dict1} create dictionary a=1 b=2 ${dict2} create dictionary a=1 Dictionary Should Contain Sub Dictionary ${dict1} ${dict2} 结果 KEYWORD Collections . Dictionary Should Contain Sub Dictionary ${dict1}, ${dict2} Documentation: Fails unless all items in dict2 are found from dict1. Start / End / Elapsed: 20170422 20:09:41.864 / 20170422 20:09:41.865 / 00:00:00.001 源代码 def dictionary_should_contain_sub_dictionary(self, dict1, dict2, msg=None, values=True): keys = self.get_dictionary_keys(dict2) diffs = [unic(k) for k in keys if k not in dict1] default = "Following keys missing from first dictionary: %s" \ % ', '.join(diffs) _verify_condition(not diffs, default, msg, values) self._key_values_should_be_equal(keys, dict1, dict2, msg, values) 说明 上一个方法只能传入单一的k-v,使用的局限性比较大,这个方法就可以支持传入一个字典 Get Dictionary Items 示例 *** Settings *** Library Collections *** Test Cases *** TestCase001 ${dict1} create dictionary a=1 b=2 ${dict2} create dictionary a=1 ${dict3} Get Dictionary Items ${dict1} 执行结果 KEYWORD ${dict3} = Collections . Get Dictionary Items ${dict1} Documentation: Returns items of the given dictionary. Start / End / Elapsed: 20170424 00:39:49.766 / 20170424 00:39:49.766 / 00:00:00.000 00:39:49.766 INFO ${dict3} = [u'a', u'1', u'b', u'2'] 源代码 def get_dictionary_items(self, dictionary): ret = [] for key in self.get_dictionary_keys(dictionary): ret.extend((key, dictionary[key])) return ret 说明 这个方法就是把字典里面的成员以列表的形式返回出来,具体应用场景还不是很确定 Get Dictionary Values 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${dict} create dictionary a=1 b=2 ${rst} get from dictionary ${dict} a log ${rst} 执行结果 Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 19:45:59.199 / 20170428 19:45:59.200 / 00:00:00.001 19:45:59.200 INFO 1 源代码 def get_from_dictionary(self, dictionary, key): try: return dictionary[key] except KeyError: raise RuntimeError("Dictionary does not contain key '%s'." % key) 说明 源代码其实非常简单,传入一个字典和一个key,然后就返回这个key对应的值 Get From List 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst} create list 1 2 3 ${new_lst} get from list ${lst} 0 log ${new_lst} 执行结果 KEYWORD BuiltIn . Log ${new_lst} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 19:50:42.533 / 20170428 19:50:42.533 / 00:00:00.000 19:50:42.533 INFO 1 源代码 def get_from_list(self, list_, index): try: return list_[self._index_to_int(index)] except IndexError: self._index_error(list_, index) 说明 执行方法就是获取列表和索引,然后返回列表的索引,值得一提的是,不论你传入的值是str类型还是int类型,都会被 python 代码转成int类型,所以源代码中 ${new_lst} get from list ${lst} 0 这样写,执行结果也是一样的。附上转换的源代码。 def _index_to_int(self, index, empty_to_zero=False): if empty_to_zero and not index: return 0 try: return int(index) except ValueError: raise ValueError("Cannot convert index '%s' to an integer." % index) Get Index From List 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst} create list 1 2 3 4 5 4 ${rst} get index from list ${lst} 3 log ${rst} 执行结果 KEYWORD BuiltIn . Log ${rst} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 19:59:05.532 / 20170428 19:59:05.532 / 00:00:00.000 19:59:05.532 INFO 2 源代码 def get_index_from_list(self, list_, value, start=0, end=None): if start == '': start = 0 list_ = self.get_slice_from_list(list_, start, end) try: return int(start) + list_.index(value) except ValueError: return -1 说明 源代码中的 self.get_slice_from_list 方法是一个切片方法,在 Get Index From List 中还可以传入两个参数, start 表示列表的开始索引, end 表示列表结束的索引,如果没有给,默认就是整个列表来取索引结果。 Get Match Count 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${count} get match count aaabbcc a log ${count} 执行结果 KEYWORD BuiltIn . Log ${count} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 20:08:08.699 / 20170428 20:08:08.700 / 00:00:00.001 20:08:08.700 INFO 3 源代码 def get_match_count(self, list, pattern, case_insensitive=False, whitespace_insensitive=False): return len(self.get_matches(list, pattern, case_insensitive, whitespace_insensitive)) def _get_matches_in_iterable(iterable, pattern, case_insensitive=False, whitespace_insensitive=False): if not is_string(pattern): raise TypeError("Pattern must be string, got '%s'." % type_name(pattern)) regexp = False if pattern.startswith('regexp='): pattern = pattern[7:] regexp = True elif pattern.startswith('glob='): pattern = pattern[5:] matcher = Matcher(pattern, caseless=is_truthy(case_insensitive), spaceless=is_truthy(whitespace_insensitive), regexp=regexp) return [string for string in iterable if is_string(string) and matcher.match(string)] 说明 这个方法是返回一个字符在字符串中重复的次数。 源代码中的第二个方法才是真正的方法, Get Match Count 方法是非常强大的,可以做普通匹配,也可以做正则匹配,首先,传入的必须是字符串,否则报错,然后判定传入的条件是否带有正则,如果传入的条件是 regexp= 开头的,表示要使用正则匹配,否则就是普通查找。 第三个参数表示是否忽略大小写,第四个参数表示是否忽略空格。 Get Matchs 此方法与上面一个方法调用的是一样的底层方法,请参考上面的内容。 Get Slice From List 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst} create list 1 2 3 4 5 6 7 8 ${slice_list} get slice from list ${lst} 3 6 log ${slice_list} 执行结果 KEYWORD BuiltIn . Log ${slice_list} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 20:20:00.780 / 20170428 20:20:00.780 / 00:00:00.000 20:20:00.780 INFO [u'4', u'5', u'6'] 源代码 def get_slice_from_list(self, list_, start=0, end=None): start = self._index_to_int(start, True) if end is not None: end = self._index_to_int(end) return list_[start:end] 说明 源代码还是比较容易看懂的,如果开始和结束不传值,那么就是返回原来的列表,否则就返回切片的列表。 Insert into list 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst} create list 1 2 3 4 5 6 7 8 insert into list ${lst} 0 a log ${lst} 执行结果 KEYWORD BuiltIn . Log ${lst} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 20:27:02.736 / 20170428 20:27:02.737 / 00:00:00.001 20:27:02.737 INFO [u'a', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8'] 源代码 def insert_into_list(self, list_, index, value): list_.insert(self._index_to_int(index), value) 说明 调用的就是 Python 中列表的 insert 方法。如果传入的索引大于列表的长度,那么值会默认插入到列表的最后一位,如果传入的是负数,比如传入-1,那么就会在列表的倒数第二位加入该元素。 根据实现的 Python 代码可以知道,传入的索引是int或者str类型都可以,实现的时候会强制转为int类型。 关于插入索引大于列表长度的问题,可以参考以下代码 Python 2.7.10 (default, Feb 6 2017, 23:53:20) [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.34)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> a = [1,2,3,4,5] >>> a [1, 2, 3, 4, 5] >>> a.insert(9, 'a') >>> a [1, 2, 3, 4, 5, 'a'] Keep In Dictionary 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${dict} create dictionary a=1 b=2 c=3 d=4 keep in dictionary ${dict} a b log ${dict} 执行结果 KEYWORD BuiltIn . Log ${dict} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 20:47:47.528 / 20170428 20:47:47.529 / 00:00:00.001 20:47:47.529 INFO {u'a': u'1', u'b': u'2'} 源代码 def keep_in_dictionary(self, dictionary, *keys): remove_keys = [k for k in dictionary if k not in keys] self.remove_from_dictionary(dictionary, *remove_keys) def remove_from_dictionary(self, dictionary, *keys): for key in keys: if key in dictionary: value = dictionary.pop(key) logger.info("Removed item with key '%s' and value '%s'." % (key, value)) else: logger.info("Key '%s' not found." % key) 说明 该方法是用来保留字典里指定的 key ,源代码也比较好理解,不过此处的注释有问题, Keeps the given keys in the dictionary and removes all other. If the given key cannot be found from the dictionary, it is ignored. 第二句是说明如果传入的 key 如果不在这个字典里,那么这个操作就会被忽略。但是实际上我们可以看到代码执行时并不是这样的,第一个方法会把字典中与传入的 keys 不匹配的 key 全部拿出来,调用第二个方法来全部从字典里移除,也就是说传入的key如果在字典里不存在,那么原字典就会变为一个空字典。 实际上的执行结果就是这样 KEYWORD ${dict} = BuiltIn . Create Dictionary a=1, b=2, c=3, d=4 00:00:00.001KEYWORD Collections . Keep In Dictionary ${dict}, e Documentation: Keeps the given keys in the dictionary and removes all other. Start / End / Elapsed: 20170428 20:52:04.051 / 20170428 20:52:04.052 / 00:00:00.001 20:52:04.052 INFO Removed item with key 'a' and value '1'. 20:52:04.052 INFO Removed item with key 'b' and value '2'. 20:52:04.052 INFO Removed item with key 'c' and value '3'. 20:52:04.052 INFO Removed item with key 'd' and value '4'. 00:00:00.000KEYWORD BuiltIn . Log ${dict} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 20:52:04.052 / 20170428 20:52:04.052 / 00:00:00.000 20:52:04.052 INFO {} List Should Contain Sub List 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst1} create list 1 2 3 4 ${lst2} create list 2 3 list should contain sub list ${lst1} ${lst2} 执行结果 KEYWORD Collections . List Should Contain Sub List ${lst1}, ${lst2} Documentation: Fails if not all of the elements in list2 are found in list1. 源代码 def list_should_contain_sub_list(self, list1, list2, msg=None, values=True): diffs = ', '.join(unic(item) for item in list2 if item not in list1) default = 'Following values were not found from first list: ' + diffs _verify_condition(not diffs, default, msg, values) List Should Not Contain Duplicates 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst1} create list 1 2 3 4 2 list should not contain duplicates ${lst1} 2 执行结果 KEYWORD Collections . List Should Not Contain Duplicates ${lst1}, 2 Documentation: Fails if any element in the list is found from it more than once. Start / End / Elapsed: 20170428 21:08:59.719 / 20170428 21:08:59.720 / 00:00:00.001 21:08:59.719 INFO '2' found 2 times. 21:08:59.720 FAIL 2 源代码 def list_should_not_contain_duplicates(self, list_, msg=None): if not isinstance(list_, list): list_ = list(list_) dupes = [] for item in list_: if item not in dupes: count = list_.count(item) if count > 1: logger.info("'%s' found %d times." % (item, count)) dupes.append(item) if dupes: raise AssertionError(msg or '%s found multiple times.' % seq2str(dupes)) 说明 该方法用于断言某个元素在列表中只会出现一次,如果出现多次则报错。 Pop From Dictionary 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${dict} create dictionary a=1 b=2 c=3 ${newdict} pop from dictionary ${dict} a log ${newdict} log ${dict} 执行结果 KEYWORD BuiltIn . Log ${dict} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 21:21:05.523 / 20170428 21:21:05.523 / 00:00:00.000 21:21:05.523 INFO {u'b': u'2', u'c': u'3'} 源代码 def pop_from_dictionary(self, dictionary, key, default=NOT_SET): if default is NOT_SET: self.dictionary_should_contain_key(dictionary, key) return dictionary.pop(key) return dictionary.pop(key, default) 说明 这里的方法有一个可选参数 default ,是用来处理不存在的键值,如果pop的键值在字典里不存在,那么框架是会报错处理的,但是如果给了一个default,那么传入的键值不存在时,会返回 default 的值。 Remove Duplicates 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst} create list 1 2 3 4 1 2 ${rst} remove duplicates ${lst} 执行结果 KEYWORD ${rst} = Collections . Remove Duplicates ${lst} Documentation: Returns a list without duplicates based on the given list. Start / End / Elapsed: 20170428 21:52:22.522 / 20170428 21:52:22.522 / 00:00:00.000 21:52:22.522 INFO 2 duplicates removed. 21:52:22.522 INFO ${rst} = [u'1', u'2', u'3', u'4'] 源代码 def remove_duplicates(self, list_): ret = [] for item in list_: if item not in ret: ret.append(item) removed = len(list_) - len(ret) logger.info('%d duplicate%s removed.' % (removed, plural_or_not(removed))) return ret 说明 该方法是一个除重方法,可以吧列表中重复的元素移除。 Sort List 示例代码 *** Settings *** Library Collections *** Test Cases *** test1 ${lst} create list 1 2 3 4 1 2 and dib sort list ${lst} log ${lst} 执行结果 KEYWORD BuiltIn . Log ${lst} Documentation: Logs the given message with the given level. Start / End / Elapsed: 20170428 22:13:57.288 / 20170428 22:13:57.288 / 00:00:00.000 22:13:57.288 INFO [u'1', u'1', u'2', u'2', u'3', u'4', u'and', u'dib'] 源代码 def sort_list(self, list_): list_.sort() 说明 该方法调用了列表的 sort 方法,需要注意的是,如果原始列表还需要使用的话,最好先保留一份,否则原始数据就会被破坏。 Note that the given list is changed and nothing is returned. Use Copy List first, if you need to keep also the original order. 总结 Collections 库是一个非常简单的库,不过里面包含了大部分的字典和列表的操作,本文仅仅覆盖了一些关键字,还有一些重复的,或者一眼就能看出来怎么用的关键字我就没有写了。通过这些示例代码,基本上能够了解Robot Framework框架的使用方法,也能够对 Robot Framework 框架的编写方式和编写思想有了一定的认识。
项目管理
2018-08-14 18:05:00
【来源:OSCHINA】测试插件-infinitest介绍
缘起 写多了业务代码,一些遗留系统里处于基本没有单测的状态,因此最近对 TDD 的开发方式很感兴趣,看了不少 TDD 介绍和实践的书。 TDD 对测试的执行次数有很高的要求,但是平常在 idea 里面写代码运行测试所需时间较长,需要先 build 才能启动测试,这就成了我们践行 TDD 的障碍,我们需要一款对测试友好的插件来帮助我们。 infinitest 插件可以让我们免除手动执行测试的尴尬,它检测文件的 class 变动,当文件有变动时,可以自动执行测试,开箱即用。infinitest 也可以使用简单的配置,让我们把 infinitest 和 Junit 相关特性结合起来,更显方便 安装与使用 安装很简单: Settings -> Plugins -> Browse repositories -> 查找infinitest -> Install -> Restart idea 添加到项目中: Project Structure(ctrl+shift+alt+s) -> Modules -> 添加infinitest idea 默认不会自动 compile文件,我们需要开启一下 Settings -> Build, Execution, Deployment -> Compiler -> Build project automatically 打上勾 如果项目中只有简单的测试,这样就可以了,如果我们需要区分不同的测试,我们需要自己添加文件来配置一下。 配置 总的文档目录 infinitest docs filters 这个配置可以让我们指定需要执行哪些测试,不执行哪些测试,支持 Junit4 的 category 和 Junit5 的 tags 添加配置文件 自己新建一个 infinitest.filters , 将文件放在 .iml 同级目录下即可。 通过 class name,支持正则表达式 # Include tests that end in ITest or TestCase include .*ITest, .*TestCase # Exclude Inner Classes exclude .*\$.* # Include tests in package com.mycompany.mypackage and sub-packages include com\.mycompany\.mypackage\..* # Include tests in package com.mycompany.mypackage and not in sub-packages include com\.mycompany\.mypackage\.[^.]* 通过 Junit5 的 tags # Include tests with "Fast" and "Regression" tags includeGroups Fast, Regression # Exclude tests with "Slow" tag excludeGroups Slow 通过 Junit4 的 category # Include tests in FastTests and RegressionTests categories includeGroups com.example.FastTests, com.example.RegressionTests # Exclude SlowTests category excludeGroups com.example.SlowTests testNg 框架相关的不做介绍,具体可以参见 infinitest filters JVM options 新建 infinitest.args 文件,位置与filters的一样,里面每一行是一个虚拟机参数,这个配置会覆盖 infinitest 的默认配置 最后 大家如果遇到什么问题可以先 Google 一下,如果没有找到答案,可以在 GitHub 上自行提问。
项目管理
2018-08-11 20:46:00
【来源:OSCHINA】获取数据库名称dbName
@Autowired DataSource ds; connection = ds.getConnection(); tring dbName = connection.getCatalog(); connection.close();
项目管理
2018-08-10 16:43:00
命令模式: 将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。 四种角色: Command:定义命令的统一接口 ConcreteCommand:Command接口的实现者,用来执行具体的命令,某些情况下可以直接用来充当Receiver。 Receiver:命令的实际执行者 Invoker:命令的请求者,是命令模式中最重要的角色。这个角色用来对各个命令进行控制。 一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。命令对象将动作和接收者包进对象中。这个对象只暴露出一个execute()方法,当此方法被调用的时候,接收者就会进行这些动作。从外面来看,其他队形不知道究竟哪个接收者进行了哪些动作,只知道如果调用execute()方法,请求的目的就能达到。 举个栗子: 要实现一个智能开关的遥控器,可以控制客厅和卧室的灯、电视的开关等功能。 定义命令接口: public interface Command { public void execute(); } 实现灯的开关命令: // 开灯命令 public class LightOnCommand implements Command { Light light; public LightOnCommand(Light light){ this.light = light; } public void execute() { light.on(); } } // 关灯命令 public class LightOffCommand implements Command { Light light; public LightOffCommand(Light light){ this.light = light; } public void execute() { light.off(); } } 遥控器: // 遥控器有七个开关,初始化都是无命令的 public class RemoteControl { Command[] onCommands; Command[] offCommands; // 初始化遥控器 public RemoteControl() { onCommands = new Command[7]; offCommands = new Command[7]; Command noCommand = new NoCommand(); for (int i = 0; i < 7; i++) { onCommands[i] = noCommand; offCommands[i] = noCommand; } } // 配置按钮对应的命令 public void setCommand(int index, Command onCommand, Command offCommand) { onCommands[index] = onCommand; offCommands[index] = offCommand; } // 按下开按钮 public void onButtonWasPushed(int index) { onCommands[index].execute(); } // 按下关按钮 public void offButtonWasPushed(int index) { onCommands[index].execute(); } } 测试遥控器: public class RemoteTest { RemoteControl remoteControl = new RemoteControl(); Light livingRoomLight = new Light("Living Room"); Light kitchenLight = new Light("kitchen"); // 设置遥控器按钮命令 LightOnCommand livingRoomLightOnCommand = new LightOnCommand(livingRoomLight); LightOffCommand livingRoomLightOffCommand = new LightOffCommand(livingRoomLight); LightOnCommand kitchenLightOnCommand = new LightOnCommand(kitchenLight); LightOffCommand kitchenLightOffCommand = new LightOffCommand(kitchenLight); remoteControl.setCommand(0, livingRoomLightOnCommand, livingRoomLightOffCommand); remoteControl.setCommand(1, kitchenLightOnCommand, kitchenLightOffCommand); // 卧室灯开关操作 remoteControl.onButtonWasPushed(0); remoteControl.offButtonWasPushed(0); // 厨房灯开关操作 remoteControl.onButtonWasPushed(1); remoteControl.offButtonWasPushed(1); }
项目管理
2018-08-09 10:05:00
1. 架构概述 J2EE体系包括java server pages(JSP) ,java SERVLET, enterprise bean,WEB service等技术。这些技术的出现给电子商务时代的WEB应用程序的开发提供了一个非常有竞争力的选择。怎样把这些技术组合起来形成一个适应项目需要的稳定架构是项目开发过程中一个非常重要的步骤。完成这个步骤可以形成一个主要里程碑基线。形成这个基线有很多好处: 各种因数初步确定 为了形成架构基线,架构设计师要对平台(体系)中的技术进行筛选,各种利弊的权衡。往往架构设计师在这个过程中要阅读大量的技术资料,听取项目组成员的建议,考虑领域专家的需求,考虑赞助商成本(包括开发成本和运行维护成本)限额。一旦架构设计经过评审,这些因数初步地就有了在整个项目过程中的对项目起多大作用的定位。 定向技术培训 一旦架构师设计的架构得到了批准形成了基线,项目开发和运行所采用的技术基本确定下来了。众多的项目经理都会对预备项目组成员的技术功底感到担心;他们需要培训部门提供培训,但就架构师面对的技术海洋,项目经理根本就提不出明确的技术培训需求。怎不能够对体系中所有技术都进行培训吧!有了架构里程碑基线,项目经理能确定这个项目开发会采用什么技术,这是提出培训需求应该是最精确的。不过在实际项目开发中,技术培训可以在基线确定之前与架构设计并发进行。 角色分工 有了一个好的架构蓝图,我们就能准确划分工作。如网页设计,JSP 标签处理类设计,SERVLET 设计,session bean设计,还有各种实现。这些任务在架构蓝图上都可以清晰地标出位置,使得项目组成员能很好地定位自己的任务。一个好的架构蓝图同时也能规范化任务,能很好地把任务划分为几类,在同一类中的任务的工作量和性质相同或相似。这样工作量估计起来有一个非常好的基础。 运行维护 前面说过各个任务在架构图上都有比较好的定位。任何人能借助它很快地熟悉整个项目的运行情况,错误出现时能比较快速地定位错误点。另外,有了清晰的架构图,项目版本管理也有很好的版本树躯干。 扩展性 架构犹如一颗参天大树的躯干,只要躯干根系牢,树干粗,长一些旁支,加一些树叶轻而易举无疑。同样,有一个稳定的经得起考验的架构,增加一两个业务组件是非常快速和容易的。 大家都知道这些好处,一心想形成一个这样的J2EE应用程序架构(就像在windows平台中的MFC)。在这个路程中经历了两个大的阶段: 1.1. 模型1 模型1其实不是一个什么稳定架构,甚至谈不上形成了架构。模型1的基础是JSP文件。它从HTTP的请求中提取参数,调用相应的业务逻辑,处理HTTP会话,最后生成HTTP文档。一系列这样的JSP文件形成一个完整的模型1应用,当然可能会有其他辅助类或文件。早期的ASP 和 PHP 技术就属于这个情况。 总的看来,这个模型的好处是简单,但是它把业务逻辑和表现混在一块,对大应用来说,这个缺点是令人容忍不了的。 1.2. 模型2 在经过一番实践,并广泛借鉴和总结经验教训之后,J2EE应用程序终于迎来了MVC(模型-视图-控制)模式。MVC模式并不是J2EE行业人士标新立异的,所以前面我谈到广发借鉴。MVC的核心就是做到三层甚至多层的松散耦合。这对基于组件的,所覆盖的技术不断膨胀的J2EE体系来说真是福音和救星。 它在浏览器(本文对客户代理都称浏览器)和JSP或SERVLET之间插入一个控制组件。这个控制组件集中了处理浏览器发过来的HTTP请求的分发逻辑,也就是说,它会根据HTTP请求的URL,输入参数,和目前应用的内部状态,把请求分发给相应的WEB 层的JSP 或SERVLET。另外它也负责选择下一个视图(在J2EE中,JSP,SERVLET会生成回给浏览器的html从而形成视图)。集中的控制组件也有利于安全验证,日志纪录,有时也封装请求数据给下面的WEB tier层。这一套逻辑的实现形成了一个像MFC的应用框架,位置如图: 1.3. 多层应用 下图为J2EE体系中典型的多层应用模型。 Client tier客户层 一般为浏览器或其他应用。客户层普遍地支持HTTP协议,也称客户代理。 WEB tier WEB应用层 在J2EE中,这一层由WEB 容器运行,它包括JSP, SERVLET等WEB部件。 EJB tier 企业组件层 企业组件层由EJB容器运行,支持EJB, JMS, JTA 等服务和技术。 EIS tier 企业信息系统层 企业信息系统包含企业内传统信息系统如财务,CRM等,特点是有数据库系统的支持。 应用框架目前主要集中在WEB层,旨在规范这一层软件的开发。其实企业组件层也可以实现这个模型,但目前主要以设计模式的形式存在。而且有些框架可以扩充,有了企业组件层组件的参与,框架会显得更紧凑,更自然,效率会更高。 2. 候选方案 目前,实现模型2的框架也在不断的涌现,下面列出比较有名的框架。 2.1. Apache Struts Struts是一个免费的开源的WEB层的应用框架,apache软件基金致力于struts的开发。Struts具是高可配置的性,和有一个不断增长的特性列表。一个前端控制组件,一系列动作类,动作映射,处理XML的实用工具类,服务器端java bean 的自动填充,支持验证的WEB 表单,国际化支持,生成HTML,实现表现逻辑和模版组成了struts的灵魂。 2.1.1. Struts和MVC 模型2的目的和MVC的目的是一样的,所以模型2基本可以和MVC等同起来。下图体现了Struts的运作机理: 2.1.1.1. 控制 如图所示,它的主要部件是一个通用的控制组件。这个控制组件提供了处理所有发送到Struts 的HTTP请求的入口点。它截取和分发这些请求到相应的动作类(这些动作类都是Action类的子类)。另外控制组件也负责用相应的请求参数填充 From bean,并传给动作类。动作类实现核心商业逻辑,它可以通过访问java bean 或调用EJB。最后动作类把控制权传给后续的JSP 文件,后者生成视图。所有这些控制逻辑利用一个叫struts-config.xml文件来配置。 2.1.1.2. 模型 模型以一个或几个java bean的形式存在。这些bean分为三种: Form beans(表单Beans) 它保存了HTTP post请求传来的数据,在Struts里,所有的Form beans都是 ActionFrom 类的子类。 业务逻辑beans 专门用来处理业务逻辑。 系统状态beans 它保存了跨越多个HTTP 请求的单个客户的会话信息,还有系统状态。 2.1.1.3. 视图 控制组件续传HTTP请求给实现了视图的JSP文件。JSP能访问beans 并生成结果文档反馈到客户。Struts提供JSP 标签库: Html,Bean,Logic,Template等来达到这个目的,并有利于分开表现逻辑和程序逻辑。 2.1.2. Struts的细节分析 2.1.2.1. 视图-控制-模型 用户发出一个*.do的HTTP请求,控制组件接收到这个请求后,查找针对这个请求的动作映射,再检查是否曾创建过相应的动作对象(action实例),如果没有则调用actionmapping生成一个动作对象,控制组件会保存这个动作对象供以后使用。接着调用actionmapping的方法得到actionForm对象。之后把actionForm作为参数传给动作对象的perform方法,这个方法结束之后会返回给控制组件一个 actionforward对象。控制组件接着从这个对象中获取下一个视图的路径和重定向属性。如果为重定向则调用HTTPSERVLETREPONSE的方法来显示下一个视图,否则相继调用requestdispatcher, SERVLETcontext的方法续传HTTP请求到下一个视图。 当动作对象运行perform方法时,可能出现错误信息。动作对象可以保存这些错误信息到一个error对象中,接着调用自身的saveerrors方法把这个错误保存到request对象的属性中。接着动作对象调用actionmapping对象的getInput方法从动作映射中获取input参数,也就是产生输入的视图,并以这个input为参数生成一个actionforward对象返回。这个input参数的JSP中一般有HTTP:errors定制标签读取这些错误信息并显示在页面上。 2.1.2.2. 模型到视图 模型到视图指视图在显示之前装载系统数据到视图的过程。系统数据一般为模型内java bean的信息。示意图表现了由控制组件forward过来的有html:form定制标签的JSP 的处理逻辑。 html:form定制标签处理对象从application scope(通过查询SERVLETCONTEXT对象的属性来实现)获取先前由控制组件actionSERVLET放在那里的动作映射等对象,由html:form 的action属性查得actionform名字、类型和范围等信息,在相应的范围内查找actionform,如果有则利用它的信息填充html form表单[实际填充动作在嵌套的html:text等定制标签的处理对象中]。否则在相应范围内创建一个actionform 对象。
项目管理
2018-07-27 09:17:00
Confluence 并不能比较容易的对外部站点进行搜索,这个是因为 Confluence 使用的是 Lucene 内部查找,但是你还是有下面 2 个可选的方案: 嵌入外部页面到 Confluence 替换 Confluence 查找 嵌入外部页面到 Confluence 如果你有少量的外部页面内容需要你的 Confluence 站点进行索引,你最好可以启用 HTML Include Macro 宏,使用这个宏将外部页面嵌入到你的 Confluence 页面中。 替换 Confluence 查找 如果你有足够的技术力量,你可以将 Confluence 的内部搜索用 crawler 进行替换,这样你可以用 crawler 搜索你的 Confluence 站点和外部站点。这个是 Confluence 提供的一个高级选项,相对于对 Confluence 的内部搜索进行修改来说,这个选项更加容易进行操作。这个要求删除你 Confluence 内部索引中的所有页面,将你 Confluence 的内部索引结果替换为你自己的 crawler 前段。 设置和替换你的联合查询来查询 Confluence 站点和你使用的其他站点,同时提供结果。你需要配置 open-source crawlers 服务器。注意,你可以通过 Confluence API 对 Confluence 进行查询。 通过 Customizing Site and Space Layouts 来参考内部的搜索,这样可以将查询的前端连接到你的 Confluence 中。 使用其他服务器提供查找的前端。你可能需要在你的应用服务器上插入合适的上下文路径,这样能够在 Confluence 中显示正确的路径。Tomcat 针对 Confluence 上下文设置的路径是在 install\confluence\WEBINF\web.xml 文件中定义的。 https://www.cwiki.us/display/CONF6ZH/Setting+Up+Confluence+to+Index+External+Sites
项目管理
2018-07-10 22:18:00
【来源:OSCHINA】使用 intellij idea 记录
官网下载:toolbox jetbrains公司的软件管理工具 http://www.jetbrains.com/ 安装 IntelliJ IDEA 启动IntelliJ IDEA 激活:http://active.chinapyg.com/ https://blog.csdn.net/u014236541/article/details/79851531 有用的的插件:https://blog.csdn.net/lgd_guangdong/article/details/80062049 快捷键:https://blog.csdn.net/troy__/article/details/52145446 注释模版:https://blog.csdn.net/zwj1030711290/article/details/80673482 IntelliJ 主题:http://www.riaway.com/ 默认配置:https://blog.csdn.net/wo541075754/article/details/70154604 发布:https://blog.csdn.net/yanjiangdi/article/details/77864610?locationNum=4&fps=1 补充快捷键: Ctrl+Shift + Enter,语句完成 “!”,否定完成,输入表达式时按 “!”键 Ctrl+E,最近的文件 Ctrl+Shift+E,最近更改的文件 Shift+Click,可以关闭文件 Ctrl+[ OR ],可以跑到大括号的开头与结尾 Ctrl+F12,可以显示当前文件的结构 Ctrl+F7,可以查询当前元素在当前文件中的引用,然后按 F3 可以选择 Ctrl+N,可以快速打开类 Ctrl+Shift+N,可以快速打开文件 Alt+Q,可以看到当前方法的声明 Ctrl+P,可以显示参数信息 Ctrl+Shift+Insert,可以选择剪贴板内容并插入 Alt+Insert,可以生成构造器/Getter/Setter等 Ctrl+Alt+V,可以引入变量。例如:new String(); 自动导入变量定义 Ctrl+Alt+T,可以把代码包在一个块内,例如:try/catch Ctrl+Enter,导入包,自动修正 Ctrl+Alt+L,格式化代码 Ctrl+Alt+I,将选中的代码进行自动缩进编排,这个功能在编辑 JSP 文件时也可以工作 Ctrl+Alt+O,优化导入的类和包 Ctrl+R,替换文本 Ctrl+F,查找文本 Ctrl+Shift+Space,自动补全代码 Ctrl+空格,代码提示(与系统输入法快捷键冲突) Ctrl+Shift+Alt+N,查找类中的方法或变量 Alt+Shift+C,最近的更改 Alt+Shift+Up/Down,上/下移一行 Shift+F6,重构 – 重命名 Ctrl+X,删除行 Ctrl+D,复制行 Ctrl+/或Ctrl+Shift+/,注释(//或者/**/) Ctrl+J,自动代码(例如:serr) Ctrl+Alt+J,用动态模板环绕 Ctrl+H,显示类结构图(类的继承层次) Ctrl+Q,显示注释文档 Alt+F1,查找代码所在位置 Alt+1,快速打开或隐藏工程面板 Ctrl+Alt+left/right,返回至上次浏览的位置 Alt+left/right,切换代码视图 Alt+Up/Down,在方法间快速移动定位 Ctrl+Shift+Up/Down,向上/下移动语句 F2 或 Shift+F2,高亮错误或警告快速定位 Tab,代码标签输入完成后,按 Tab,生成代码 Ctrl+Shift+F7,高亮显示所有该文本,按 Esc 高亮消失 Alt+F3,逐个往下查找相同文本,并高亮显示 Ctrl+Up/Down,光标中转到第一行或最后一行下 Ctrl+B/Ctrl+Click,快速打开光标处的类或方法(跳转到定义处) Ctrl+Alt+B,跳转到方法实现处 Ctrl+Shift+Backspace,跳转到上次编辑的地方 Ctrl+O,重写方法 Ctrl+Alt+Space,类名自动完成 Ctrl+Alt+Up/Down,快速跳转搜索结果 Ctrl+Shift+J,整合两行 Alt+F8,计算变量值 Ctrl+Shift+V,可以将最近使用的剪贴板内容选择插入到文本 Ctrl+Alt+Shift+V,简单粘贴 Shift+Esc,不仅可以把焦点移到编辑器上,而且还可以隐藏当前(或最后活动的)工具窗口 F12,把焦点从编辑器移到最近使用的工具窗口 Shift+F1,要打开编辑器光标字符处使用的类或者方法 Java 文档的浏览器 Ctrl+W,可以选择单词继而语句继而行继而函数 Ctrl+Shift+W,取消选择光标所在词 Alt+F7,查找整个工程中使用地某一个类、方法或者变量的位置 Ctrl+I,实现方法 Ctrl+Shift+U,大小写转化 Ctrl+Y,删除当前行 Shift+Enter,向下插入新行 psvm/sout,main/System.out.println(); Ctrl+J,查看更多 Ctrl+Shift+F,全局查找 Ctrl+F,查找/Shift+F3,向上查找/F3,向下查找 Ctrl+Shift+S,高级搜索 Ctrl+U,转到父类 Ctrl+Alt+S,打开设置对话框 Alt+Shift+Inert,开启/关闭列选择模式 Ctrl+Alt+Shift+S,打开当前项目/模块属性 Ctrl+G,定位行 Alt+Home,跳转到导航栏 Ctrl+Enter,上插一行 Ctrl+Backspace,按单词删除 Ctrl+”+/-”,当前方法展开、折叠 Ctrl+Shift+”+/-”,全部展开、折叠 【调试部分、编译】 Ctrl+F2,停止 Alt+Shift+F9,选择 Debug Alt+Shift+F10,选择 Run Ctrl+Shift+F9,编译 Ctrl+Shift+F10,运行 Ctrl+Shift+F8,查看断点 F8,步过 F7,步入 Shift+F7,智能步入 Shift+F8,步出 Alt+Shift+F8,强制步过 Alt+Shift+F7,强制步入 Alt+F9,运行至光标处 Ctrl+Alt+F9,强制运行至光标处 F9,恢复程序 Alt+F10,定位到断点 Ctrl+F8,切换行断点 Ctrl+F9,生成项目 Alt+1,项目 Alt+2,收藏 Alt+6,TODO Alt+7,结构 Ctrl+Shift+C,复制路径 Ctrl+Alt+Shift+C,复制引用,必须选择类名 Ctrl+Alt+Y,同步 Ctrl+~,快速切换方案(界面外观、代码风格、快捷键映射等菜单) Shift+F12,还原默认布局 Ctrl+Shift+F12,隐藏/恢复所有窗口 Ctrl+F4,关闭 Ctrl+Shift+F4,关闭活动选项卡 Ctrl+Tab,转到下一个拆分器 Ctrl+Shift+Tab,转到上一个拆分器 【重构】 Ctrl+Alt+Shift+T,弹出重构菜单 Shift+F6,重命名 F6,移动 F5,复制 Alt+Delete,安全删除 Ctrl+Alt+N,内联 【查找】 Ctrl+F,查找 Ctrl+R,替换 F3,查找下一个 Shift+F3,查找上一个 Ctrl+Shift+F,在路径中查找 Ctrl+Shift+R,在路径中替换 Ctrl+Shift+S,搜索结构 Ctrl+Shift+M,替换结构 Alt+F7,查找用法 Ctrl+Alt+F7,显示用法 Ctrl+F7,在文件中查找用法 Ctrl+Shift+F7,在文件中高亮显示用法
项目管理
2018-07-10 21:22:00
【来源:OSCHINA】Confluence 6 配置快速导航
当在 Confluence 中的快速导航进行查找的时候(请查看 Searching Confluence )能够帮助你显示页面下拉列表和其他的项目,这个是通过查找页面标题进行比对的。在默认情况下,这个功能是启用的,并且最大允许用户同时使用这个功能的用户数量被限制为 40。这些参数可以通过下面描述的方法进行修改。 最大数量的快速导航同时搜索功能的数量限制了同时在 Confluence 服务器上使用这个功能的用户数量。如果你的 Confluence 服务器上有大量的独立用户,同时这些用户有都经常使用这个快速搜索功能的话,一些用户可能会被拒绝使用这个功能,这个时候你可以考虑增加使用的限制。 希望配置快速导航: 在屏幕的右上角单击 控制台按钮 ,然后选择 General Configuration 链接。 选择左侧面板中的 进一步配置(Further Configuration) 。 选择 编辑(Edit) 。 希望禁用快速导航功能,取消选择 快速导航(Quick Navigation) 的选择框。 希望修改同时使用快速导航请求的最大数量,在字段 最大同时请求(Max Simultaneous Requests) 输入框中输入相应的值。 选择 保存(Save)。 https://www.cwiki.us/display/CONF6ZH/Configuring+Quick+Navigation
项目管理
2018-07-09 09:22:00
所有的其他数据库,包括有页面,内容都存储在数据库中。如果你安装的 Confluence 是用于评估或者你选择使用的是 Embedded H2 Database 数据库。数据库有关的文件将会存储在 database/ 目录中,这个目录位于 Home 目录下面。否则数据库将会存储你 Confluence 站点所使用的所有数据。 https://www.cwiki.us/display/CONF6ZH/Confluence+Home+and+other+important+directories
项目管理
2018-07-04 00:12:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/context] 本来比较习惯直接用eclipse里面的export成可运行jar包,开始选择的library handling 是第一个选项,结果运行后一直报下面的错误; java -jar testmq.jar 六月 18, 2018 7:11:16 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@17a7cec2: startup date [Mon Jun 18 19:11:16 CST 2018]; root of context hierarchy 六月 18, 2018 7:11:16 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [Producer.xml] Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/context] Offending resource: class path resource [Producer.xml] at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:70) at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85) at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:80) at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.error(BeanDefinitionParserDelegate.java:301) at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1408) at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1401) at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:172) at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:142) at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:94) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:508) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:392) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:304) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:181) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:217) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:252) at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:127) at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:93) at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:129) at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:613) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:514) at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:139) at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:83) at com.autohome.mq.testmq.ProducerMain.main(ProducerMain.java:16) 查询了好多同样的问题,以为是直接export不太对,需要用maven打包,比如看到了下面这个链接https://www.cnblogs.com/icewee/articles/3703491.html,是因为缺什么包导致; 然后换成了mavn打包,结果还是不太对,和我用export导出来的效果一样,查了很久都没有查到原因; 刚开始,用mvn打包,直接都是打成文件的 https://www.cnblogs.com/dzblog/p/6913809.html 按这个方式去打包,结果都生成了别的lib包,没有直接打进jar包;所以只能在当前目录下运行; 然后看到了这篇文章 https://blog.csdn.net/birdben/article/details/51951651 明白了原因,是因为因为的plugin不对; 主要使用了maven-jar-plugin和maven-dependency-plugin这两个Maven的插件,maven-jar-plugin负责构建jar包,maven-dependency-plugin负责导出所有依赖的jar包,这里我们使用了maven-assembly-plugin插件,这个插件可以将所有当前jar依赖的所有jar包打包到当前jar里面,这样就不需要导出所有依赖的jar包了,直接运行jar就可以了,实在太方便了 ^_^ 但是这样还是不行,感觉和export的效果其实是一致的;于是又找了一些文章 https://blog.csdn.net/qing0706/article/details/51612040 ,总算找到了原因;结果也是确实是因为 spring.handlers、spring.schemas,打开这两个文件一看,里面都只包含了spring-mvc的配置 需要在pom的打包后面添加别的插件, 再一想,我用maven和eclipse打包效果一样,是不是eclipse本身就有这样的方法了, 于是,尝试了一下,过然如此; 来源于 https://www.cnblogs.com/jiangwangxiang/p/8596785.html 中间部分
项目管理
2018-06-18 21:33:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Spring Cloud集成项目有很多,下面我们列举一下和Spring Cloud相关的优秀项目,我们的企业架构中用到了很多的优秀项目,说白了,也是站在巨人的肩膀上去整合的。在学习Spring Cloud之前大家必须了解一下相关项目,希望可以帮助到大家。 Spring Cloud Config 配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储、Git以及Subversion。 Spring Cloud Bus ​事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与Spring Cloud Config联合实现热部署。 Eureka 云端服务发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。 Hystrix 熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。 Zuul Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。 Archaius 配置管理API,包含一系列配置管理API,提供动态类型化属性、线程安全配置操作、轮询框架、回调机制等功能。 Consul 封装了Consul操作,consul是一个服务发现与配置工具,与Docker容器可以无缝集成。 Spring Cloud for Cloud Foundry 通过Oauth2协议绑定服务到CloudFoundry,CloudFoundry是VMware推出的开源PaaS云平台。 Spring Cloud Sleuth 日志收集工具包,封装了Dapper和log-based追踪以及Zipkin和HTrace操作,为SpringCloud应用实现了一种分布式追踪解决方案。 Spring Cloud Data Flow 大数据操作工具,作为Spring XD的替代产品,它是一个混合计算模型,结合了流数据与批量数据的处理方式。 Spring Cloud Security 基于spring security的安全工具包,为你的应用程序添加安全控制。 Spring Cloud Zookeeper 操作Zookeeper的工具包,用于使用zookeeper方式的服务发现和配置管理。 Spring Cloud Stream 数据流操作开发包,封装了与Redis,Rabbit、Kafka等发送接收消息。 Spring Cloud CLI 基于 Spring Boot CLI,可以让你以命令行方式快速建立云组件。 Ribbon 提供云端负载均衡,有多种负载均衡策略可供选择,可配合服务发现和断路器使用。 Turbine Turbine是聚合服务器发送事件流数据的一个工具,用来监控集群下hystrix的metrics情况。 Feign Feign是一种声明式、模板化的HTTP客户端。 Spring Cloud Task 提供云端计划任务管理、任务调度。 Spring Cloud Connectors 便于云端应用程序在各种PaaS平台连接到后端,如:数据库和消息代理服务。 Spring Cloud Cluster 提供Leadership选举,如:Zookeeper, Redis, Hazelcast, Consul等常见状态模式的抽象和实现。 Spring Cloud Starters Spring Boot式的启动项目,为Spring Cloud提供开箱即用的依赖管理。 从现在开始,我这边会将近期研发的spring cloud微服务云架构的搭建过程和精髓记录下来,帮助更多有兴趣研发spring cloud框架的朋友,大家来一起探讨spring cloud架构的搭建过程及如何运用于企业项目。
项目管理
2018-06-13 09:35:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 一个c#开发的开源ide 可以支持很多语言但是用来开发c#最爽不过 简单来张图: 下载地址直接百度搜名字就有
项目管理
2018-06-14 12:00:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Paxos算法在分布式领域具有非常重要的地位。但是Paxos算法有两个比较明显的缺点:1.难以理解 2.工程实现更难。 网上有很多讲解Paxos算法的文章,但是质量参差不齐。看了很多关于Paxos的资料后发现,学习Paxos最好的资料是论文《Paxos Made Simple》,其次是中、英文版维基百科对Paxos的介绍。本文试图带大家一步步揭开Paxos神秘的面纱。 Paxos是什么 Paxos算法是基于 消息传递 且具有 高度容错特性 的 一致性算法 ,是目前公认的解决 分布式一致性 问题 最有效 的算法之一。 Google Chubby的作者Mike Burrows说过这个世界上 只有一种 一致性算法,那就是Paxos,其它的算法都是 残次品 。 虽然Mike Burrows说得有点夸张,但是至少说明了Paxos算法的地位。然而,Paxos算法也因为晦涩难懂而臭名昭著。本文的目的就是带领大家深入浅出理解Paxos算法,不仅理解它的执行流程,还要理解算法的推导过程,作者是怎么一步步想到最终的方案的。只有理解了推导过程,才能深刻掌握该算法的精髓。而且理解推导过程对于我们的思维也是非常有帮助的,可能会给我们带来一些解决问题的思路,对我们有所启发。 问题产生的背景 在常见的分布式系统中,总会发生诸如 机器宕机 或 网络异常 (包括消息的延迟、丢失、重复、乱序,还有网络分区)等情况。Paxos算法需要解决的问题就是如何在一个可能发生上述异常的分布式系统中,快速且正确地在集群内部对 某个数据的值 达成 一致 ,并且保证不论发生以上任何异常,都不会破坏整个系统的一致性。 注:这里 某个数据的值 并不只是狭义上的某个数,它可以是一条日志,也可以是一条命令(command)。。。根据应用场景不同, 某个数据的值 有不同的含义。 相关概念 在Paxos算法中,有三种角色: Proposer Acceptor Learners 在具体的实现中,一个进程可能 同时充当多种角色 。比如一个进程可能 既是Proposer又是Acceptor又是Learner 。 还有一个很重要的概念叫 提案(Proposal) 。最终要达成一致的value就在提案里。 注: 暂且 认为『 提案=value 』,即提案只包含value。在我们接下来的推导过程中会发现如果提案只包含value,会有问题,于是我们再对提案 重新设计 。 暂且 认为『 Proposer可以直接提出提案 』。在我们接下来的推导过程中会发现如果Proposer直接提出提案会有问题,需要增加一个学习提案的过程。 Proposer可以提出(propose)提案;Acceptor可以接受(accept)提案;如果某个提案被选定(chosen),那么该提案里的value就被选定了。 回到刚刚说的『对某个数据的值达成一致』,指的是Proposer、Acceptor、Learner都认为同一个value被选定(chosen)。那么,Proposer、Acceptor、Learner分别在什么情况下才能认为某个value被选定呢? Proposer:只要Proposer发的提案被Acceptor接受(刚开始先认为只需要一个Acceptor接受即可,在推导过程中会发现需要半数以上的Acceptor同意才行),Proposer就认为该提案里的value被选定了。 Acceptor:只要Acceptor接受了某个提案,Acceptor就任务该提案里的value被选定了。 Learner:Acceptor告诉Learner哪个value被选定,Learner就认为那个value被选定。 问题描述 假设有一组可以 提出(propose)value (value在提案Proposal里)的 进程集合 。一个一致性算法需要保证提出的这么多value中, 只有一个 value被选定(chosen)。如果没有value被提出,就不应该有value被选定。如果一个value被选定,那么所有进程都应该能 学习(learn) 到这个被选定的value。对于一致性算法, 安全性(safaty) 要求如下: 只有被提出的value才能被选定。 只有一个value被选定,并且 如果某个进程认为某个value被选定了,那么这个value必须是真的被选定的那个。 我们不去精确地定义其 活性(liveness) 要求。我们的目标是保证 最终有一个提出的value被选定 。当一个value被选定后,进程最终也能学习到这个value。 Paxos的目标:保证最终有一个value会被选定,当value被选定后,进程最终也能获取到被选定的value。 假设不同角色之间可以通过发送消息来进行通信,那么: 每个角色以任意的速度执行,可能因出错而停止,也可能会重启。一个value被选定后,所有的角色可能失败然后重启,除非那些失败后重启的角色能记录某些信息,否则等他们重启后无法确定被选定的值。 消息在传递过程中可能出现任意时长的延迟,可能会重复,也可能丢失。但是消息不会被损坏,即消息内容不会被篡改(拜占庭将军问题)。 推导过程 最简单的方案——只有一个Acceptor 假设只有一个Acceptor(可以有多个Proposer),只要Acceptor接受它收到的第一个提案,则该提案被选定,该提案里的value就是被选定的value。这样就保证只有一个value会被选定。 但是,如果这个唯一的Acceptor宕机了,那么整个系统就 无法工作 了! 因此,必须要有 多个Acceptor ! 多个Acceptor 多个Acceptor的情况如下图。那么,如何保证在多个Proposer和多个Acceptor的情况下选定一个value呢? 下面开始寻找解决方案。 如果我们希望即使只有一个Proposer提出了一个value,该value也最终被选定。 那么,就得到下面的约束: P1:一个Acceptor必须接受它收到的第一个提案。 但是,这又会引出另一个问题:如果每个Proposer分别提出不同的value,发给不同的Acceptor。根据P1,Acceptor分别接受自己收到的value,就导致不同的value被选定。出现了不一致。如下图: 刚刚是因为『一个提案只要被一个Acceptor接受,则该提案的value就被选定了』才导致了出现上面不一致的问题。因此,我们需要加一个规定: 规定:一个提案被选定需要被 半数以上 的Acceptor接受 这个规定又暗示了:『一个Acceptor必须能够接受不止一个提案!』不然可能导致最终没有value被选定。比如上图的情况。v1、v2、v3都没有被选定,因为它们都只被一个Acceptor的接受。 最开始讲的『 提案=value 』已经不能满足需求了,于是重新设计提案,给每个提案加上一个提案编号,表示提案被提出的顺序。令『 提案=提案编号+value 』。 虽然允许多个提案被选定,但必须保证所有被选定的提案都具有相同的value值。否则又会出现不一致。 于是有了下面的约束: P2:如果某个value为v的提案被选定了,那么每个编号更高的被选定提案的value必须也是v。 一个提案只有被Acceptor接受才可能被选定,因此我们可以把P2约束改写成对Acceptor接受的提案的约束P2a。 P2a:如果某个value为v的提案被选定了,那么每个编号更高的被Acceptor接受的提案的value必须也是v。 只要满足了P2a,就能满足P2。 但是,考虑如下的情况:假设总的有5个Acceptor。Proposer2提出[M1,V1]的提案,Acceptor2~5(半数以上)均接受了该提案,于是对于Acceptor2~5和Proposer2来讲,它们都认为V1被选定。Acceptor1刚刚从宕机状态恢复过来(之前Acceptor1没有收到过任何提案),此时Proposer1向Acceptor1发送了[M2,V2]的提案(V2≠V1且M2>M1),对于Acceptor1来讲,这是它收到的第一个提案。根据P1(一个Acceptor必须接受它收到的第一个提案。),Acceptor1必须接受该提案!同时Acceptor1认为V2被选定。这就出现了两个问题: Acceptor1认为V2被选定,Acceptor2~5和Proposer2认为V1被选定。出现了不一致。 V1被选定了,但是编号更高的被Acceptor1接受的提案[M2,V2]的value为V2,且V2≠V1。这就跟P2a(如果某个value为v的提案被选定了,那么每个编号更高的被Acceptor接受的提案的value必须也是v)矛盾了。 所以我们要对P2a约束进行强化! P2a是对Acceptor接受的提案约束,但其实提案是Proposer提出来的,所有我们可以对Proposer提出的提案进行约束。得到P2b: P2b:如果某个value为v的提案被选定了,那么之后任何Proposer提出的编号更高的提案的value必须也是v。 由P2b可以推出P2a进而推出P2。 那么,如何确保在某个value为v的提案被选定后,Proposer提出的编号更高的提案的value都是v呢? 只要满足P2c即可: P2c:对于任意的N和V,如果提案[N, V]被提出,那么存在一个半数以上的Acceptor组成的集合S,满足以下两个条件中的任意一个: S中每个Acceptor都没有接受过编号小于N的提案。 S中Acceptor接受过的最大编号的提案的value为V。 Proposer生成提案 为了满足P2b,这里有个比较重要的思想:Proposer生成提案之前,应该先去 『学习』 已经被选定或者可能被选定的value,然后以该value作为自己提出的提案的value。如果没有value被选定,Proposer才可以自己决定value的值。这样才能达成一致。这个学习的阶段是通过一个 『Prepare请求』 实现的。 于是我们得到了如下的 提案生成算法 : Proposer选择一个 新的提案编号N ,然后向 某个Acceptor集合 (半数以上)发送请求,要求该集合中的每个Acceptor做出如下响应(response)。 (a) 向Proposer承诺保证 不再接受 任何编号 小于N的提案 。 (b) 如果Acceptor已经接受过提案,那么就向Proposer响应 已经接受过 的编号小于N的 最大编号的提案 。 我们将该请求称为 编号为N 的 Prepare请求 。 如果Proposer收到了 半数以上 的Acceptor的 响应 ,那么它就可以生成编号为N,Value为V的 提案[N,V] 。这里的V是所有的响应中 编号最大的提案的Value 。如果所有的响应中 都没有提案 ,那 么此时V就可以由Proposer 自己选择 。 生成提案后,Proposer将该 提案 发送给 半数以上 的Acceptor集合,并期望这些Acceptor能接受该提案。我们称该请求为 Accept请求 。(注意:此时接受Accept请求的Acceptor集合 不一定 是之前响应Prepare请求的Acceptor集合) Acceptor接受提案 Acceptor 可以忽略任何请求 (包括Prepare请求和Accept请求)而不用担心破坏算法的 安全性 。因此,我们这里要讨论的是什么时候Acceptor可以响应一个请求。 我们对Acceptor接受提案给出如下约束: P1a:一个Acceptor只要尚 未响应过 任何 编号大于N 的 Prepare请求 ,那么他就可以 接受 这个 编号为N的提案 。 如果Acceptor收到一个编号为N的Prepare请求,在此之前它已经响应过编号大于N的Prepare请求。根据P1a,该Acceptor不可能接受编号为N的提案。因此,该Acceptor可以忽略编号为N的Prepare请求。当然,也可以回复一个error,让Proposer尽早知道自己的提案不会被接受。 因此,一个Acceptor 只需记住 :1. 已接受的编号最大的提案 2. 已响应的请求的最大编号。 Paxos算法描述 经过上面的推导,我们总结下Paxos算法的流程。 Paxos算法分为 两个阶段 。具体如下: 阶段一: (a) Proposer选择一个 提案编号N ,然后向 半数以上 的Acceptor发送编号为N的 Prepare请求 。 (b) 如果一个Acceptor收到一个编号为N的Prepare请求,且N 大于 该Acceptor已经 响应过的 所有 Prepare请求 的编号,那么它就会将它已经 接受过的编号最大的提案(如果有的话) 作为响应反馈给Proposer,同时该Acceptor承诺 不再接受 任何 编号小于N的提案 。 阶段二: (a) 如果Proposer收到 半数以上 Acceptor对其发出的编号为N的Prepare请求的 响应 ,那么它就会发送一个针对 [N,V]提案 的 Accept请求 给 半数以上 的Acceptor。注意:V就是收到的 响应 中 编号最大的提案的value ,如果响应中 不包含任何提案 ,那么V就由Proposer 自己决定 。 (b) 如果Acceptor收到一个针对编号为N的提案的Accept请求,只要该Acceptor 没有 对编号 大于N 的 Prepare请求 做出过 响应 ,它就 接受该提案 。 Learner学习被选定的value Learner学习(获取)被选定的value有如下三种方案: 如何保证Paxos算法的活性 通过选取 主Proposer ,就可以保证Paxos算法的活性。至此,我们得到一个 既能保证安全性,又能保证活性 的 分布式一致性算法 —— Paxos算法 。
项目管理
2019-01-08 15:11:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 注意: 实现类UserServiceImpl,MyUserServiceImpl 需要区分:@Service("userServicel") @Service("myUserService") https://blog.csdn.net/russle/article/details/80287763
项目管理
2018-12-15 12:26:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 在任何时候,如果你希望某一个页面称为你空间的主页,你可以非常容易的从 编辑空间细节(Edit Space Details)标签页中进行修改。 希望编辑空间的细节: 进入空间后,然后从边栏的底部选择 空间工具(Space tools) > 外观和感觉(Look and Feel) 。. 选择 编辑空间细节(Edit Space Details) 。 在 主页面(Home page) 中输入你希望使用的页面,然后单击 保存(Save) 。 你可以修改你空间的主页面,名字和描述。但是你不能修改空间的标识(Space key)。 https://www.cwiki.us/display/CONF6ZH/Set+up+a+Space+Home+Page
项目管理
2018-12-06 04:59:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Spring Boot 可以使用经典的开发工具或者使用安装的命令行工具。不管使用何种方式,你都需要确定你的 Java 版本为 Java SDK v1.8 或者更高的版本。在你开始安装之前,你需要确定你当前安装的 Java 版本满足系统运行的需要。 你可以使用下面的命令进行查看: $ java -version 如果你是 Java 项目开发的新手或者你希望实践使用 Spring Boot。你应该使用 Spring Boot 命令行工具( Spring Boot CLI ),否则的话,请阅读有关经典安装指南。 针对 Java 开发人员的安装指南 对于Java 开发者来说,使用 Spring Boot 就跟使用其他 Java 库一样,只需要在你的 classpath 下引入适当的 spring-boot-*.jar 文件。 Spring Boot不需要集成任何特殊的工具,所以你可以使用任何IDE或文本编辑器;同时,Spring Boot应用也没有什么特殊之处,你可以像对待其他Java程序那样运行,调试它。 尽管可以拷贝 Spring Boot jars,但我们还是更加建议你使用支持依赖管理的构建工具,比如 Maven 或 Gradle。 Maven 安装 Spring Boot 兼容 Apache Maven 3.3 或更高版本。如果本地没有安装Maven,你可以参考 maven.apache.org 上的指南在你本地安装 Maven。 在很多操作系统中,Maven 可以通过包管理器进行安装。如果你使用 OSX Homebrew 操作系统,你可以考虑使用 brwe 安装 Maven。 在 Ubuntu 中,你可以运行 sudo apt-get install 命令来安装 Maven。 Windows 用户,如果你使用了 Chocolatey ,你可以从弹出的管理员控制台中运行 choco install mave n 命令。 Spring Boot依赖使用的 groupId 为 org.springframework.boot 。 通常,你的 Maven POM 文件会继承 spring-boot-starter-parent 工程,并声明一个或多个 Starters 依赖。此外,Spring Boot提供了一个可选的 Maven 插件 ,用于创建可执行的 jars。 下面的 XML 文件中显示了一个常用的 pom.xml 文件。 < project xmlns = " http://maven.apache.org/POM/4.0.0 " xmlns:xsi = " http://www.w3.org/2001/XMLSchema-instance " xsi:schemaLocation = " http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd " > < modelVersion > 4.0.0 < groupId > com.example < artifactId >myproject < version > 0.0.1-SNAPSHOT < parent > < groupId > org.springframework.boot < artifactId >spring-boot-starter-parent < version > 2.1.0.RELEASE < dependencies > < dependency > < groupId > org.springframework.boot < artifactId >spring-boot-starter-web < build > < plugins > < plugin > < groupId > org.springframework.boot < artifactId >spring-boot-maven-plugin 通常来说 spring-boot-starter-parent 是使用 Spring Boot 的一种不错的方式,但它可能并不总是最合适的。有时你可能需要继承一个不同的父 POM,或者不喜欢我们的默认配置。 在这种情况下,你可以使用 import 作用域(import scope)来替代默认的父 POM 继承,具体请查看:这种替代方案,具体查看 Section 13.2.2, “Using Spring Boot without the Parent POM” 页面中的内容。 Gradle 安装 Spring Boot 现在能够兼容 Gradle 4.4 及其后续版本。如果你的系统中还没有安装 Gradle, 你可以参考 gradle.org 页面中的内容。 Spring Boot 的依赖可通过 groupId 为 org.springframework.boot 来进行声明。通常,你的项目将声明一个或多个 “Starters” 依赖。Spring Boot 同时还提供了一个有用的 Gradle plugin 插件。这个插件通常可以用来简化依赖声明和创建可以执行的 jars。 Gradle Wrapper 当你需要构建项目时,Gradle Wrapper提供一种有效的获取 Gradle 的方式。它是一小段脚本和库,跟你的代码一块提交,用于启动构建进程,具体参考页面 docs.gradle.org/4.2.1/userguide/gradle_wrapper.html 中的内容。 更多有关开始使用 Spring Boot 和 Gradle 的细节可以在 Getting Started section 页面中的 Gradle 插件参考指南中找到。 https://www.cwiki.us/display/SpringBootZH/Installing+Spring+Boot
项目管理
2018-12-01 02:22:00
【来源:OSCHINA】手写tomcat+servlet
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 写程序一定要有思路,思路很重要! 一、我们分两步第一步先实现手写tomcat,第二部写servlet 所用技术: 1、soket通信 IO流 2、http请求与相应 3、解析xml 4、java反射技术 导入所需要的jar: dom4j dom4j 1.6.1 项目目录结构: 现在开始我们的第一步,手写tomcat 新建 Request 类: import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class Request { private String method; private String url; public Request(InputStream inputStream) throws IOException { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String[] methodAndUrl = bufferedReader.readLine().split(" "); this.method= methodAndUrl[0]; this.url=methodAndUrl[1]; System.out.println(method); System.out.println(url); } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } } Response 类: import java.io.IOException; import java.io.OutputStream; public class Response { public OutputStream outputStream; public String wirte; public static final String responseHeader="HTTP/1.1 200 \r\n" + "Content-Type: text/html\r\n" + "\r\n"; public Response(OutputStream outputStream) throws IOException { this.outputStream= outputStream; } public String getWirte() { return wirte; } public void setWirte(String wirte) { this.wirte = wirte; } } SocketProcess 类: import java.io.OutputStream; import java.net.Socket; import java.util.Map; public class SocketProcess extends Thread{ protected Socket socket; public SocketProcess(Socket socket){ this.socket = socket; } @Override public void run() { try { Request request = new Request(socket.getInputStream()); Response response = new Response(socket.getOutputStream()); // Map map = Mytomcat.servletMapping; // // System.out.println("map大小为:"+map.size()); // for (Map.Entry entry : map.entrySet()) { // System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()); // } //从映射中找 System.out.println(request.getUrl()); String servelName = (String) Mytomcat.servletMapping.get(request.getUrl()); System.out.println(servelName); if(servelName!=null && !servelName.isEmpty()) { //映射有的话找到对应的对象 Servelt servlet = (Servelt) Mytomcat.servlet.get(servelName); if(servlet!=null) { servlet.doGet(request, response); }else { System.out.println("找不到对应的servlet"); } }else { System.out.println("找不到对应的servletMapping"); } String res = Response.responseHeader+response.getWirte(); OutputStream outputStream = socket.getOutputStream(); outputStream.write(res.getBytes("GBK")); outputStream.flush(); outputStream.close(); }catch (Exception ex){ ex.printStackTrace(); }finally { if (socket != null) { try { socket.close(); } catch (Exception e) { e.printStackTrace(); } } } } } Mytomcat import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.List; import org.dom4j.Element; public class Mytomcat { private static final int port = 8099; public static final HashMap servletMapping = new HashMap(); public static final HashMap servlet = new HashMap(); private void init() { InputStream io = null; String basePath; try { System.out.println("加载配置文件开始"); //读取web.xml UtilsXml xml = new UtilsXml(UtilsXml.class.getResource("/")+"web.xml"); //讲所有的类都存储到容器中 并且创造对象 List list = xml.getNodes("servlet"); for (Element element : list) { servlet.put(element.element("servlet-name").getText(), Class.forName(element.element("servlet-class").getText()).newInstance()); } //映射关系创建 List list2 = xml.getNodes("servlet-mapping"); for (Element element : list2) { servletMapping.put(element.element("url-pattern").getText(), element.element("servlet-name").getText()); } System.out.println("加载配置文件结束"); } catch (Exception ex) { ex.printStackTrace(); } finally { if (io != null) { try { io.close(); } catch (Exception e) { e.printStackTrace(); } } } } private void start() { try { ServerSocket serverSocket = new ServerSocket(port); System.out.println("Tomcat 服务已启动,地址:localhost ,端口:" + port); this.init(); //持续监听 do { Socket socket = serverSocket.accept(); System.out.println(socket); //处理任务 Thread thread = new SocketProcess(socket); thread.start(); } while (true); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { Mytomcat tomcat = new Mytomcat(); tomcat.start(); } } UtilsXml: import java.util.List; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; public class UtilsXml { //定义解析器和文档对象 public SAXReader saxReader; public Document document; public UtilsXml(String path){ //获取解析器 saxReader = new SAXReader(); try { //获取文档对象 document = saxReader.read(path); } catch (DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 根据节点名称获取内容 * @param name 节点名称 * @return 节点内容 */ public String getElementText(String name){ //定位根节点 Element root = document.getRootElement(); List mapp = root.elements("servlet-mapping"); List servlet = root.elements("servlet"); String serveltName = ""; String classpath = ""; for (Element e : mapp) { // System.out.println(e.element("url-pattern").getText()); if(e.element("url-pattern").getText().equals(name)){ serveltName = e.element("servlet-name").getText(); break; } } for (Element e : servlet) { // System.out.println(e.element("servlet-name").getText()); if(e.element("servlet-name").getText().equals(serveltName)){ classpath = e.element("servlet-class").getText(); break; } } return classpath; // //根据名称定位节点 // Element element = root.element(name); // //返回节点内容 // return element.getText(); } /** * 获取节点下的所有节点 * @param root * @param name * @return */ public List getNodes(String name){ Element root = document.getRootElement(); return root.elements(name); } public static void main(String[] args) { UtilsXml xml = new UtilsXml(UtilsXml.class.getResource("/")+"web.xml"); //System.out.println(xml.getElementText("/myhtml.html")); List list = xml.getNodes("servlet"); for (Element element : list) { System.out.println(element.element("servlet-name").getText() ); System.out.println(element.element("servlet-class").getText() ); } } Servelt 抽象类: import com.siyuan.http.Request; import com.siyuan.http.Response; public abstract class Servelt { public void service(Request request, Response response) { //判断是调用doget 还是 dopost if ("get".equalsIgnoreCase(request.getMethod())) { this.doGet(request, response); } else { this.doPost(request, response); } } public abstract void doGet(Request request, Response response); public abstract void doPost(Request request, Response response); } 第一个servlet : import com.siyuan.http.Request; import com.siyuan.http.Response; public class MyfisrtServlet extends Servelt { @Override public void doGet(Request request, Response response) { System.out.println("进入了我的第一个servlet"); response.setWirte("进入了第一个servlet"); } @Override public void doPost(Request request, Response response) { } } 第二个servlet: import com.siyuan.http.Request; import com.siyuan.http.Response; public class ScoendServlet extends Servelt{ @Override public void doGet(Request request, Response response) { System.out.println("进入了我的第二个servlet"); response.setWirte("进入了第二个servlet"); } @Override public void doPost(Request request, Response response) { // TODO Auto-generated method stub } } 新建web.xml myhtml.html com.siyuan.servlet.MyfisrtServlet myhtml.html /myhtml.html myhtml2.html com.siyuan.servlet.ScoendServlet myhtml2.html /myhtml2.html 运行效果: 有问题讨论可以加群一起讨论: 600922504
项目管理
2018-11-30 16:17:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 上一篇文章我们介绍了eureka服务注册中心的搭建,这篇文章介绍一下如何使用eureka服务注册中心,搭建一个简单的服务端注册服务,客户端去调用服务使用的案例。愿意了解源码的朋友直接求求交流分享技术:二一四七七七五六三三 案例中有三个角色:服务注册中心、服务提供者、服务消费者,其中服务注册中心就是我们上一篇的eureka单机版启动既可,流程是首先启动注册中心,服务提供者生产服务并注册到服务中心中,消费者从服务中心中获取服务并执行。 服务提供 我们假设服务提供者有一个hello方法,可以根据传入的参数,提供输出“hello ,this is first messge”的服务 1、pom包配置 创建一个springboot项目,pom.xml中添加如下配置: org.springframework.cloud spring-cloud-starter-eureka org.springframework.boot spring-boot-starter-test test 2、配置文件 application.properties配置如下: spring.application.name=spring-cloud-producer server.port=9000 eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/ 3、启动类 启动类中添加 @EnableDiscoveryClient 注解 @SpringBootApplication @EnableDiscoveryClient public class ProducerApplication { public static void main(String[] args) { SpringApplication.run(ProducerApplication.class, args); } } 4、controller 提供hello服务 @RestController public class HelloController { @RequestMapping("/hello") public String index(@RequestParam String name) { return "hello "+name+",this is first messge"; } } 添加 @EnableDiscoveryClient 注解后,项目就具有了服务注册的功能。启动工程后,就可以在注册中心的页面看到SPRING-CLOUD-PRODUCER服务。 到此服务提供者配置就完成了。 服务调用 1、pom包配置 和服务提供者一致 org.springframework.cloud spring-cloud-starter-eureka org.springframework.boot spring-boot-starter-test test 2、配置文件 application.properties配置如下: spring.application.name=spring-cloud-consumer server.port=9001 eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/ 3、启动类 启动类添加 @EnableDiscoveryClient 和 @EnableFeignClients 注解。 @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } } @EnableDiscoveryClient :启用服务注册与发现 @EnableFeignClients:启用feign进行远程调用 Feign是一个声明式Web Service客户端。使用Feign能让编写Web Service客户端更加简单, 它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。 4、feign调用实现 @FeignClient(name= "spring-cloud-producer") public interface HelloRemote { @RequestMapping(value = "/hello") public String hello(@RequestParam(value = "name") String name); } name:远程服务名,及spring.application.name配置的名称 此类中的方法和远程服务中contoller中的方法名和参数需保持一致。 5、web层调用远程服务 将HelloRemote注入到controller层,像普通方法一样去调用即可。 @RestController public class ConsumerController { @Autowired HelloRemote HelloRemote; @RequestMapping("/hello/{name}") public String index(@PathVariable("name") String name) { return HelloRemote.hello(name); } } 到此,最简单的一个服务注册与调用的例子就完成了。 测试 简单调用 依次启动spring-cloud-eureka、spring-cloud-producer、spring-cloud-consumer三个项目 先输入:http://localhost:9000/hello?name=neo 检查spring-cloud-producer服务是否正常 返回:hello neo,this is first messge 说明spring-cloud-producer正常启动,提供的服务也正常。 浏览器中输入:http://localhost:9001/hello/neo 返回:hello neo,this is first messge 说明客户端已经成功的通过feign调用了远程服务hello,并且将结果返回到了浏览器。 负载均衡 以上面spring-cloud-producer为例子修改,将其中的controller改动如下: @RestController public class HelloController { @RequestMapping("/hello") public String index(@RequestParam String name) { return "hello "+name+",this is producer 2 send first messge"; } } 在配置文件中改动端口: spring.application.name=spring-cloud-producer server.port=9003 eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/ 打包启动后,在eureka就会发现两个服务提供者,如下图: 然后在浏览器再次输入:http://localhost:9001/hello/neo 进行测试: 第一次返回结果:hello neo,this is first messge 第二次返回结果:hello neo,this is producer 2 send first messge 不断的进行测试下去会发现两种结果交替出现,说明两个服务中心自动提供了服务均衡负载的功能。如果我们将服务提供者的数量在提高为N个,测试结果一样,请求会自动轮询到每个服务端来处理。 整体代码结构如下:
项目管理
2018-11-27 10:21:00
【来源:OSCHINA】Shiro框架
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 提供了认证,授权,加密,会话管理等功能 在spring配置文件中配置shiro,需要配置的有shiro的过滤器工厂,在里面我们可以配置什么页面需要认证,什么认证不需要认证,认证成功后跳转的路径,认证失败跳转的路径,还有授权校验失败跳转的路径。
项目管理
2018-11-20 16:07:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 介绍 鸿鹄云架构【SSO单点登录系统】为所有微服务提供统一的用户认证服务,系统本身属于微服务模式,使用JWT+Redis分布式存储方案,确保不同微服务、系统之间的安全通讯和统一用户校验、认证。在整个服务平台中起着用户枢纽中心的作用。 平台基础功能 用户注册&登录、用户登录&校验(APP)、用户登录&校验(PC)、用户登出、用户密码修改、用户密码重置 运行环境支持 开发工具:Eclipse、MyEclipse、Idea WEB容器:内置Tomcat JDK版本:1.8+ 系统支持:Window、Linux 数据库:Mysql、Alibaba Druid 分布式服务:SpringCloud、Eureka、Config 服务框架:SpringBoot、SpringMVC、Mybatis Plus、Restful、Token认证 构建方式:Maven、Jenkins 前端架构:Bootstrap 4、H5、CSS3 后面的章节我们详细介绍一下每个平台的使用和规划,希望可以帮助到大家!
项目管理
2018-11-19 10:01:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Spring Cloud是一系列框架的有序集合。利用Spring Boot的开发模式简化了分布式系统基础设施的开发,如 服务发现、注册、配置中心、消息总线、负载均衡、断路器、数据监控 等(这里只简单的列了一部分),都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud将目前比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装,屏蔽掉了复杂的配置和实现原理,最终整合出一套简单易懂、易部署和易维护的分布式系统架构平台。 Spring Cloud组成 Spring Cloud的子项目,大致可分成两类: 一类是对现有成熟框架Spring Boot的封装和抽象,也是数量最多的项目; 第二类是开发了一部分分布式系统的基础设施的实现,如Spring Cloud Stream就是kafka, ActiveMQ这样的角色。开发人员进行 微服务 的实践,第一类子项目就已经足够使用,如: Spring Cloud Netflix   是对Netflix开发的一套分布式服务框架的封装,包括服务的发现和注册,负载均衡、断路器、REST客户端、请求路由等。 Spring Cloud Config   将配置信息中央化保存, 配置Spring Cloud Bus可以实现动态修改配置文件。 Spring Cloud Bus   分布式消息队列,是对Kafka, MQ的封装。 Spring Cloud Security   对Spring Security的封装,并能配合Netflix使用。 Spring Cloud Zookeeper   对Zookeeper的封装,使之能配置其它Spring Cloud的子项目使用。 Spring Cloud Eureka Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件中的一部分,它基于Netflix Eureka 做了二次分装,主要负责完成微服务架构中的服务治理功能。 Spring Cloud未来 Spring Cloud为未来互联网企业提供分布式基础设施解决方案。同时,随着近几年微服务架构和Docker容器概念的火爆,也会让Spring Cloud在未来越来越“云”化的软件开发风格中立有一席之地,尤其是在目前五花八门的分布式解决方案中提供了标准化的、全站式的技术方案,有效推进服务端软件系统技术水平提升。 从现在开始,我这边会将近期研发的spring cloud微服务云架构的搭建过程和精髓记录下来,帮助更多有兴趣研发spring cloud框架的朋友,大家来一起探讨spring cloud架构的搭建过程及如何运用于企业项目。
项目管理
2018-11-01 15:18:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 环境搭建 本文环境指的 Spring Boot下1.4.2版本下 pom.xml (核心内容) org.springframework.boot spring-boot-starter-parent 1.4.2.RELEASE org.springframework.boot spring-boot-starter-web 1 2 3 4 5 6 7 8 9 10 11 12 如果单纯想用RestTemplate 引上面的应该是多余了,博主没去查阅具体那个包,可以自行去Maven 中央仓库搜索有没有专门的RestTemplate包 RestTemplate有简写方法,但不具备通用性且无法做细节设置故本文不再赘述 举例: restTemplate.getForObject(url, String.class); // 向url发送 Get类型请求,将返回结果 以String类型显示 // 能很明显发现无法设置编码格式等,乱码等问题无法解决 1 2 3 有兴趣可访问,下文只提通用性方法 RestTemplate 官方 API 注意 默认的 RestTemplate 有个机制是请求状态码非200 就抛出异常,会中断接下来的操作,如果要排除这个问题那么需要覆盖默认的 ResponseErrorHandler ,下面为用什么也不干覆盖默认处理机制 RestTemplate restTemplate = new RestTemplate(); ResponseErrorHandler responseErrorHandler = new ResponseErrorHandler() { @Override public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { return true; } @Override public void handleError(ClientHttpResponse clientHttpResponse) throws IOException { } }; restTemplate.setErrorHandler(responseErrorHandler); 1 2 3 4 5 6 7 8 9 10 11 12 13 实例 application/json类型请求 RestTemplate restTemplate=new RestTemplate(); String url="http://www.testXXX.com"; /* 注意:必须 http、https……开头,不然报错,浏览器地址栏不加 http 之类不出错是因为浏览器自动帮你补全了 */ HttpHeaders headers = new HttpHeaders(); /* 这个对象有add()方法,可往请求头存入信息 */ headers.setContentType(MediaType.APPLICATION_JSON_UTF8); /* 解决中文乱码的关键 , 还有更深层次的问题 关系到 StringHttpMessageConverter,先占位,以后补全*/ HttpEntity entity = new HttpEntity(body, headers); /* body是Http消息体例如json串 */ restTemplate.exchange(url, HttpMethod.POST, entity, String.class); /*上面这句返回的是往 url发送 post请求 请求携带信息为entity时返回的结果信息 String.class 是可以修改的,其实本质上就是在指定反序列化对象类型,这取决于你要怎么解析请求返回的参数*/ 1 2 3 4 5 6 7 8 9 10 11 12 另post、get、delete……等其他http请求类型只需将exchange参数修改为HttpMethod.GET 等等。 application/x-www-form-urlencoded类型请求 RestTemplate restTemplate=new RestTemplate(); /* 注意:必须 http、https……开头,不然报错,浏览器地址栏不加 http 之类不出错是因为浏览器自动帮你补全了 */ String url="http://www.testXXX.com"; String bodyValTemplate = "var1=" + URLEncoder.encode("测试数据1", "utf-8") + "&var2=" + URLEncoder.encode("test var2", "utf-8"); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); HttpEntity entity = new HttpEntity(bodyValTemplate, headers); restTemplate.exchange(url, HttpMethod.POST, entity, String.class); 1 2 3 4 5 6 7 8 9 另一种构造 HttpEntity 的方法 LinkedMultiValueMap body=new LinkedMultiValueMap(); body.add("var1","测试数据1"); body.add("var2","test Val2"); HttpEntity entity = new HttpEntity(body, headers); 1 2 3 4 上方请求在PostMan里等效于图 RestTemplate还有专门支持异步请求的特殊类 AsyncRestTemplate,具体用法和RestTemplate类似(需要 ListenableFutureCallback 基础) --------------------- 作者:SolidCocoi 来源:CSDN 原文:https://blog.csdn.net/u014430366/article/details/65633679?utm_source=copy 版权声明:本文为博主原创文章,转载请附上博文链接!
项目管理
2018-10-17 09:28:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 在Java Web开发中,我们通常需要通过GET、POST请求其他系统提供的服务。其中, JDK自带的HttpURLConnection 、 Apache HttpClient 等方式都可以实现。当然,这些方式都有一个很明显的缺陷,那就是代码很繁琐。而Spring提供的RestTemplate封装了这些库的实现,可以让我们的HTTP请求更加简洁、直观。 在RestTemplate中定义了11个独立的操作,它们分别是: 方法 描述 delete() 在特定的URL上对资源执行HTTP DELETE操作 exchange() 在URL上执行特定的HTTP方法,返回的ResponseEntity包含了响应体所映射成的对象 execute() 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象 getForEntity() 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象 getForObject() 发送一个HTTP GET请求,返回根据响应体映射形成的对象 postForEntity() POST数据到一个URL,返回的ResponseEntity包含了响应体所映射成的对象 postForLocation() POST数据到一个URL,返回新创建资源的URL postForObject() POST数据到一个URL,返回根据响应体映射形成的对象 put() PUT资源到特定的URL headForHeaders() optionsForAllow() 发送HTTP HEAD请求,返回包含特定资源URL的HTTP头 发送HTTP OPTIONS请求,返回对特定URL的Allow头信息 接下来,我将对常用的几个方法分别介绍。 (1)在项目中添加依赖: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider com.fasterxml.jackson.dataformat jackson-dataformat-xml org.apache.httpcomponents httpclient com.alibaba fastjson 1.2.46 (2)在项目中注入RestTemplate: 在注入RestTemplate的bean的时候,可以通过ClientHttpRequestFactory指定RestTemplate发起HTTP请求的底层实现所采用的类库。对此,ClientHttpRequestFactory接口主要提供了以下两种实现方法: i)SimpleClientHttpRequestFactory: 也就是底层使用java.net包提供的方式创建Http连接请求。示例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package cn.zifangsky.springbootdemo.config; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.web.client.RestTemplate; @Configuration public class RestTemplateConfig { /** * 返回RestTemplate * @param factory * @return */ @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory){ //消息转换器,一般情况下可以省略,只需要添加相关依赖即可 // List> messageConverters = new ArrayList<>(); // messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); // messageConverters.add(new FormHttpMessageConverter()); // messageConverters.add(new MappingJackson2XmlHttpMessageConverter()); // messageConverters.add(new MappingJackson2HttpMessageConverter()); RestTemplate restTemplate = new RestTemplate(factory); // restTemplate.setMessageConverters(messageConverters); return restTemplate; } /** * ClientHttpRequestFactory接口的第一种实现方式,即: * SimpleClientHttpRequestFactory:底层使用java.net包提供的方式创建Http连接请求 * @return */ @Bean public SimpleClientHttpRequestFactory simpleClientHttpRequestFactory(){ SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); requestFactory.setReadTimeout(5000); requestFactory.setConnectTimeout(5000); return requestFactory; } } ii)HttpComponentsClientHttpRequestFactory(推荐使用): 也就是底层使用Httpclient连接池的方式创建Http连接请求。示例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 package cn.zifangsky.springbootdemo.config; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.http.Header; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy; import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicHeader; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.web.client.RestTemplate; @Configuration public class RestTemplateConfig { /** * 返回RestTemplate * @param factory * @return */ @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory){ //消息转换器,Spring Boot环境可省略,只需要添加相关依赖即可 // List> messageConverters = new ArrayList<>(); // messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); // messageConverters.add(new FormHttpMessageConverter()); // messageConverters.add(new MappingJackson2XmlHttpMessageConverter()); // messageConverters.add(new MappingJackson2HttpMessageConverter()); RestTemplate restTemplate = new RestTemplate(factory); // restTemplate.setMessageConverters(messageConverters); return restTemplate; } /** * ClientHttpRequestFactory接口的另一种实现方式(推荐使用),即: * HttpComponentsClientHttpRequestFactory:底层使用Httpclient连接池的方式创建Http连接请求 * @return */ @Bean public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory(){ //Httpclient连接池,长连接保持30秒 PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS); //设置总连接数 connectionManager.setMaxTotal(1000); //设置同路由的并发数 connectionManager.setDefaultMaxPerRoute(1000); //设置header List
headers = new ArrayList
(); headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04")); headers.add(new BasicHeader("Accept-Encoding", "gzip, deflate")); headers.add(new BasicHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3")); headers.add(new BasicHeader("Connection", "keep-alive")); //创建HttpClient HttpClient httpClient = HttpClientBuilder.create() .setConnectionManager(connectionManager) .setDefaultHeaders(headers) .setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)) //设置重试次数 .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) //设置保持长连接 .build(); //创建HttpComponentsClientHttpRequestFactory实例 HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); //设置客户端和服务端建立连接的超时时间 requestFactory.setConnectTimeout(5000); //设置客户端从服务端读取数据的超时时间 requestFactory.setReadTimeout(5000); //设置从连接池获取连接的超时时间,不宜过长 requestFactory.setConnectionRequestTimeout(200); //缓冲请求数据,默认为true。通过POST或者PUT大量发送数据时,建议将此更改为false,以免耗尽内存 requestFactory.setBufferRequestBody(false); return requestFactory; } } (3)使用getForObject()方法发起GET请求: getForObject()方法实际上是对getForEntity()方法的进一步封装,二者用法类似。 唯一的区别在于getForObject()方法只返回所请求类型的对象, 而getForEntity()方法会返回请求的对象以及响应的Header、响应状态码等额外信息。 三个getForObject()方法的签名如下: 1 2 3 4 5 T getForObject(String url, Class responseType, Object... uriVariables) throws RestClientException; T getForObject(String url, Class responseType, Map uriVariables) throws RestClientException; T getForObject(URI url, Class responseType) throws RestClientException; 示例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package cn.zifangsky.SpringBootDemo.controller; import java.util.HashMap; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.web.client.RestTemplate; import cn.zifangsky.springbootdemo.config.RestTemplateConfig; import cn.zifangsky.springbootdemo.config.WebMvcConfig; import cn.zifangsky.springbootdemo.model.DemoObj; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes={WebMvcConfig.class,RestTemplateConfig.class}) @WebAppConfiguration("src/main/resources") public class TestRestTemplate { @Autowired private RestTemplate restTemplate; /** * 测试最基本的Get请求 */ @Test public void testGetMethod1(){ DemoObj obj = restTemplate.getForObject("http://127.0.0.1:9090/rest/testJson2?id={1}&name={2}" , DemoObj.class , 1,"Tom"); System.out.println(obj); } } 注:本篇文章的单元测试请求的REST服务均来源于上一篇文章: https://www.zifangsky.cn/1215.html 上面代码设置请求参数使用了数字占位符,同时getForObject()方法的最后一个参数是一个可变长度的参数,用于一一替换前面的占位符。当然,除了这种方式之外,还可以使用Map来设置参数,比如: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /** * 测试Get请求另一种设置参数的方式 */ @Test public void testGetMethod2(){ Map uriVariables = new HashMap(); uriVariables.put("var_id", "1"); uriVariables.put("var_name", "Tom"); DemoObj obj = restTemplate.getForObject("http://127.0.0.1:9090/rest/testJson2?id={var_id}&name={var_name}" , DemoObj.class , uriVariables); System.out.println(obj); } 运行单元测试之后,最后输出如下: DemoObj [id=2, name=Tom Ret] 此外需要注意的是,由于上面代码只是简单的单元测试,因此请求URL就直接硬编码在代码中了。实际开发则需要将之配置到配置文件或者Zookeeper、Redis中。比如这样: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package cn.zifangsky.springbootdemo.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import cn.zifangsky.springbootdemo.model.DemoObj; @RestController @RequestMapping("/restTemplate") public class RestTemplateController { @Value("${SERVER_URL}") private String SERVER_URL; @Autowired private RestTemplate restTemplate; @RequestMapping(path="/getDemoObj",produces={MediaType.APPLICATION_JSON_UTF8_VALUE}) public DemoObj getDemoObj(){ DemoObj obj = restTemplate.getForObject(SERVER_URL + "/rest/testXML?id={1}&name={2}" , DemoObj.class , 1,"Tom"); return obj; } } (4)使用getForEntity()方法发起GET请求: 三个getForEntity()方法的签名如下: 1 2 3 4 5 ResponseEntity getForEntity(String url, Class responseType, Object... uriVariables) throws RestClientException; ResponseEntity getForEntity(String url, Class responseType, Map uriVariables) throws RestClientException; ResponseEntity getForEntity(URI url, Class responseType) throws RestClientException; 示例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /** * 测试Get请求返回详细信息,包括:响应正文、响应状态码、响应Header等 */ @Test public void testGetMethod3(){ ResponseEntity responseEntity = restTemplate.getForEntity("http://127.0.0.1:9090/rest/testJson2?id={1}&name={2}" , DemoObj.class , 1,"Tom"); DemoObj body = responseEntity.getBody(); int statusCodeValue = responseEntity.getStatusCodeValue(); HttpHeaders headers = responseEntity.getHeaders(); System.out.println("responseEntity.getBody():" + body); System.out.println("responseEntity.getStatusCodeValue():" + statusCodeValue); System.out.println("responseEntity.getHeaders():" + headers); } 运行单元测试之后,最后输出如下: responseEntity.getBody():DemoObj [id=2, name=Tom Ret] responseEntity.getStatusCodeValue():200 responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:22:28 GMT], Content-Type=[application/json;charset=utf-8], Transfer-Encoding=[chunked]} (5)使用postForObject()方法发起POST请求: 在RestTemplate中,POST请求跟GET请求类似,也可以使用如下三个方法来请求: 1 2 3 4 5 6 7 T postForObject(String url, Object request, Class responseType, Object... uriVariables) throws RestClientException; T postForObject(String url, Object request, Class responseType, Map uriVariables) throws RestClientException; T postForObject(URI url, Object request, Class responseType) throws RestClientException; 示例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 /** * 测试最基本的Post请求 */ @Test public void testPostMethod1(){ DemoObj request = new DemoObj(1l, "Tim"); DemoObj obj = restTemplate.postForObject("http://127.0.0.1:9090/rest/testJson1" , request, DemoObj.class); System.out.println(obj); } 运行单元测试之后,最后输出如下: DemoObj [id=2, name=Tim Ret] (6)使用postForEntity()方法发起POST请求: 三个postForEntity()方法的签名如下: 1 2 3 4 5 6 7 ResponseEntity postForEntity(String url, Object request, Class responseType, Object... uriVariables) throws RestClientException; ResponseEntity postForEntity(String url, Object request, Class responseType, Map uriVariables) throws RestClientException; ResponseEntity postForEntity(URI url, Object request, Class responseType) throws RestClientException; 示例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /** * 测试Post请求返回详细信息,包括:响应正文、响应状态码、响应Header等 */ @Test public void testPostMethod2(){ DemoObj request = new DemoObj(1l, "Tim"); ResponseEntity responseEntity = restTemplate.postForEntity("http://127.0.0.1:9090/rest/testJson1" , request, DemoObj.class); DemoObj body = responseEntity.getBody(); int statusCodeValue = responseEntity.getStatusCodeValue(); HttpHeaders headers = responseEntity.getHeaders(); System.out.println("responseEntity.getBody():" + body); System.out.println("responseEntity.getStatusCodeValue():" + statusCodeValue); System.out.println("responseEntity.getHeaders():" + headers); } 运行单元测试之后,最后输出如下: responseEntity.getBody():DemoObj [id=2, name=Tim Ret] responseEntity.getStatusCodeValue():200 responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:32:02 GMT], Content-Type=[application/json;charset=utf-8], Transfer-Encoding=[chunked]} (7)使用exchange()方法执行指定的HTTP请求: exchange()方法跟上面的getForObject()、getForEntity()、postForObject()、postForEntity()等方法不同之处在于它可以指定请求的HTTP类型。示例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /** * 测试Exchange请求 */ @Test public void testExchange(){ //设置header HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "application/x-zifangsky"); //设置参数 String requestBody = "1#Converter"; HttpEntity requestEntity = new HttpEntity(requestBody,headers); ResponseEntity responseEntity = restTemplate.exchange("http://127.0.0.1:9090/convert" , HttpMethod.POST, requestEntity, String.class); System.out.println("responseEntity.getBody():" + responseEntity.getBody()); System.out.println("responseEntity.getHeaders():" + responseEntity.getHeaders()); } 运行单元测试之后,最后输出如下: responseEntity.getBody():{“id”:2,”name”:”Converter Ret”} responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:42:29 GMT], Content-Type=[application/x-zifangsky], Transfer-Encoding=[chunked]} 参考: https://segmentfault.com/a/1190000011093597 http://rensanning.iteye.com/blog/2362105
项目管理
2018-10-17 09:25:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Amitabha n. <梵>(佛)阿弥陀佛 佛曰: 因果 那么一天 看见文章1. 发现还有2.这么个中间件 于是追寻源码,发现了3. 再然后寻思自己也可以写一个什么中间件,以便学习掌握 于是便有了 Amitabha 因果线 1. asp.net core 教程(六)-中间件 2. Microsoft.AspNetCore.Diagnostics 3. https://github.com/aspnet/Diagnostics 4. WelcomePage 获取 建议通过 NuGet 获取包并使用 当然通过 Source Code 获取使用也是可以的 示例 新建一个空的 ASP.NET Core Web 应用程序 获取 Amitabha 在 Startup.cs 中添加以下内容,运行后,就能看见我佛了... // Startup.cs using Amitabha; public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // ... app.UseAmitabha(); // ... }
项目管理
2018-09-30 10:39:00
【来源:OSCHINA】C++获取系统盘符极简方法
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 盘符获取 std::shared_ptr> disks(void) { wchar_t data[255]; return [&](int length)->std::shared_ptr> { auto result = std::make_shared< std::list>(); std::wstring disk = L"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; for (int i = 0; i < length; i++) { if (std::wstring::npos != disk.find(data[i])) { result->push_back(data[i]); } } return result; } (GetLogicalDriveStrings(sizeof(data), data)); }
项目管理
2018-09-25 17:00:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 最近,美国一个程序员因为同事不写注释,代码不规范,最严重的是天天使用 git push -f 参数强行覆盖仓库,该程序员忍无可忍向四名同事开抢,其中一人情况危急!!! (此信息后来证实枪击事件的确发生,但并非代码原因,但从另外的角度也可看出强推代码所引发的后果让人非常愤怒) 不写注释、代码不规范是一个非常普遍的问题,其严重性还不足以导致枪击事件发生,毕竟算是个人行为,不会对别人的工作产生大的破坏作用。但是 git push -f 的仓库强推参数,则直接导致别人辛辛苦苦编写的代码付之一炬。这种被删代码的愤怒之心想必有过此遭遇的人都深有体会。 但是我们在谴责这种强行推送仓库的行为之时也应该注意到,有挺大一部分开发人员对 -f 参数所产生的破坏并不知晓,另外也可能可能存在一些无心的误操作。而我们宁愿相信绝大多数人并不会恶意强行覆盖同事的仓库,他们只是在遇到代码冲突时无所适从,再加上网上一些文章的误导,只要能解决推送,就不顾及任何后果。 由于很多用户跟我们反馈各种因为强推导致仓库被重置、代码被删除、提交记录消失等问题,甚至还有用户直接甩锅给平台,认为是平台的故障导致他们仓库出现问题,这让我们意识到不应该再做壁上观,于是码云限制强推的功能就推出了: 珍爱生命,远离强推。想了解更多关于“限制强推”的功能请访问 https://blog.gitee.com/2018/08/09/git_push_unallowed/
项目管理
2018-09-23 09:53:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 将一些必须必要条件准备好 1,安装chorme浏览器 1,安装依赖 sudo apt-get install libxss1 libappindicator1 libindicator7 2,下载google安装包 wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 3,安装 sudo dpkg -i google-chrome*.deb sudo apt-get install -f 2,安装chormedrive 1,安装xvfb以便我们可以无头奔跑地运行Chrome sudo apt-get install xvfb 2,安装依赖 sudo apt-get install unzip 3,下载安装包 wget -N http://chromedriver.storage.googleapis.com/2.26/chromedriver_linux64.zip 4,解压缩+添加执行权限 unzip chromedriver_linux64.zip 5,移动 sudo mv -f chromedriver /usr/local/share/chromedriver 6,建立软连接 sudo ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver sudo ln -s /usr/local/share/chromedriver /usr/bin/chromedriver 3,安装python依赖 1,安装Python依赖 pip3 install selenium 百度的模拟登录 ''' 百度的模拟登录,但手机的验证码只能手动输入 ''' from selenium import webdriver import time sel = webdriver.Chrome() myurl = "https://www.baidu.com/" sel.get(myurl) #找到登录节点,进行登录 try: sel.find_element_by_css_selector("#u1 a:nth-child(7)").click() print("click success!!") except: print("click failed") #js页面加载出来的页面必须要有time.sleep来取加载页面 # 等待页面加载时间 time.sleep(2) #找到用户名登录的节点,进行登录 try: sel.find_element_by_css_selector("p#TANGRAM__PSP_10__footerULoginBtn").click() print("click username login success!") except: print("not find username login!") #设置休眠时间.模拟用户点击 time.sleep(1) #查找手机号输入框,username就是自己要登录的手机帐号 try: sel.find_element_by_css_selector("#TANGRAM__PSP_10__userName").send_keys("username") print("input success!") except: print("input fail please input again") #查找密码输入框,进行输入密码,password就是对应的密码 try: sel.find_element_by_css_selector("#TANGRAM__PSP_10__password").send_keys("password") print("input password success") except: print("input password failed") #点击登录 try: sel.find_element_by_css_selector("#TANGRAM__PSP_10__submit").click() print("click success!") except: print("click failed") time.sleep(1) #点击发送验证码 try: sel.find_element_by_css_selector("#TANGRAM__36__button_send_mobile").click() print("send information to your moblephone") except: print("send information failed") time.sleep(1) #如何得到验证码并输入 #手机验证码暂时没有办法自动获取到,只能手动输入,这个问题主要是由于设置的安全登录引起的 try: info = input("输入验证码:") sel.find_element_by_css_selector("#TANGRAM__36__input_vcode").send_keys(info) print("input success") except: print("input failed") time.sleep(1) ###点击确定键 try: sel.find_element_by_css_selector("#TANGRAM__36__button_submit").click() print("click ensure success") except: print("click failed")
项目管理
2018-09-03 10:11:00
【来源:OSCHINA】Motan源码阅读--初识Motan
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Motan Motan是一套高性能,易于使用的RPC框架。提供了服务治理,包括服务节点自动发现,摘除,高可用和负载均衡。Motan具有良好扩展性,主要模块都提供了不同实现,例如多种注册中心,多种rpc协议等。 功能 支持通过spring配置方式集成,无需额外编写代码即可以为服务提供分布式调用能力。 支持集成consul,zk等配置服务组件,提供集群环境服务发现及治理能力。 支持动态自定义负载均衡,跨机房流量调整等高级服务调度能力。 基于高并发,高负载场景进行优化,保障生产环境下RPC服务高可用。 模块 Motan框架中主要有register,transport,serialize,protocol几个功能,各个模块都支持通过SPI进行扩展。 registry 用来和注册中心交互,包括服务注册,服务订阅,服务变更通知,服务心跳发送等。server端会在系统初始化时通过registry模块注册服务,client端在系统初始化时通过registry模块订阅到服务提供者列表,当server列表变更时由registry模块通知client。 protocol 用来进行RPC服务端描述和RPC服务端配置管理,可以添加不同filter来统计并发限制等功能。 serialize 将RPC请求中的参数,结果等对象进行序列化和反序列化,进行对象与字节流互相转换,默认使用对java友好的hessian2。 transport 用来进行远程通信,默认使用Netty nio的TCP长链接方式。 cluster Client端使用的模块,cluster是一组可用的server在逻辑上的封装,包含若干可以提供RPC服务的server,实际请求时,会根据不同的高可用和负载均衡策略选择一个可用server发起远程调用。 在进行RPC请求时,client通过代理机制调用cluster模块,cluster根据配置和HA和LoadBalance选出一个可用的server,通过serialize模块把RPC请求转换成字节流,然后通过transport发送到server端。 配置 Motan将功能模块抽象为四个可配置的元素,分别为: Protocol:服务通信协议,服务提供方与消费方进行远程调用的协议,默认为motan协议,使用hessian2进行序列化,netty作为endpoint及使用motan自定义的协议编码。 Registry:注册中心,服务提供方将服务信息(ip,端口,服务策略等信息)注册到注册中心,服务消费方通过注册中心发现服务,当服务发生变更,注册中心负责通知各个消费方。 Service:服务提供方提供的服务。使用方将核心业务抽取出来,作为独立的服务,通过暴露服务并将服务注册到注册中心,从而使调用方调用。 Referer:服务消费方对服务的引用,服务调用方。 Motan扩展来6个自定义的Spring xml标签: motan:protocol motan:registry Motan:basicservice motan:service motan:basicreferer motan:referer
项目管理
2018-08-24 13:14:02
【来源:OSCHINA】DDD(六)--领域事件
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 1、引言 领域事件就字面理解为,领域内已发生的活动,这是一个有具体状态的事件。 2、了解领域事件 将领域中所发生的活动建模成一系列的离散事件。每个事件都用领域对象来表示。领域事件是领域模型的组成部分,表示领域中所发生的事情。 要点:“领域事件是领域模型的组成部分”,通过这句话可以得知,领域事件不会是领域模型,那么在具体代码中领域事件是作为实体还是值对象呢? 事件直接的理解是“已经”发生的活动,是不变,所以值对像是比较好的选择。 领域事件是一个领域模型中极其重要的部分,用来表示领域中发生的事件。忽略不相关的领域活动,同时明确领域专家要跟踪或希望被通知的事情,或与其他模型对象中的状态更改相关联。 要点: 领域事件作为领域模型的重要部分,是领域建模的工具之一。 领域建模即分析业务,而领域事件是领域中一种状态的反应,例如肚子饿了,会通过神经感觉反馈给身体饿了这个信息。而这个信息可以让我们得知领域会做哪些业务。 用来捕获领域中已经发生的事情。 已发生的事情才被称之为事件。 并不是领域中所有发生的事情都要建模为领域事件,要忽略无业务价值的事件。 例如人的身体不会告诉人本身指甲太长了,但是却会通知身体肚子饿了,因为指甲长不影响人的存货,而饿肚子却会被饿死。即有价值才值得建模事件。 领域事件是领域专家所关心的(需要跟踪的、希望被通知的、会引起其他模型对象改变状态的)发生在领域中的一些事情。 同第三点所述一样,有价值才会被关心才能作为领域事件。 简而言之,领域事件是用来捕获领域中发生的具有业务价值的一些事情。它的本质就是事件,不要将其复杂化。在DDD中,领域事件作为通用语言的一种,是为了清晰表述领域中产生的事件概念,帮助我们深入理解领域模型。 3、领域事件的作用 上述中 领域事件是领域专家所关心的(需要跟踪的、希望被通知的、会引起其他模型对象改变状态的)发生在领域中的一些事情。 从这句话可以知道,领域事件所做的是跟踪事件和通知,也就是当发生了某件事情,才去做某一个操作。 例如: 当用户下单,扣除库存。 当用户支付,扣除用户余额。 当用户签收包裹,订单状态变更已签收。 那么,这些都是领域事件吗?回到刚才的话中, “需要跟踪的、希望被通知的、会引起其他模型对象改变状态的” 用户下单扣除库存是即时的操作,不满足加粗字体句中的三个条件。 用户支付扣除余额同理。用户签收包裹变更订单状态,满足会引起其他模型对象改变状态这个条件,所以是领域事件。 所以只有需要被跟踪、被通知、会改变其他模型状态的业务,才需要创建领域事件。 接上面签收例子,当订单状态变更后,商家从第三方平台收到货款,这个时候领域中是最终一致性的。所以部分领域事件是可以保持领域最终一致性。 上述多个例子中可以分析出,无论是变更订单状态,还是商家收到货款,这些都不是具体业务,既然如此,在使用领域事件时应该是能够保持原代码不做业务改变的,保证业务不变,只是对业务结果做出处理。 再者应当是可以解决领域的聚合性问题。DDD中的聚合有一个原则是,在单个事务中,只允许对一个聚合对象进行修改,由此产生的其他改变必须在单独的事务中完成。如果一个业务跨多个聚合对象,领域事件会是一个不错的工具来解决这个问题。通过领域事件的方式可以达到各个组件之间的数据一致性,通过最终一致性取代事务一致性。 4、领域事件建模 那么如何建模领域事件呢? 首先抽象事件源,事件源应该至少包含事件发生的时间和触发事件的对象。 抽象事件处理,事件处理要与事件源进行绑定。 领域事件不是无缘无故产生的,它有一个发布方。同理,它也要有一个订阅方。领域事件的发布可以使用 发布--订阅模式 来实现。 5、事件存储 事件存储,顾名思义,即事件的持久化。那为什么要持久化事件? 简单来说就是记录当时的领域状态。 当事件发布失败时,可用于重新发布。 通过消息中间件去分发事件,提高系统的吞吐量。 用于事件溯源。 源代码管理工具我们都用过,如Git、SVN等,通过记录文件每一次的修改记录,以便我们跟踪每一次对源代码的修改,从而我们可以随时回滚到文件的指定修改版本。 事件溯源的本质亦是如此,不过它存储的并非聚合每次变化的结果,而是存储应用在该聚合上的历史领域事件。当需要恢复某个状态时,需要把应用在聚合的领域事件按序“重放”到要恢复状态对应的领域事件为止。 6、小结 经过上面的分析,我们知道引入领域事件的目的主要有两个,一是解耦,二是实现数据的最终一致性。 最后,对于领域事件,我们可以这样理解: 通过将领域中所发生的活动建模成一系列的离散事件,跟踪事件执行相应的业务逻辑。 也可以简要理解为:领域事件 = 事件发布 + 事件存储 + 事件分发 + 事件处理。
项目管理
2019-09-06 18:56:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 1. 登录码云,进入个人主页 2. 新建仓库 3. 创建新仓库后清空仓库,保持仓库干净 4. 进入仓库代码页,复制仓库地址 5. 如项目未交由Git管理,可先将项目共享给Git管理 6. 在MyEclipse中将代码Push到仓库上
项目管理
2019-07-10 15:20:00
【来源:OSCHINA】Centos7从源码安装GDB
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 1.默认情况下,Centos7自带一个低版本的GDB,使用命令 # gdb -v 查看。 我们需要从官网下载gdb-8.3.tar.gz源代码: GDB官网 --> Dowload面版 --> SourcesSite --> gdb-8.3.tar.gz 2.解压 $ tar -zxvf gdb-8.3.tar.gz 3.配置 $ cd gdb-8.3 $ ./configure 4.编译 $ make 5.安装 $ su # make install 如果 make install 过程中报 WARNING: 'makeinfo' is missing on your system. 错。需要先通过命令 # yum install texinfo 安装texinfo。再从步骤3开始走一遍。 6.测试 $ gdb -v
项目管理
2019-07-05 12:11:00
【来源:OSCHINA】apache转发代理配置
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 配置域名: 1.在windows系统文件中配置hosts, 添加127.0.0.1 www.nhn-test.com 2.在Apache配置文件中去掉 conf/extra/httpd-vhosts.conf 前面的注解,即可引用httpd-vhosts.conf文件 3.在httpd-vhosts.conf中加入 DocumentRoot "E:\myWorkplays\hangame-template-master_hangame-template-master\src\main\webapp\WEB-INF\jsp" ServerName www.nhn-test.com 即可通过 http://www.nhn-test.com:8080/hangame-template/page?name=t 进行访问 出现问题: Invalid command 'Order', perhaps misspelled or defined by a module not included in the server config 开始配置出现上面的报错信息 解决办法: 把#LoadModule access_compat_module modules/mod_access_compat.so 前面改成: LoadModule access_compat_module modules/mod_access_compat.so 4.以上配置访问需要加端口,所以需要配置反向代理,才可以实现域名不带端口访问 httpd写反向代理,把制定域名的80端口映射到8080即可 配置代理: 1.将httpd.conf中 mod_proxy.so mod_proxy_http.so前面的注释去掉 开启代理使apache具备将URL转发给Tomcat的能力。 2.在httpd-vhosts.conf中添加 ProxyPass / http://www.nhn-test.com:8080/ ProxyPassReverse / http://www.nhn-test.com:8080/ 即可不加端口号对资源进行访问。 纠结了一天的坑以及学到的总结: 常用的两种方式:apache转发代理或者使用mod_jk 我用的就是第一种方法,Apache HTTP service 和 Tomcat server 整合,一般是希望对于用户只公布Apache HTTP server 的网址,而Tomcat的网址则不公布,达到对网站的保护,访问Tomcat的HTTP请求,通过Apache转发给Tomcat,Tomcat处理结束后,将回应的结果返回到Apache,然后Apache HTTP 再回应发回给用户浏览器。 方法:使用mod_jk,很多网站上介绍到apache和tomcat整合的时候,都是接受so的使用,这是一种比较老的方法,而且现在tomcat的官网上已经没有了对y应so的下载,所以我这边没有找到其资源,配置文件都需要改动,所以我不推荐使用。 还有其他几种方法 https://blog.csdn.net/huhuhuemail/article/details/78183579 我用的是mod_proxy方法,是Apache自带功能,并且配置比较简单。
项目管理
2019-06-28 16:31:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> java代码判断浏览器是否是IE11 HttpServletRequest request = ServletActionContext.getRequest(); String agent = request.getHeader("User-Agent"); //判断是IE浏览器而且不是IE11的时候 if (version.equals("WPS") && request.getHeader("User-Agent").toUpperCase().indexOf("MSIE") == -1 && !(null != agent && -1 != agent.indexOf("like Gecko"))) { System.out.print("是IE浏览器而且不是IE11"); } js判断浏览器是否是IE11 var u = window.navigator.userAgent.toLocaleLowerCase(), ie11 = /(trident)\/([\d.]+)/, style = u.match(ie11); if(style){ alert("该浏览器是ie11"); }
项目管理
2019-06-20 09:08:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 转载本文需注明出处:微信公众号EAWorld,违者必究。 引言: Spark是在借鉴了MapReduce之上发展而来的,继承了其分布式并行计算的优点并改进了MapReduce明显的缺陷。Spark主要包含了Spark Core、Spark SQL、Spark Streaming、MLLib和GraphX等组件。 本文主要分析了 Spark RDD 以及 RDD 作为开发的不足之处,介绍了 SparkSQL 对已有的常见数据系统的操作方法,以及重点介绍了普元在众多数据开发项目中总结的基于 SparkSQL Flow 开发框架。 目录: 一、Spark RDD 二、基于Spark RDD数据开发的不足 三、SparkSQL 四、SparkSQL Flow 一、Spark RDD RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、元素可并行计算的集合。 RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。 //Scala 在内存中使用列表创建 val lines = List(“A”, “B”, “C”, “D” …) val rdd:RDD = sc.parallelize(lines); //以文本文件创建 val rdd:RDD[String] = sc.textFile(“hdfs://path/filename”) Spark RDD Partition 分区划分 新版本的 Hadoop 已经把 BlockSize 改为 128M,也就是说每个分区处理的数据量更大。 Spark 读取文件分区的核心原理 本质上,Spark 是利用了 Hadoop 的底层对数据进行分区的 API(InputFormat): public abstract class InputFormat{ public abstract ListgetSplits(JobContextcontext ) throwsIOException,InterruptedException; public abstract RecordReader createRecordReader(InputSplitsplit, TaskAttemptContextcontext )throwsIOException,InterruptedException; } Spark 任务提交后通过对输入进行 Split,在 RDD 构造阶段,只是判断是否可 Split(如果参数异常一定在此阶段报出异常),并且 Split 后每个 InputSplit 都是一个分区。只有在Action 算子提交后,才真正用 getSplits 返回的 InputSplit 通过 createRecordReader 获得每个 Partition 的连接。 然后通过 RecordReader 的 next() 遍历分区内的数据。 Spark RDD 转换函数和提交函数 Spark RDD 的众多函数可分为两大类Transformation 与 Action。Transformation 与 Action 的区别在于,对 RDD 进行 Transformation 并不会触发计算:Transformation 方法所产生的 RDD 对象只会记录住该 RDD 所依赖的 RDD 以及计算产生该 RDD 的数据的方式;只有在用户进行 Action 操作时,Spark 才会调度 RDD 计算任务,依次为各个 RDD 计算数据。这就是 Spark RDD 内函数的“懒加载”特性。 二、基于Spark RDD数据开发的不足 由于MapReduce的shuffle过程需写磁盘,比较影响性能;而Spark利用RDD技术,计算在内存中流式进行。另外 MapReduce计算框架(API)比较局限, 使用需要关注的参数众多,而Spark则是中间结果自动推断,通过对数据集上链式执行函数具备一定的灵活性。 即使 SparkRDD 相对于 MapReduce 提高很大的便利性,但在使用上仍然有许多问题。体现在一下几个方面: RDD 函数众多,开发者不容易掌握,部分函数使用不当 shuffle时造成数据倾斜影响性能; RDD 关注点仍然是Spark太底层的 API,基于 Spark RDD的开发是基于特定语言(Scala,Python,Java)的函数开发,无法以数据的视界来开发数据; 对 RDD 转换算子函数内部分常量、变量、广播变量使用不当,会造成不可控的异常; 对多种数据开发,需各自开发RDD的转换,样板代码较多,无法有效重利用; 其它在运行期可能发生的异常。如:对象无法序列化等运行期才能发现的异常。 三、SparkSQL Spark 从 1.3 版本开始原有 SchemaRDD 的基础上提供了类似Pandas DataFrame API。新的DataFrame API不仅可以大幅度降低普通开发者的学习门槛,同时还支持Scala、Java与Python三种语言。更重要的是,由于脱胎自SchemaRDD,DataFrame天然适用于分布式大数据场景。 一般的数据处理步骤:读入数据 -> 对数据进行处理 -> 分析结果 -> 写入结果 SparkSQL 结构化数据 处理结构化数据(如 CSV,JSON,Parquet 等); 把已经结构化数据抽象成 DataFrame (HiveTable); 非结构化数据通过 RDD.map.filter 转换成结构化进行处理; 按照列式数据库,只加载非结构化中可结构化的部分列(Hbase,MongoDB); 处理非结构化数据,不能简单的用 DataFrame 装载。而是要用 SparkRDD 把数据读入,在通过一系列的 Transformer Method 把非结构化的数据加工为结构化,或者过滤到不合法的数据。 SparkSQL DataFrame SparkSQL 中一切都是 DataFrame,all in DataFrame. DataFrame是一种以RDD为基础的分布式数据集,类似于传统数据库中的二维表格。DataFrame与RDD的主要区别在于,前者带有schema元信息,即DataFrame所表示的二维表数据集的每一列都带有名称和类型。如果熟悉 Python Pandas 库中的 DataFrame 结构,则会对 SparkSQL DataFrame 概念非常熟悉。 TextFile DataFrame import.org.apache.spark.sql._ //定义数据的列名称和类型 valdt=StructType(List(id:String,name:String,gender:String,age:Int)) //导入user_info.csv文件并指定分隔符 vallines = sc.textFile("/path/user_info.csv").map(_.split(",")) //将表结构和数据关联起来,把读入的数据user.csv映射成行,构成数据集 valrowRDD = lines.map(x=>Row(x(0),x(1),x(2),x(3).toInt)) //通过SparkSession.createDataFrame()创建表,并且数据表表头 val df= spark.createDataFrame(rowRDD, dt) 读取规则数据文件作为DataFrame SparkSession.Builder builder = SparkSession.builder() Builder.setMaster("local").setAppName("TestSparkSQLApp") SparkSession spark = builder.getOrCreate(); SQLContext sqlContext = spark.sqlContext(); # 读取 JSON 数据,path 可为文件或者目录 valdf=sqlContext.read().json(path); # 读取 HadoopParquet 文件 vardf=sqlContext.read().parquet(path); # 读取 HadoopORC 文件 vardf=sqlContext.read().orc(path); JSON 文件为每行一个 JSON 对象的文件类型,行尾无须逗号。文件头也无须[]指定为数组;SparkSQL 读取是只是按照每行一条 JSON Record序列化; Parquet文件 Configurationconfig = new Configuration(); ParquetFileReaderreader = ParquetFileReader.open( HadoopInputFile.fromPath(new Path("hdfs:///path/file.parquet"),conf)); Mapschema = reader.getFileMetaData().getKeyValueMetaData(); String allFields= schema.get("org.apache.spark.sql.parquet.row.metadata"); allFiedls 的值就是各字段的名称和具体的类型,整体是一个json格式进行展示。 读取 Hive 表作为 DataFrame Spark2 API 推荐通过 SparkSession.Builder 的 Builder 模式创建 SparkContext。 Builder.getOrCreate() 用于创建 SparkSession,SparkSession 是 SparkContext 的封装。 在Spark1.6中有两个核心组件SQLcontext和HiveContext。SQLContext 用于处理在 SparkSQL 中动态注册的表,HiveContext 用于处理 Hive 中的表。 从Spark2.0以上的版本开始,spark是使用全新的SparkSession接口代替Spark1.6中的SQLcontext和HiveContext。SQLContext.sql 即可执行 Hive 中的表,也可执行内部注册的表; 在需要执行 Hive 表时,只需要在 SparkSession.Builder 中开启 Hive 支持即可(enableHiveSupport())。 SparkSession.Builder builder = SparkSession.builder().enableHiveSupport(); SparkSession spark = builder.getOrCreate(); SQLContext sqlContext = spark.sqlContext(); // db 指 Hive 库中的数据库名,如果不写默认为 default // tableName 指 hive 库的数据表名 sqlContext.sql(“select * from db.tableName”) SparkSQL ThriftServer //首先打开 Hive 的 Metastore服务 hive$bin/hive –-service metastore –p 8093 //把 Spark 的相关 jar 上传到hadoophdfs指定目录,用于指定sparkonyarn的依赖 jar spark$hadoop fs –put jars/*.jar /lib/spark2 // 启动 spark thriftserver 服务 spark$ sbin/start-thriftserver.sh --master yarn-client --driver-memory 1G --conf spark.yarn.jars=hdfs:///lib/spark2/*.jar 当hdfs 上传了spark 依赖 jar 时,通过spark.yarn.jars 可看到日志 spark 无须每个job 都上传jar,可节省启动时间 19/06/1114:08:26 INFO Client: Source and destination file systems are the same. Notcopying hdfs://localhost:9000/lib/spark2/snappy-java-1.0.5.jar 19/06/1114:08:26 INFO Client: Source and destination file systems are the same. Notcopying hdfs://localhost:9000/lib/spark2/snappy-java-1.1.7.3.jar //通过 spark bin 下的 beeline 工具,可以连接到 spark ThriftServer(SparkOnHive) bin/beeline -u jdbc:hive2://ip:10000/default -n hadoop -u 是指定 beeline 的执行驱动地址; -n 是指定登陆到 spark Session 上的用户名称; Beeline 还支持传入-e 可传入一行 SQL, -equery that should be executed 也可通过 –f 指定一个 SQL File,内部可用逗号分隔的多个 SQL(存储过程) -fscript file that should be executed SparkSQL Beeline 的执行效果展示 SparkSQL ThriftServer 对于 SparkSQL ThriftServer 服务,每个登陆的用户都有创建的 SparkSession,并且执行的对个 SQL 会通过时间顺序列表展示。 SparkSQL ThriftServer 服务可用于其他支持的数据库工具创建查询,也用于第三方的 BI 工具,如 tableau。 四、SparkSQL Flow SparkSQL Flow 是以 SparkSQL 为基础,开发的统一的基于 XML 配置化的可执行一连串的 SQL 操作,这一连串的 SQL 操作定义为一个 Flow。下文开始 SparkSQL Flow 的介绍: SparkSQL Flow 是基于 SparkSQL 开发的一种基于 XML 配置化的 SQL 数据流转处理模型。该模型简化了 SparkSQL 、Spark RDD的开发,并且降低开发了难度,适合了解数据业务但无法驾驭大数据以及 Spark 技术的开发者。 一个由普元技术部提供的基于 SparkSQL 的开发模型; 一个可二次定制开发的大数据开发框架,提供了灵活的可扩展 API; 一个提供了 对文件,数据库,NoSQL 等统一的数据开发视界语义; 基于 SQL 的开发语言和 XML 的模板配置,支持 Spark UDF 的扩展管理; 支持基于 Spark Standlone,Yarn,Mesos 资源管理平台; 支持开源、华为、星环等平台统一认证。 SparkSQL Flow 适合的场景: 批量 ETL; 非实时分析服务; SparkSQL Flow XML 概览 Properties 内定义一组变量,可用于宏替换; Methods 内可注册 udf 和 udaf 两种函数; Prepare 内可定义前置 SQL,用于执行 source 前的 sql 操作; Sources 内定义一个到多个数据表视图; Transformer 内可定义 0 到多个基于 SQL 的数据转换操作(支持 join); Targets 用于定义 1 到多个数据输出; After 可定义 0到多个任务日志; 如你所见,source 的 type 参数用于区分 source 的类型,source 支持的种类直接决定SparkSQL Flow 的数据源加载广度;并且,根据 type 不同,source 也需要配置不同的参数,如数据库还需要 driver,url,user和 password 参数。 Transformer 是基于 source 定的数据视图可执行的一组转换 SQL,该 SQL 符合 SparkSQL 的语法(SQL99)。Transform 的 SQL 的执行结果被作为中间表命名为 table_name 指定的值。 Targets 为定义输出,table_name 的值需在 source 或者 Transformer 中定义。 SparkSQL Flow 支持的Sourse 支持从 Hive 获得数据; 支持文件:JSON,TextFile(CSV),ParquetFile,AvroFile 支持RDBMS数据库:PostgreSQL, MySQL,Oracle 支持 NOSQL 数据库:Hbase,MongoDB SparkSQL Flow TextFile Source textfile 为读取文本文件,把文本文件每行按照 delimiter 指定的字符进行切分,切分不够的列使用 null 填充。 fields="cust_id,name1,gender1,age1:int" delimiter="," path="file:///Users/zhenqin/software/hive/user.txt"/> Tablename 为该文件映射的数据表名,可理解为数据的视图; Fields 为切分后的字段,使用逗号分隔,字段后可紧跟该字段的类型,使用冒号分隔; Delimiter 为每行的分隔符; Path 用于指定文件地址,可以是文件,也可是文件夹; Path 指定地址需要使用协议,如:file:// 、 hdfs://,否则跟 core-site.xml 配置密切相关; SparkSQL Flow DB Source table="user" url="jdbc:mysql://localhost:3306/tdb?characterEncoding=UTF-8" driver="com.mysql.jdbc.Driver" user="root" password="123456"/> RDBMS 是从数据库使用 JDBC读取 数据集。支持 type 为:db、mysql、oracle、postgres、mssql; tablename 为该数据表的抽象 table 名称(视图); url、driver、user,password 为数据库 JDBC 驱动信息,为必须字段; SparkSQL 会加载该表的全表数据,无法使用 where 条件。 SparkSQL Flow Transformer SELECT c_phone,c_type,c_num, CONCAT_VAL(cust_id) as cust_ids FROM user_concat_testx group by c_phone,c_type,c_num Transform 支持 cached 属性,默认为 false;如果设置为 true,相当于把该结果缓存到内存中,缓存到内存中的数据在后续其它 Transform 中使用能提高计算效率。但是需使用大量内存,开发者需要评估该数据集能否放到内存中,防止出现 OutofMemory 的异常。 SparkSQL Flow Targets SparkSQL Flow Targets 支持输出数据到一个或者多个目标。这些目标,基本覆盖了 Source 包含的外部系统。下面以 Hive 举例说明: table_name="cust_id_agmt_id_t" savemode=”append” target_table_name="cust_id_agmt_id_h"/> table_name 为 source 或者 Transform 定义的表名称; target_table_name 为 hive 中的表结果,Hive 表可不存在也可存在,sparksql 会根据 DataFrame 的数据类型自动创建表; savemode 默认为 overwrite 覆盖写入,当写入目标已存在时删除源表再写入;支持 append 模式, 可增量写入。 Target 有一个特殊的 show 类型的 target。用于直接在控制台输出一个 DataFrame 的结果到控制台(print),该 target 用于开发和测试。 Rows 用于控制输出多少行数据。 SparkSQL Around After 用于 Flow 在运行结束后执行的一个环绕,用于记录日志和写入状态。类似 Java 的 try {} finally{ round.execute() } 多个 round 一定会执行,round 异常不会导致任务失败。 sql="insert into cpic_task_history(id, task_type, catalog_model, start_time, retry_count, final_status, created_at) values(${uuid}, ${task.type}, ${catalog.model}, ${starttime}, 0, ${status}, now())" url="${jdbc.url}" .../> sql="update cpic_task_history set end_time = ${endtime}, final_status = ${status}, error_text = ${error} where id = ${uuid}" url="${jdbc.url}”…/> Prepare round 和 after round 配合使用可用于记录 SparkSQL Flow 任务的运行日志。 SparkSQL Around可使用的变量 SparkSQL Around的执行效果 Prepare round 可做插入(insert)动作,after round 可做更新 (update)动作,相当于在数据库表中从执行开始到结束有了完整的日志记录。SparkSQL Flow 会保证round 一定能被执行,而且 round 的执行不影响任务的状态。 SparkSQL Flow 提交 bin/spark-submit --master yarn-client --driver-memory 1G \ --num-executors 10 --executor-memory 2G \ --jars /lib/jsoup-1.11.3.jarlib/jsqlparser-0.9.6.jar,/lib/mysql-connector-java-5.1.46.jar \ --conf spark.yarn.jars=hdfs:///lib/spark2/*.jar \ --queue default --name FlowTest \ etl-flow-0.2.0.jar -f hive-flow-test.xml 接收必须的参数 –f,可选的参数为支持 Kerberos 认证的租户名称principal,和其认证需要的密钥文件。 usage: spark-submit --jars etl-flow.jar --class com.yiidata.etl.flow.source.FlowRunner -f,--xml-fileFlow XML File Path --keytabFilekeytab File Path(Huawei) --krb5Filekrb5 File Path(Huawei) --principalprincipal for hadoop(Huawei) SparkSQL Execution Plan 每个Spark Flow 任务本质上是一连串的 SparkSQL 操作,在 SparkUI SQL tab 里可以看到 flow 中重要的数据表操作。 regiserDataFrameAsTable 是每个 source 和 Transform 的数据在 SparkSQL 中的数据视图,每个视图都会在 SparkContex 中注册一次。 对RegisterDataFrameAsTable的分析 通过单个 regiserDataFrameAsTable 项进行分析,SparkSQL 并不是把source 的数据立即计算把数据放到内存,而是每次执行 source 时只是生成了一个 Logical Plan,只有遇到需要提交的算子(Action),SparkSQL 才会触发前面所依赖的的 plan 执行。 总结 这是一个开发框架,不是一个成熟的产品,也不是一种架构。他只是基于 SparkSQL 整合了大多数的外部系统,能通过 XML 的模板配置完成数据开发。面向的是理解数据业务但不了解 Spark 的数据开发人员。整个框架完成了大多数的外部系统对接,开发者只需要使用 type 获得数据,完成数据开发后通过 target 回写到目标系统中。整个过程基本无须程序开发,除非当前的 SQL 函数无法满足使用的情况下,需要自行开发一下特定的 UDF。因此本框架在对 SparkSQL 做了二次开发基础上,大大简化了 Spark 的开发,可降低了开发者使用难度。 关于作者:震秦,普元资深开发工程师,专注于大数据开发 8 年,擅长 Hadoop 生态内各工具的使用和优化。参与某公关广告(上市)公司DMP 建设,负责数据分层设计和批处理,调度实现,完成交付使用;参与国内多省市公安社交网络项目部署,负责产品开发(Spark 分析应用);参与数据清洗加工为我方主题库并部署上层应用。 关于EAWorld:微服务,DevOps,数据治理,移动架构原创技术分享。长按二维码关注!
项目管理
2019-06-19 13:51:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 本文首发于: Jenkins 中文社区 原文链接 作者:Javin Paul 10节课带你深入学习 DevOps 工程 对那些想要涉足 DevOps 领域的工程师来说,这些多样的课程提供了一个很好的开始 DevOps 现在真的很热门,对于杰出的工程师和 DevOps 专业人员来说有许多工作机会。 如果你想成为一名 DevOps 工程师,那么你来对地方了。在本文中,我将分享一下最好的在线培训课程, 让你成为 DevOps 专业人员。 Devops 最重要的优势,它可以帮助你更好地发布软件并且利用现代自动化工具对环境和软件开发过程中提供更多控制。这就是 DevOps 专业人员需求呈指数增长的原因。除了 Data Science 和 Machine Learning 外,它也是薪酬最高的 IT 工作之一。 根据 Glassdoor 的数据, DevOps 的工程师每年的收入从105000美元到146000美元不等。这意味着,如果你正在寻找加薪或想在美好年纪从事一些令人兴奋的工作赚更多的钱,学习 DevOps 可能是一个不错的选择。 学习像 Jenkins 这样的持续集成工具和像 Docker 这样的容器以及一般的 DevOps 技能,在技术领域获得了巨大的动力。这与几年前的移动应用程序开发类似。 公司希望新的开发人员能够管理 Web 应用程序的整个生命周期。这意味着开发和部署应用程序。 为了成为一名有效的 DevOps 工程师,您必须扩展对软件开发中使用的不同工具的知识,包括构建工具(如 Maven 、 Ant 和 Gradle )、单元测试工具(如 Junit 和 Selenium )、部署工具(如 Docker )、监控工具(如 New Relic )、基础设施自动化工具(如 Chef 和 Puppet )、源代码控制工具,如 Git 和 Github,以及持续集成工具,如 Jenkins 和 TeamCity。这些课程为基本的 DevOps 工具提供了很好的介绍。 十节面向经验丰富的开发人员 DevOps 课程 在不浪费更多时间的情况下,这里列出了一些学习 DevOps 的最佳课程以及在软件开发和部署过程中实现自动化所需的基本工具。 1.学习路径:现代 DevOps DevOps 以一种全新的方式看待软件开发。您可以实现自动化,构建基础结构服务器的配置,然后解决自动化、连续部署、容器和监控方面的问题。 Git、 Docker 和 Puppet 是现代 DevOps 世界中最重要的工具, 本课程 将向您介绍这三种工具。 简而言之,这是一门很好的 入门课程 ,适用于系统管理员、开发人员和 IT 专业人员等 DevOps 领域的新手,还提供了对基本 DevOps 工具 的良好概述。 2.面向 DevOps 和开发者的 Docker 技术 Docker 是 DevOps 最重要的技术之一。它允许您将组件捆绑在一起,并将它们部署在任何平台(如 Linux 或 Windows )上的容器上。 本课程涵盖了 Docker 软件的所有基础知识,并向您传授了开发和部署 Docker 现代应用程序所需的一切知识。 3.Jenkins,从小白到专家:成为一名 DevOps JenKins 大师 Jenkins 可能是 DevOps 工程师进行持续集成工作的最重要工具。 对于 DevOps 专业人员,具备持续检查、持续集成、持续部署的知识,且知道它们之间的区别是十分必要的。 本课程 涵盖了有关 Jenkins 的所有基础知识,并向您传授建立 Jenkins 构建管道所需的所有知识,从持续检查(构建、测试和静态分析)开始,一直到持续部署(待部署和生产)。 4.学习 DevOps:完整的 Kubernetes 课程 当谷歌十年前开始运行容器时,没有人能够达到这种基础设施的灵活性和效率。利用这些知识,谷歌发布了 Kubernetes 作为一个免费的开源项目。 如今,Kubernetes 被那些希望获得跟谷歌一样效率和速度的小公司和大企业使用。 本课程 将教您如何在 Kubernetes 上运行、部署、管理和维护容器化的 Docker 应用程序。 5.学习 DevOps:持续发布更好的软件 这是关于 Udemy 的 DevOps 的最完整的课程之一,它将教授您 DevOps 工程师 使用的大多数基本工具和技术。 本课程 面向这样的软件工程师和系统管理员:他们希望提供更好的软件,并帮助您在交付和部署过程中更好地使用 Git 、Vagrant、Chef、Ansible、 Jenkins 、Docker 和 Kubernetes 这些工具。 6.DevOps 课程的 Docker:从开发到生产 本课程 向您展示了通过 Docker,您可以构建什么以及如何进行构建。除此之外,你还将学习 Docker 的基本知识!我们将一起讨论开发和部署多服务 Flask 和 Ruby on Rails 应用程序。 7.学习 DevOps:使用 TerraForm 实现基础设施自动化 基础设施自动化是 DevOps 的一个重要组成部分。像 Ansible、Chef、Puppet 等工具都很有用,但 TerraForm 最近更受欢迎,如果您正在或即将成为一个 Ops/DevOps,您需要掌握这些工具,这就是 本在线课程 将帮助您的地方。 Terraform 开始于相同的规则、基础设施即代码,但更专注于基础结构本身的自动化。您的整个云基础设施(实例、卷、网络、IP)在 TerraForm 中被描述。 在 本课程 中,您将学习如何通过 TerraForm 以及 AWS 、Packer、Docker、ECR、ECS 和 Jenkins 来实现自动化基础设施。 8.使用 AWS codepipeline、Jenkins 和 AWS codedeploy 的 DevOps 如果您对什么是持续集成(CI)或持续交付/持续部署(CD)感到困惑,以及如何使用 Amazon Web 服务(如 AWS 和Jenkins) 进行 DevOps ,那么这是适合您的课程。 在 本课程 中,您将学习必要的DevOps技能,以及在AWS云中的持续集成和持续交付。 9.DevOps:用 Jenkins pipelines, Maven, Gradle 进行 CI/CD 本在线 DevOps 课程 将教您如何使用 Jenkins 及它的一些插件(尤其是流水线插件),来构建复杂的持续集成和持续交付流水线。 本课程 旨在向您传授 Jenkins 的经验,并建立 DevOps 流水线,即使您几乎没有经验,也可以帮助您实现这些 DevOps 实践,从而简化您的开发过程。 总之,用 Java 、 Gradle 、 Maven 、AtdiPrand 和 Sqitch 构建的持续集成、持续交付和 DevOps 流水线是一个伟大的过程。 10.用 Docker, Jenkins, GIT, Vagrant, 和 Maven 完成 DevOps 如果您正在寻找一个 实践 DevOps 课程 ,它不仅可以向您解释 DevOps 工程师的角色,而且可以提供关于基本 DevOps 工具的实践经验,那么这是您的课程。 它提供了一个关于 DevOps 基本技术的完整教程。您将通过实践指导学习 Docker Mastery、Jenkins、Git、Vagrant 和 Maven。 正如我所说,对优秀的 DevOps 工程师和软件开发人员的需求呈指数级增长,市场上没有足够的 DevOps 专业人员来支持这一需求。 这意味着这是一个学习 DevOps 并进入这个更负责任、高薪和令人兴奋的领域的绝佳机会。如果你打算在2019年成为一名 DevOps 工程师,那么这些课程是一个很好的起点。 如果你喜欢这些 DevOps 课程,请与你的朋友和同事分享。如果您有任何问题或反馈,请留言。
项目管理
2019-06-18 19:09:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 摘要: 每一个程序员都应该读的一本书。 原文: 重构:一项常常被忽略的基本功 作者:hengg Fundebug 经授权转载,版权归原作者所有。 五月初的时候朋友和我说《重构》出第 2 版了,我才兴冲冲地下单,花了一个礼拜时间一口气把它读完后,才有了这篇书评。掩卷沉思,我无比赞同豆瓣网友“天心一”的评论: 这本书虽然很流行,但是应该看它而没有看的人,还是太多太多了。 一个老读者的自白 作为一个开发者,2012年初识本书的时候,我在写 Java;2019年本书再版,我在写 JavaScript。真是应了那句老话儿:“凡是可以用 JavaScript 来写的应用,最终都会用 JavaScript 来写。” JavaScript 特别适合重构,因为它很容易写的无法维护。 当然这只是个玩笑,实际上作者也解释过:重构背后的理念和架构适用于任何编程语言,选择 JavaScript 只是因为它应用的比较广泛。无论使用哪种编程语言都可以写出优秀的或者糟糕的代码,同样也都可以以本书的思路和技巧进行重构。 使用 JavaScript 展示代码范例,并不意味这本书中介绍的技巧只适用于JavaScript。 对比新旧两版,作者“重构”了这本书:前几章有所扩展,后几章结构调整较大,移除了原来的 12-14 章。总的来说,重构后的第 2 版更接地气、更适应时代:不再有“大型重构”,更多地聚焦操作的细节。 “Fowler 先生不仅没有拔高,反而把功夫做得更扎实了。” —— 摘自译者序 虽然本书的副标题是“改善既有代码的设计”,但通读全书之后,我觉得这本书对于设计新系统时如何避免“坏味道”也是很有指导意义的。 重构和敏捷开发是一对亲兄弟 提重构就不能不提敏捷开发,马丁·福勒本身就是敏捷开发的发起者之一。敏捷作为“当红炸子鸡”,与重构有着很多相似的地方。 一是,这两者都容易成为“挂羊头,卖狗肉”中的“羊头”,很多情况下,所谓的重构就是抽出时间来重写现有的几乎无法维护的代码,就如同很多“敏捷”只做到了“不拒绝需求变更”而没有真正做到响应变化;二是,它们实现起来都是一定难度且它们的实践过程可以是交叉的——它们都着眼于具体细节而不是空架子,都欢迎变化,都强调小步快走、持续改进;三是,敏捷开发很重要的两个环节就是设计与重构,两者相辅相成,彼此互补,在实践的过程中保持较强的适应力。 重构的技巧 可以说,我在重构过程中遇到的问题大多都能在本书中找到答案。 我们看看作者对重构的定义: 重构(名词): 对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。 重构(动词): 使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。 为何重构、如何重构、重构的原则与手法,都可以在这本书中找到。从第 5 章起作者提供了多达 300 页的重构名录、60 余项重构的具体技巧(老版本是 70 多项,新版本移除了大规模项目的重构)。我觉得这一份非常详尽的重构手法清单更接近于字典,适合粗读之后在用到的时候再具体查阅。 至于什么时候能够用到这份名录,作者在第 3 章也有介绍:当代码有了“坏味道”就可以着手进行重构了。所谓“坏味道”,我认为并非是一程不变的准则,而是需要根据团队、项目、采用的技术栈等各方面综合得出的一种无法定量描述的经验。所以,作者用了“味道”这样一种体验来代指需要重构的地方。在作者列出的每种“坏味道”中,都给出了对应的重构手法。虽然作者罗列的 20 多种“坏味道”覆盖面很广,但是你和你的团队仍然可以总结出自己的经验来指导重构。实际上,与第 1 版相比,第 2 版中的“坏味道”增加了“神秘命名”“全局数据”“循环语句”,删除了“不完美的库类”。 我认为本书最重要也最容易被忽略的章节就是第 4 章—— 构筑测试体系 。在第 4 章中,作者通过一个生产计划的示例一步一步的构建了一个完整的单元测试体系。显然,掌握单元测试是有一定成本的,这就导致有些开发者(尤其是前端领域)完全不注重单元测试。他们认为测试是QA的职责,自己只需要保证冒烟测试通过即可。然而反直觉的是,良好的单元测试不但是重构的先决条件和好帮手,而且能帮我们整理设计的思路,从而更好的写出优秀的代码。因为在写单元测试的时候,我们会假设自己是一个“代码破坏者”,思考如何破坏代码的运行、寻找那些可能出错的边界条件。单元测试的编写和运行可以在写完代码后进行,也可以在写代码之前动手。先写单元测试再写代码的技巧叫作测试驱动开发(TDD),也是敏捷开发的基石之一。关于TDD的技艺,作者的好友 Kent Beck 专门写了一本书,即《测试驱动开发》。 作者在第 1 章的示例中提到: “小步快走,代码永远处于可工作状态。” 而且作者特意强调: “每当我要进行重构的时候,第一个步骤永远相同:我得确保即将修改的代码拥有一组可靠的测试。” 对于单元测试,我有一点小小的心得可以与大家分享:**尽量编写纯函数。**纯函数是没有副作用的函数,给出同样的参数值,纯函数总是返回同样的结果,它不依赖于参数以外的值。显然,纯函数更便于单元测试。 当然单元测试也不是万能的,它不可能检出所有的bug,而且单元测试集的覆盖率也是一个见仁见智的指标,具体需要写多少单元测试,覆盖多少代码,都是需要我们在开发中结合实际情况自己权衡的。无论如何,单元测试一直是一中非常重要却常常被忽视的技能。 另外,我在开发实践中坚持一个“432”的原则,供大家参考: 一个类包括注释代码不要超过400行; 一个纯函数最好不要超过30行; 函数内循环嵌套最多2层。 重构的现状 有些朋友对“重构”是不支持甚至是深恶痛绝的。 一部分开发者不愿意把精力“浪费”在重构上 他们觉得重构是“给飞行中的飞机修引擎”,有可能出现很多问题却带不来多少拿得出手的成绩;重构总是会在“不经意间”破坏原有功能,带来的麻烦很多,投入与收益完全不成比例,也很少会是面试的重点,花精力在这上面实在是费力不讨好。 许多leader反对盲目重构 在创业公司里基本不会有重构的呼声,原因无须赘言;而在一些大企业里,leader们也不是都喜欢重构,因为花时间重构意味着占用了开发新功能的时间,在代码还能跑起来甚至看起来跑得还不错的时候去重构无疑是画蛇添足;与重构带来的风险相比,重构带来的好处就不是那么有说服力了。 大部分QA对重构持谨慎的质疑态度 代码的变动意味着需要进行回归测试,而敏捷当道的时代,每个迭代中QA的关注重点都在新功能上,能够分配给回归测试的精力很有限,而在测试通过后的重构极有可能导致此次变更对QA不透明,无形中增加了上线的风险。 我认为以上几种反对重构的场景都是不恰当的重构导致的。 大家只是越来越接纳“重构”这个词,因为这个词听起来很好,有一种积极应对变化的感觉,但真正在做的还是跟以前一样,毫无规矩的修改。 在实践中,重构的要求是很高的:它需要有足够详尽的单元测试,需要有持续集成的环境,需要随时随地在“小步伐地永远让代码处于可工作状态”下去进行改善。正是因为许多项目的“重构”是在并不满足以上条件也没有经过成本估算、策略规划的情况下进行的,自然很容易导致失败。 水土不服 实际上,还有一部分开发者虽然认识到了重构是提升代码质量的有效手段,是诸如“在当下努力工作,以免日后有更多的活儿”此类观念的具现。然而在某种程度上说,这在当前996.icu大环境下是不适用的。关于这一点就只能见仁见智、自己衡量了。 没有银弹 最后,我想说一句: 没有银弹 。 重构和设计模式一样,是对于最佳实践的提炼,是一系列技巧的集合,它不是打通任督二脉的灵丹妙药。如果你是一个有追求但却从来没有系统地了解过重构的程序员(当然我不相信世界上会有这种程序员),那你会发现,你在日常工作中不经意间已经用过了这本书中提到的各种重构手法。 重构是注重实践的技艺,仅仅了解其理念而忽视实践则有如抟沙作饭,白费心思;而企图把它当做“万金油”来解决所有问题也只会陷入不恰当重构的陷阱,最终得不偿失。只有在合适的场景下恰当的实践,才会实现其应有的价值。
项目管理
2019-06-15 09:49:00