精华内容
下载资源
问答
  • 我采用的是authentication 是从ldap进行,authorization是根据数据库的role,resource,user以及他们之间关联表进行。需要注意的是,如果要对所有的资源进行过滤,可以继承anonymousProcessingFilter。 两个配置文件...

    SS 的版本是2.0.5。

    我采用的是authentication 是从ldap进行,authorization是根据数据库的role,resource,user以及他们之间关联表进行。

    需要注意的是,如果要对所有的资源进行过滤,可以继承anonymousProcessingFilter。

     

    两个配置文件,

         web.xml 这个没的说。

        <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
        version="2.4">
        <display-name>SkillDB</display-name>
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext*.xml</param-value>
        </context-param>
        <!-- Configure filter of spring -->
        <filter>
            <filter-name>springSecurityFilterChain</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>springSecurityFilterChain</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        
        <!-- Configure the core filter of struts -->
        <filter>
            <filter-name>struts2</filter-name>
            <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>struts2</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        
        <!-- session config -->
        <session-config>
        <!-- Default to 30 minutes session timeouts -->
            <session-timeout>2</session-timeout>
        </session-config>
        
       <!-- Configure the srping context -->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
     
        <welcome-file-list>
            <welcome-file>login.jsp</welcome-file>
        </welcome-file-list>
    </web-app>

         ********

     

         application-security.xml

     

          <?xml version="1.0" encoding="UTF-8"?>
    <beans:beans xmlns="http://www.springframework.org/schema/security"
        xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">
        <!--
            <http> <intercept-url pattern="/login.jsp" filters="none"/>
            <intercept-url pattern="/**" access="ROLE_COMMON_USER"/> <form-login
            login-page="/login.jsp"/> </http>
        -->
        <beans:bean id="springSecurityFilterChain"
            class="org.springframework.security.util.FilterChainProxy">
            <beans:property name="filterInvocationDefinitionSource">
                <beans:value>
                     <![CDATA[                 CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON  
                    PATTERN_TYPE_APACHE_ANT
                    /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterSecurityInterceptor
                    ]]>
                </beans:value>
            </beans:property>
        </beans:bean>

        <beans:bean id="httpSessionContextIntegrationFilter"
            class="org.springframework.security.context.HttpSessionContextIntegrationFilter">
            <beans:property name="contextClass">
                <beans:value>org.springframework.security.context.SecurityContextImpl
                </beans:value>
            </beans:property>
        </beans:bean>

        <!--
            <beans:bean id="sessionTimeoutFilter"
            class="com.bleum.javateam.skilldb.security.service.SessionTimeoutFilter">
            <custom-filter before="CONCURRENT_SESSION_FILTER" /> </beans:bean>
        -->

        <!--
            <ldap-server id="contextSource"
            url="ldap://cn.bleum.com:389/OU=Bleum,DC=cn,DC=bleum,DC=com"
            manager-dn="(CN={0}),OU=JavaBench,OU=Development,OU=Bleum,DC=cn,DC=bleum,DC=com"
            manager-password="({1})" />
        -->
        <beans:bean id="contextSource"
            class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
            <beans:constructor-arg
                value="ldap://cn.bleum.com:389/OU=Bleum,DC=cn,DC=bleum,DC=com" />
            <beans:property name="userDn"
                value="CN=Vincent Meng,OU=JavaBench,OU=Development,OU=Bleum,DC=cn,DC=bleum,DC=com" />
            <beans:property name="password" value="BLpla,37410" />
        </beans:bean>

        <beans:bean id="userSearch"
            class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
            <beans:constructor-arg index="0" value="" />
            <beans:constructor-arg index="1" value="(CN={0})" />
            <beans:constructor-arg index="2" ref="contextSource" />
            <beans:property name="searchSubtree" value="true" />
        </beans:bean>

        <beans:bean id="ldapAuthProvider"
            class="org.springframework.security.providers.ldap.LdapAuthenticationProvider">
            <custom-authentication-provider />
            <beans:constructor-arg>
                <beans:bean
                    class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
                    <beans:constructor-arg ref="contextSource" />
                    <beans:property name="userSearch" ref="userSearch" />
                </beans:bean>
            </beans:constructor-arg>
            <beans:constructor-arg>
                <beans:bean
                    class="com.bleum.javateam.skilldb.security.service.AuthoritiesPopulator">
                    <beans:constructor-arg ref="userServiceImpl" />
                </beans:bean>
            </beans:constructor-arg>
        </beans:bean>

        <beans:bean id="anonymousAuthenticationProvider"
            class="org.springframework.security.providers.anonymous.AnonymousAuthenticationProvider">
            <beans:property name="key" value="anonymous" />
        </beans:bean>

        <beans:bean id="authenticationManager"
            class="org.springframework.security.providers.ProviderManager">
            <beans:property name="providers">
                <beans:list>
                    <beans:ref local="ldapAuthProvider" />
                    <beans:ref local="anonymousAuthenticationProvider" />
                </beans:list>
            </beans:property>
        </beans:bean>

        <beans:bean id="authenticationProcessingFilter"
            class="org.springframework.security.ui.webapp.AuthenticationProcessingFilter">
            <beans:property name="authenticationManager" ref="authenticationManager" />
            <beans:property name="authenticationFailureUrl" value="/login.jsp?error=true" />
            <beans:property name="defaultTargetUrl" value="/profile.html" />
            <beans:property name="filterProcessesUrl" value="/j_spring_security_check" />
        </beans:bean>

        <beans:bean id="securityContextHolderAwareRequestFilter"
            class="org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter" />

        <beans:bean id="anonymousProcessingFilter"
            class="com.bleum.javateam.skilldb.security.service.CustomizeAnonymousProcessingFilter">
            <!--
                <beans:property name="key" value="anonymous"/> <beans:property
                name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
            -->
            <custom-filter before="ANONYMOUS_FILTER" />
            <beans:property name="userAttribute" value="anonymousUser,ROLE_COMMON_USER" />
            <beans:property name="key" value="springsecurity" />
        </beans:bean>

        <beans:bean id="exceptionTranslationFilter"
            class="org.springframework.security.ui.ExceptionTranslationFilter">
            <beans:property name="authenticationEntryPoint">
                <beans:bean
                    class="org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint">
                    <beans:property name="loginFormUrl" value="/login.jsp" />
                    <beans:property name="forceHttps" value="false" />
                </beans:bean>
            </beans:property>

            <beans:property name="accessDeniedHandler">
                <beans:bean class="org.springframework.security.ui.AccessDeniedHandlerImpl">
                    <beans:property name="errorPage" value="/accessDenied.html" />
                </beans:bean>
            </beans:property>

        </beans:bean>

        <beans:bean id="dataSource"
            class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <beans:property name="driverClassName"
                value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
            <beans:property name="url"
                value="jdbc:sqlserver://192.168.0.71:1433;DatabaseName=skilldb" />
            <beans:property name="username" value="root" />
            <beans:property name="password" value="javateam2010!" />
        </beans:bean>

        <beans:bean id="filterInvocationDefinitionSource"
            class="com.bleum.javateam.skilldb.security.service.JdbcFilterInvocationDefinitionSourceFactoryBean">
            <beans:property name="dataSource" ref="dataSource" />
            <beans:property name="resourceQuery"
                value="
                select re.res_value,r.role_name
                  from role r
                  join role_resource rr
                    on r.id=rr.roleId
                  join resource re
                    on re.ID=rr.resourceId
              order by re.res_priority
            " />
        </beans:bean>


        <beans:bean id="accessDecisionManager"
            class="com.bleum.javateam.skilldb.security.service.DecisionManager" />

        <beans:bean id="filterSecurityInterceptor"
            class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
            <beans:property name="authenticationManager" ref="authenticationManager" />
            <beans:property name="accessDecisionManager" ref="accessDecisionManager" />
            <!--
                <beans:bean
                class="org.springframework.security.vote.AffirmativeBased">
                <beans:property name="allowIfAllAbstainDecisions" value="false" />
                <beans:property name="decisionVoters"> <beans:list> <beans:bean
                class="org.springframework.security.vote.RoleVoter" > <beans:property
                name="rolePrefix" value=""/> </beans:bean> <beans:bean
                class="org.springframework.security.vote.AuthenticatedVoter" />
                </beans:list> </beans:property> </beans:bean>
           
            </beans:property>
        -->
            <beans:property name="objectDefinitionSource" ref="filterInvocationDefinitionSource" />
        </beans:bean>
    </beans:beans>

     

    相应的实现类(此类借鉴他人的方法):

    public class JdbcFilterInvocationDefinitionSourceFactoryBean extends
        JdbcDaoSupport implements FactoryBean {
        private String resourceQuery;

        public boolean isSingleton() {
        return true;
        }

        public Class getObjectType() {
        return FilterInvocationDefinitionSource.class;
        }

        public Object getObject() {
        return new DefaultFilterInvocationDefinitionSource(
            this.getUrlMatcher(), this.buildRequestMap());
        }

        protected Map<String, String> findResources() {   
        ResourceMapping resourceMapping = new ResourceMapping(getDataSource(),
            resourceQuery);

        Map<String, String> resourceMap = new LinkedHashMap<String, String>();

        for (Resource resource : (List<Resource>) resourceMapping.execute()) {
            String url = resource.getUrl();
            String role = resource.getRole();

            if (resourceMap.containsKey(url)) {
            String value = resourceMap.get(url);
            resourceMap.put(url, value + "," + role);
            } else {
            resourceMap.put(url, role);
            }
        }
        return resourceMap;
        }

        protected LinkedHashMap<RequestKey, ConfigAttributeDefinition> buildRequestMap() {
        LinkedHashMap<RequestKey, ConfigAttributeDefinition> requestMap = null;
        requestMap = new LinkedHashMap<RequestKey, ConfigAttributeDefinition>();

        ConfigAttributeEditor editor = new ConfigAttributeEditor();

        Map<String, String> resourceMap = this.findResources();

        for (Map.Entry<String, String> entry : resourceMap.entrySet()) {
            RequestKey key = new RequestKey(entry.getKey(), null);
            editor.setAsText(entry.getValue());
            requestMap.put(key, (ConfigAttributeDefinition) editor.getValue());
        }   
        return requestMap;
        }

        protected UrlMatcher getUrlMatcher() {
        return new AntUrlPathMatcher();
        }

        public void setResourceQuery(String resourceQuery) {
        this.resourceQuery = resourceQuery;
        }

        private class Resource {
        private String url;
        private String role;

        public Resource(String url, String role) {
            this.url = url;
            this.role = role;
        }

        public String getUrl() {
            return url;
        }

        public String getRole() {
            return role;
        }
        }

        private class ResourceMapping extends MappingSqlQuery {
        protected ResourceMapping(DataSource dataSource, String resourceQuery) {
            super(dataSource, resourceQuery);
            compile();
        }

        protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
            String url = rs.getString(1);
            String role = rs.getString(2);
            Resource resource = new Resource(url, role);

            return resource;
        }
        }

    展开全文
  • Providing upload functionality in our plugin is always tricky ... We need to be able to provide a good user experience (UX) for uploading, while also keeping an eye on the security concerns that...

    Providing upload functionality in our plugin is always tricky business. We need to be able to provide a good user experience (UX) for uploading, while also keeping an eye on the security concerns that come with it. If it’s not done properly, we could potentially put the site at risk for any security vulnerabilities that arise.

    在我们的插件中提供上传功能始终是一件棘手的事情。 我们需要能够为上传提供良好的用户体验(UX),同时还要注意随之而来的安全性问题。 如果处理不当,我们有可能使站点面临任何出现的安全漏洞的风险。

    Instead of building the whole solution from scratch, we can leverage the WordPress core code to our advantage to speed up the development, specifically utilising async-upload.php file that’s located in the wp-admin directory.

    无需从头开始构建整个解决方案,我们可以利用WordPress核心代码来加快开发速度,特别是利用wp-admin目录中的async-upload.php文件。

    Using the async-upload.php file has several advantages. Firstly, since it’s used by WordPress core itself for async uploading in the media library, we can be assured that the code is up to standard. Plus, all the validation and privilege checking has been done so we don’t need to do that ourselves.

    使用async-upload.php文件具有多个优点。 首先,由于WordPress核心本身已将其用于媒体库中的异步上传,因此我们可以确保代码符合标准。 另外,所有验证和特权检查都已经完成,因此我们不需要自己做。

    要求 (Requirements)

    There are several rules that need to be followed if we’re to utilise this script. Here’s a breakdown to each of them.

    如果要使用此脚本,需要遵循几个规则。 这是每个人的细分。

    • The file input that is used must has its name attribute set to async-upload

      使用的file输入必须将其name属性设置为async-upload

    This is due to the fact that once the validation is passed inside the async-upload.php file, wp_ajax_upload_attachment which then further calls the media_handle_upload function that uses async-upload as the first arguments. Using any other value will not work.

    这是由于以下事实:一旦在async-upload.php文件中传递了验证, wp_ajax_upload_attachment进一步调用media_handle_upload函数,该函数使用async-upload作为第一个参数。 使用任何其他值将不起作用。

    • The nonce that we sent alongside the AJAX request must use the default _wpnonce key with the value generated from the wp_create_nonce('media-form') function

      我们与AJAX请求一起发送的随机数必须使用默认的_wpnonce密钥以及wp_create_nonce('media-form')函数生成的值

    This is due to the validation in the form of check_ajax_referer that is happening inside the wp_ajax_upload_attachment function.

    这是由于在形式验证check_ajax_referer是在内部发生wp_ajax_upload_attachment功能。

    • The data sent via the AJAX request also needs to have a key called action with a value of upload-attachment

      通过AJAX请求发送的数据还需要有一个名为action的键,其值为上upload-attachment

    This is validated inside the async-upload.php file which will only trigger the wp_ajax_upload_attachment function when the value is set correctly.

    这在async-upload.php文件中进行了验证,该文件仅在正确设置该值时才会触发wp_ajax_upload_attachment函数。

    关于插件 (About the Plugin)

    To better illustrate the idea of building custom AJAX file upload functionality into a plugin, we will create a simple plugin to test it out.

    为了更好地说明将自定义AJAX文件上传功能构建到插件中的想法,我们将创建一个简单的插件对其进行测试。

    For the purpose of this tutorial, we’re going to create a plugin that allows registered users to submit an image for some sort of contest. We will have a frontend submission form, which means that the upload form will be displayed on a certain page where the user can directly upload the image. This is a perfect candidate to implement AJAX uploading functionality.

    就本教程而言,我们将创建一个插件,允许注册用户提交某种比赛的图像。 我们将有一个前端提交表单,这意味着上载表单将显示在用户可以直接上载图像的特定页面上。 这是实现AJAX上传功能的理想之选。

    Since we’re trying to keep things simple, let’s define some guidelines of what this plugin will and will not do, for the sake of the length of this tutorial.

    由于我们试图使事情保持简单,因此,为了本教程的长度,让我们定义一些有关此插件将要做什么和不做什么的准则。

    The plugin will be able to:

    该插件将能够:

    • Allow the admin to attach the form to any page via a shortcode.

      允许管理员通过简码将表单附加到任何页面。
    • Show the registered users a submission form with AJAX upload functionality.

      向注册用户显示具有AJAX上传功能的提交表单。
    • Send an email to notify the site admin upon submission.

      发送电子邮件以在提交后通知站点管理员。

    For the scope of this tutorial, the plugin will not:

    在本教程的范围内,该插件不会:

    • Store any submissions into the database.

      将所有提交内容存储到数据库中。
    • View the submissions in the backend.

      在后端查看提交的内容。
    • Allow anonymous users to upload the files.

      允许匿名用户上传文件。

    引导插件 (Bootstrapping the Plugin)

    Head to the wp-content/plugins folder and create a new folder where all of our plugin codes will reside. I will use the name sitepoint-upload for the rest of this tutorial, with prefix of su_ to all functions and hooks callbacks.

    转到wp-content/plugins文件夹并创建一个新文件夹,所有我们的插件代码都将驻留在该文件夹中。 在本教程的其余部分中,我将使用名称sitepoint-upload ,所有功能均带有su_前缀,并挂钩回调。

    Next, create the main plugin file, with the same name as the folder, to make things easier. Inside the plugin folder, we will also have a js folder which contains an empty script.js file for now.

    接下来,使用与文件夹相同的名称创建主插件文件,以使事情变得容易。 在plugin文件夹中,我们还将有一个js文件夹,其中现在包含一个空的script.js文件。

    Here’s an updated directory structure for our plugin.

    这是我们插件的更新目录结构。

    wp-content/
    |-- plugins/
        |-- sitepoint-upload/
            |-- js/
            |   |-- script.js
            |--sitepoint-upload.php

    Let’s put in a simple plugin header into the plugin main file, sitepoint-upload.php, then go ahead to the plugins page to activate it. Here’s example of mine:

    让我们在插件主文件sitepoint-upload.php放入一个简单的插件头,然后转到插件页面将其激活。 这是我的示例:

    <?php
    /*
    Plugin Name: Simple Uploader
    Plugin URI: https://www.sitepoint.com
    Description: Simple plugin to demonstrate AJAX upload with WordPress
    Version: 0.1.0
    Author: Firdaus Zahari
    Author URI: https://www.sitepoint.com/author/fzahari/
    */

    排队脚本 (Enqueue the Script)

    We then can enqueue the empty script.js to the frontend, which will be used to handle our AJAX uploading functionality, as well as enhancing the submission form.

    然后,我们可以将空的script.js排队到前端,该前端将用于处理AJAX上载功能以及增强提交表单。

    function su_load_scripts() {
        wp_enqueue_script('image-form-js', plugin_dir_url( __FILE__ ) . 'js/script.js', array('jquery'), '0.1.0', true);
    }
    add_action('wp_enqueue_scripts', 'su_load_scripts');

    We’re also going to localize some data which will be used inside script.js using the function wp_localize_script. We need three things, a correct URL to both admin-ajax.php since we’re going to submit the form via AJAX as well, and also the URL to the async-upload.php file. The third item we need to localize is the nonce, which will be generated using the wp_create_nonce function.

    我们还将使用wp_localize_script函数本地化将在script.js使用的一些数据。 我们需要三件事,一个正确的admin-ajax.php URL,因为我们也将通过AJAX提交表单,还需要一个async-upload.php文件的URL。 我们需要本地化的第三项是随机数,它将使用wp_create_nonce函数生成。

    The updated callback function for our wp_enqueue_scripts hook looks like the this:

    wp_enqueue_scripts挂钩的更新后的回调函数如下所示:

    function su_load_scripts() {
        wp_enqueue_script('image-form-js', plugin_dir_url( __FILE__ ) . 'js/script.js', array('jquery'), '0.1.0', true);
    
        $data = array(
                    'upload_url' => admin_url('async-upload.php'),
                    'ajax_url'   => admin_url('admin-ajax.php'),
                    'nonce'      => wp_create_nonce('media-form')
                );
    
        wp_localize_script( 'image-form-js', 'su_config', $data );
    }
    add_action('wp_enqueue_scripts', 'su_load_scripts');

    注册提交表单的简码 (Register the Shortcode for a Submission Form)

    We will then need to register the shortcode for our submission form, so that we can easily put it in any pages we want instead of writing the same markup over and over again. Our form will have:

    然后,我们将需要为提交表单注册简码,以便我们可以轻松地将其放入所需的任何页面中,而不必一遍又一遍地编写相同的标记。 我们的表格将包含:

    • A text input field for user’s name

      用户名的文本输入字段
    • Another email input field for user’s email address

      用户电子邮件地址的另一个电子邮件输入字段
    • The async-upload file input for AJAX uploading

      用于AJAX上传的async-upload文件输入

    • A bunch of placeholder div that will be used for email preview, error messages and other items.

      一堆占位符div ,将用于电子邮件预览,错误消息和其他项目。

    We will also be disabling the submission form entirely if the user is not currently logged in, and display a login link instead.

    如果用户当前尚未登录,我们还将完全禁用提交表单,而是显示一个登录链接。

    function su_image_form_html(){
        ob_start();
        ?>
            <?php if ( is_user_logged_in() ): ?>
                <p class="form-notice"></p>
                <form action="" method="post" class="image-form">
                    <?php wp_nonce_field('image-submission'); ?>
                    <p><input type="text" name="user_name" placeholder="Your Name" required></p>
                    <p><input type="email" name="user_email" placeholder="Your Email Address" required></p>
                    <p class="image-notice"></p>
                    <p><input type="file" name="async-upload" class="image-file" accept="image/*" required></p>
                    <input type="hidden" name="image_id">
                    <input type="hidden" name="action" value="image_submission">
                    <div class="image-preview"></div>
                    <hr>
                    <p><input type="submit" value="Submit"></p>
                </form>
            <?php else: ?>
                <p>Please <a href="<?php echo esc_url( wp_login_url( get_permalink() ) ); ?>">login</a> first to submit your image.</p>
            <?php endif; ?>
        <?php
        $output = ob_get_clean();
        return $output;
    }
    add_shortcode('image_form', 'su_image_form_html');

    A few explanations about the shortcode callback function above:

    有关上述shortcode回调函数的一些解释:

    • The shortcode we register is image_form.

      我们注册的简码是image_form

    • We’re using output buffering so that we can be more flexible with what we expose inside the shortcode callback function.

      我们正在使用输出缓冲,以便我们可以更灵活地利用shortcode回调函数中公开的内容。

    • We’re also restricting file selection for images only via the accept attribute on the file input. Note that this doesn’t replace the actual file validation. (More info)

      我们还仅通过文件输入上的accept属性来限制图像的文件选择。 请注意,这并不能代替实际的文件验证。 (更多信息)

    • For the login URL, we supply the current page permalink into the wp_login_url so that the user will be redirected back to our submission page upon successful login.

      对于登录URL,我们将当前页面永久链接提供到wp_login_url以便在成功登录后将用户重定向到我们的提交页面。

    upload_files功能添加到特定用户角色 (Add upload_files Capability to the Specific User Roles)

    To make sure our plugin is functioning properly, we need to alter the capability of role of subscriber because by default, users with the role subscriber don’t have the capability to upload files.

    为确保我们的插件正常运行,我们需要更改subscriber角色的功能,因为默认情况下,具有subscriber角色的subscriber不具有上传文件的能力。

    function su_allow_subscriber_to_uploads() {
        $subscriber = get_role('subscriber');
    
        if ( ! $subscriber->has_cap('upload_files') ) {
            $subscriber->add_cap('upload_files');
        }
    }
    add_action('admin_init', 'su_allow_subscriber_to_uploads');

    Note, the subscriber role will only be modified if it still doesn’t have upload_files capability.

    请注意,仅当subscriber角色仍然不具有upload_files功能时,才会对其进行修改。

    Now that we’re finished with our plugin basics, let’s create a new page that will display our submission form.

    现在我们已经完成了插件基础知识,让我们创建一个新页面来显示我们的提交表单。

    Submit Your Image WordPress

    This is how the form looks on the frontend, on a default WordPress installation with twentysixteen theme active.

    这是表单在前端上的外观,在默认的WordPress安装中, twentysixteen主题处于活动状态。

    Frontend submission form (Logged In)

    If we’re logged out of the site, the notice will be shown instead.

    如果我们退出网站,则会显示该通知。

    Frontend submission form (Logged out)

    Looks like our plugin comes together nicely!

    看起来我们的插件很好地结合在一起!

    实施AJAX上传 (Implementing the AJAX Upload)

    Now that our base plugin has been configured correctly, we can focus on the core functionality that we need to do, the AJAX upload.

    现在我们的基本插件已正确配置,我们可以专注于我们需要做的核心功能,即AJAX上传。

    Let’s open up our script.js file that is located inside the js folder to proceed. We will first wrap the whole code within immediately-invoked function expression (IIFE).

    让我们打开位于js文件夹内的script.js文件继续。 我们首先将整个代码包装在立即调用的函数表达式(IIFE)中

    Next, we will cache a few selectors to speed up our code. This includes the references to the image preview div, the input file, as well as the div used to display the upload notice.

    接下来,我们将缓存一些选择器以加速我们的代码。 这包括对图像预览div ,输入文件以及用于显示上传通知的div引用。

    (function($) {
        $(document).ready(function() {
            var $formNotice = $('.form-notice');
            var $imgForm    = $('.image-form');
            var $imgNotice  = $imgForm.find('.image-notice');
            var $imgPreview = $imgForm.find('.image-preview');
            var $imgFile    = $imgForm.find('.image-file');
            var $imgId      = $imgForm.find('[name="image_id"]');
        });
    })(jQuery);

    The cached selectors will be useful to us in the long run. As mentioned before, there are few rules that need to be followed in order for the validation in the async-upload.php file to pass. To do that, we will make a POST request via AJAX to the async-upload.php file with the correct key or value pairs as specified. This can be done using the FormData API.

    从长远来看,缓存的选择器对我们很有用。 如前所述,为了使async-upload.php文件中的验证通过,需要遵循一些规则。 为此,我们将使用指定的正确键或值对通过AJAX向async-upload.php文件发出POST请求。 这可以使用FormData API来完成。

    We will first hook on the change event on the file input, and if the input is changed, only then we will trigger the AJAX upload.

    我们将首先挂钩文件输入上的change事件,如果输入被更改,则只有这样我们才会触发AJAX上传。

    $imgFile.on('change', function(e) {
        e.preventDefault();
    
        var formData = new FormData();
    
        formData.append('action', 'upload-attachment');
        formData.append('async-upload', $imgFile[0].files[0]);
        formData.append('name', $imgFile[0].files[0].name);
        formData.append('_wpnonce', su_config.nonce);
    
        $.ajax({
            url: su_config.upload_url,
            data: formData,
            processData: false,
            contentType: false,
            dataType: 'json',
            type: 'POST',
            success: function(resp) {
                console.log(resp);
            }
        });
    });

    For now, let’s leave the code as above, and test the upload functionality to make sure we’re on the right track. Using the developer console (depending on what browser is used), check the console tab for the output. A sample of response given by async-upload.php file upon successful upload as follows:

    现在,让我们保留上面的代码,并测试上传功能,以确保我们处在正确的轨道上。 使用开发者控制台(取决于所使用的浏览器),检查控制台选项卡的输出。 成功上传后, async-upload.php文件给出的响应示例如下:

    upload php

    We can also check for the file existence by going directly to the wp-content/uploads directory. Now that we see that the uploading functionality is working well, let’s work on a few improvements to our upload script. Here are some improvements that I can think of:

    我们还可以通过直接转到wp-content/uploads目录来检查文件是否存在。 现在,我们看到上传功能运行良好,让我们对上传脚本进行一些改进。 我可以想到以下一些改进:

    • Show a progress bar or text during the upload process.

      在上传过程中显示进度条或文本。
    • Show the uploaded image preview on successful upload.

      成功上传后显示上传的图片预览。
    • Display error if the upload failed.

      如果上传失败,则显示错误。
    • Provide a way for user to upload a new image to replace the current one.

      为用户提供一种上传新图像来替换当前图像的方法。

    Let’s see how to do this one by one.

    让我们来看看如何一一完成。

    在上传过程中显示进度栏或文本 (Show a Progress Bar or Text During the Upload Process)

    This is actually a simple one. We only need to define a callback for the beforeSend of jQuery AJAX. Somewhere in the code for the AJAX upload, put the code block as follows:

    这实际上很简单。 我们只需要为jQuery AJAX的beforeSend定义一个回调。 在用于AJAX上传的代码中的某处,将代码块如下所示:

    beforeSend: function() {
        $imgFile.hide();
        $imgNotice.html('Uploading&hellip;').show();
    },

    We use the empty div with the class image-notice defined previously to show the progress text to the user. We’re also hiding the file input during the upload process.

    我们使用空div和先前定义的类image-notice来向用户显示进度文本。 在上传过程中,我们还将隐藏文件输入。

    For the supported browsers, we can even show the upload percentage. What we can do is to override the original jQuery xhr object with our own. Add this to the $.ajax configuration:

    对于受支持的浏览器,我们甚至可以显示上传百分比。 我们可以做的是用我们自己的对象覆盖原始的jQuery xhr对象。 将此添加到$.ajax配置中:

    xhr: function() {
        var myXhr = $.ajaxSettings.xhr();
    
        if ( myXhr.upload ) {
            myXhr.upload.addEventListener( 'progress', function(e) {
                if ( e.lengthComputable ) {
                    var perc = ( e.loaded / e.total ) * 100;
                    perc = perc.toFixed(2);
                    $imgNotice.html('Uploading&hellip;(' + perc + '%)');
                }
            }, false );
        }
    
        return myXhr;
    }

    What this code does for supported browsers, is simply appending the upload percentage after the Uploading text, which is a rather nice enhancement. For unsupported browsers, nothing will happen which is a nice graceful degradation.

    这段代码对受支持的浏览器的作用是,只需在“ Uploading文本之后附加上载百分比,这是一个相当不错的增强。 对于不受支持的浏览器,将不会发生任何事情,这是一个很好的优雅降级。

    成功上传时显示上传的图像预览,或者上传失败时显示错误 (Show the Uploaded Image Preview on Successful Upload or Display Error When the Upload Failed)

    Depending on the response we get from the async-upload.php script, we will show a different message to the user. If the success key is set to true, we can then show the uploaded image to the user, and hide the file input. If the upload fails, we will replace the text inside the div with image-notice previously.

    根据从async-upload.php脚本获得的响应,我们将向用户显示不同的消息。 如果success密钥设置为true ,那么我们可以向用户显示上传的图像,并隐藏文件输入。 如果上传失败,我们之前会用image-notice替换div的文本。

    success: function(resp) {
        if ( resp.success ) {
            $imgNotice.html('Successfully uploaded.');
    
            var img = $('<img>', {
                src: resp.data.url
            });
    
            $imgId.val( resp.data.id );
            $imgPreview.html( img ).show();
    
        } else {
            $imgNotice.html('Fail to upload image. Please try again.');
            $imgFile.show();
            $imgId.val('');
        }
    }

    $imgId is a hidden input that we’re using to reference the uploaded image ID. We’re going to use this value later for the form submission, so don’t worry about it yet.

    $imgId是一个隐藏的输入,我们将用来引用上载的图像ID。 稍后我们将使用此值进行表单提交,因此不必担心。

    为用户提供一种上传新图像以替换当前图像的方式 (Provide a Way for User to Upload a New Image to Replace the Current One)

    What we’re going to do is to provide a link as a method for the user to replace the currently uploaded image with a new one. We will change the notice shown when the upload succeeds from:

    我们要做的是提供一个链接,以供用户用新的图像替换当前上传的图像。 上传成功后,我们将更改显示的通知:

    $imgNotice.html('Successfully uploaded.');

    to

    $imgNotice.html('Successfully uploaded. <a href="#" class="btn-change-image">Change?</a>');

    Now that we have an anchor with a class of btn-change-image, we will use that to our advantage. We can then add a click event listener on that anchor, when it’s clicked, it will remove the current image preview. We will also hide the notice message, as well as display the file input again with its value which has been reset.

    现在我们有了一个带有btn-change-image类的锚,我们将利用它来发挥我们的优势。 然后,我们可以在该锚点上添加一个click事件侦听器,当单击它时,它将删除当前图像预览。 我们还将隐藏通知消息,并再次显示输入的文件及其已重置的值。

    $imgForm.on( 'click', '.btn-change-image', function(e) {
        e.preventDefault();
        $imgNotice.empty().hide();
        $imgFile.val('').show();
        $imgId.val('');
        $imgPreview.empty().hide();
    });

    We also need to reset the file input value when it’s clicked, so that the change event can be triggered again.

    单击文件输入值时,我们还需要重置它,以便可以再次触发change事件。

    $imgFile.on('click', function() {
        $(this).val('');
        $imgId.val('');
    });

    Before we proceed to the next section, let’s run through the uploading functionality once again and see if everything works as intended.

    在继续下一节之前,让我们再次浏览上传功能,看看一切是否按预期进行。

    完成插件 (Completing the Plugin)

    We’re going to handle the form submission via AJAX, so we’re binding an event listener to the submit event of that form.

    我们将通过AJAX处理表单提交,因此我们将事件侦听器绑定到该表单的submit事件。

    $imgForm.on('submit', function(e) {
        e.preventDefault();
    
        var data = $(this).serialize();
    
        $.post( su_config.ajax_url, data, function(resp) {
            if ( resp.success ) {
                $formNotice.css('color', 'green');
                $imgForm[0].reset();
                $imgNotice.empty().hide();
                $imgPreview.empty().hide();
                $imgId.val('');
                $imgFile.val('').show();
            } else {
                $formNotice.css('color', 'red');
            }
    
            $formNotice.html( resp.data.msg );
        });
    });

    Based on the above code, we’re going to process the submission on the backend using the built-in WordPress AJAX action. Upon successful submission, we’re going to reset the form, remove the image preview, as well as set the form notice text to green.

    基于以上代码,我们将使用内置的WordPress AJAX action在后端处理提交。 成功提交后,我们将重置表单,删除图像预览,并将表单通知文本设置为绿色。

    For a failed submission, we simply set the form notice text colour to red. This will allow the user to review the form data, before retrying again.

    对于失败的提交,我们只需将表单通知文本颜色设置为红色。 这将允许用户在重试之前查看表单数据。

    Now, open up the plugin main file again to add the AJAX callback. Since we’re setting the action value to image_submission, we will need to add a valid callback to the wp_ajax_image_submission action.

    现在,再次打开插件主文件以添加AJAX回调。 由于我们将action值设置为image_submission ,因此我们需要向wp_ajax_image_submission动作添加有效的回调。

    add_action('wp_ajax_image_submission', 'su_image_submission_cb');

    In the callback function, there are a few things that need to be done first. We need to check for a valid AJAX nonce, as well as validating the user inputs. For the scope of this tutorial, we’re going to simply email the site admin for any new submission.

    在回调函数中,首先需要完成一些事情。 我们需要检查有效的AJAX随机数,以及验证用户输入。 在本教程的范围内,我们将简单地通过电子邮件向网站管理员发送任何新提交的内容。

    Here’s the full code for the AJAX callback function:

    这是AJAX回调函数的完整代码:

    function su_image_submission_cb() {
        check_ajax_referer('image-submission');
    
        $user_name  = filter_var( $_POST['user_name'],FILTER_SANITIZE_STRING );
        $user_email = filter_var( $_POST['user_email'], FILTER_VALIDATE_EMAIL );
        $image_id   = filter_var( $_POST['image_id'], FILTER_VALIDATE_INT );
    
        if ( ! ( $user_name && $user_email && $image_id ) ) {
            wp_send_json_error( array('msg' => 'Validation failed. Please try again later.') );
        }
    
        $to      = get_option('admin_email');
        $subject = 'New image submission!';
        $message = sprintf(
                        'New image submission from %s (%s). Link: %s',
                        $user_name,
                        $user_email,
                        wp_get_attachment_url( $image_id )
                    );
    
        $result = wp_mail( $to, $subject, $message );
    
        if ( $result ) {
            wp_send_json_error( array('msg' => 'Email failed to send. Please try again later.') );
        } else {
            wp_send_json_success( array('msg' => 'Your submission successfully sent.') );
        }
    }

    For our purpose, a simple check_ajax_referer check and native filter_var PHP function is suffice for our use case. We’re also going to utilise the wp_send_json_error and wp_send_json_success function to send back the response.

    就我们的目的而言,一个简单的check_ajax_referer检查和本机filter_var PHP函数就足够了。 我们还将利用wp_send_json_errorwp_send_json_success函数发送回响应。

    With that, our plugin is finished and fully functional. To verify, try completing the form properly and see if the email is received with the link to the uploaded image.

    这样,我们的插件就完成了并且功能齐全。 要进行验证,请尝试正确填写表单,然后查看是否收到了带有上载图像链接的电子邮件。

    进一步改进 (Further Improvements)

    Since the objective of this tutorial is to demonstrate how to do AJAX uploading via the internal async-upload.php file, we’re definitely cutting things short in a few places. Here are some suggestions that can improve our simple plugin overall.

    由于本教程的目的是演示如何通过内部async-upload.php文件进行AJAX上传,因此我们肯定会在一些地方async-upload.php一些工作。 这里有一些建议,可以从整体上改善我们的简单插件。

    • Adding more fields to the form to capture any additional value for submission.

      在表单中添加更多字段以捕获提交的任何其他值。
    • Enqueue a separate CSS file to better style the form, notice and upload progress.

      排队一个单独CSS文件,以更好地样式化表单,注意和上传进度。
    • Save the submitted data into the database, so that we can review it back again.

      将提交的数据保存到数据库中,以便我们可以再次查看它。
    • Doing more validation to the upload process so that it can be more secure.

      对上传过程进行更多验证,以使其更加安全。

    The full source code of the plugin is available on GitHub.

    该插件的完整源代码可在GitHub找到

    结论 (Conclusion)

    As a conclusion, implementing AJAX upload in a plugin can be sped up if we know where to look. By using the async-upload.php file, we can reduce the development time to implement the feature, as well as gaining some confidence since the same file is used by WordPress core to process the user upload in the administration dashboard.

    结论是,如果我们知道从哪里看,则可以加快在插件中实现AJAX上传的速度。 通过使用async-upload.php文件,我们可以减少开发时间来实现该功能,并获得一定的信心,因为WordPress核心使用同一文件来处理管理仪表板中的用户上传。

    翻译自: https://www.sitepoint.com/enabling-ajax-file-uploads-in-your-wordpress-plugin/

    展开全文
  • 设计师马丁 ai教程文件Curiosity is a fundamental part of every Designer’s... As User Experience (UX) Designers, finding the best way to meet the users and the business needs is our main goal. However,...

    设计师马丁 ai教程文件

    Curiosity is a fundamental part of every Designer’s personality. As User Experience (UX) Designers, finding the best way to meet the users and the business needs is our main goal. However, “the best way” should not be the most trendy or pretty one, but the most efficient one. Researching and understanding Artificial Intelligence (AI) capabilities enables UX Designers to create AI-powered proposals with a human-centred approach, as well as boost our workflows.

    好奇心是每个设计师个性的基本组成部分。 作为用户体验(UX)设计师,寻找满足用户和业务需求的最佳方法是我们的主要目标。 但是,“最佳方法”不应该是最时髦或最漂亮的方法,而应该是最有效的方法。 通过研究和理解人工智能(AI)功能, UX设计人员可以以人为本的方式创建基于AI的提案 ,并增强我们的工作流程。

    Design is not just what it looks like and feels like. Design is how it works.” Steve Jobs

    设计不仅是外观和感觉。 设计是它的工作方式。 ”史蒂夫·乔布斯

    AI与否 (AI or not AI)

    AI plays an extremely important role in improving the users’ experience by providing disruptive and much more efficient solutions. However, many companies are forcing their teams to introduce AI features just because it is a buzzword, without understanding its capabilities, and if it is worth investing in it. Introducing AI must be a decision based on the belief that it is the best way to improve the product. A problem should not be created to introduce AI. AI should be introduced as the best way to solve a pre-identified problem. In addition, many Product Owners and Designers face challenges in understanding how AI works and what it can provide. So, even though they identify a problem that could be solved with AI, they do not follow that path due to a lack of knowledge.

    通过提供颠覆性的,效率更高的解决方案,人工智能在改善用户体验方面发挥着极其重要的作用。 但是,许多公司正迫使他们的团队引入AI功能,只是因为它是一个流行语,而没有了解其功能以及是否值得投资。 引入AI必须是基于以下信念的决策,即这是改进产品的最佳方法。 引入AI不应创建问题。 应该引入AI作为解决预先确定的问题的最佳方法 。 此外,许多产品负责人和设计师在理解AI的工作原理和提供的功能方面也面临挑战。 因此,即使他们确定了AI可以解决的问题,但由于缺乏知识,他们也没有遵循这条道路。

    Based on this, let’s first have a high-level look at what AI, Machine Learning and Deep Learning are:

    基于此,让我们首先从高层次看一下什么是AI,机器学习和深度学习:

    1 / AI (1/ AI)

    AI is the broadest way to think about computer intelligence. It can be associated with anything from a voice-recognition system to a playlist recommendation based on your interests. The technology can be categorized into two main groups: artificial narrow intelligence (ANI) and artificial general intelligence (AGI).

    人工智能是思考计算机智能的最广泛方法。 它可以与任何内容相关联,从语音识别系统到根据您的兴趣推荐的播放列表。 这项技术可以分为两大类:人工智能(ANI)和人工智能(AGI)。

    • Artificial narrow intelligence (ANI): It is where we currently are. Narrow AI can perform one specific task. Apple’s face recognition system, a computer playing chess or even a self-driven car are all examples of ANI.

      人工智能(ANI) :这就是我们目前的位置。 窄AI可以执行一项特定任务。 苹果公司的面部识别系统,下棋的计算机甚至是无人驾驶汽车都是ANI的例子。

    • Artificial general intelligence (AGI): It is where we are going. This type of technology would own the cognitive abilities to understands the world as we do and can perform a range of tasks. AGI is considered “human-level”.

      人工智能(AGI) :这就是我们要去的地方。 这种技术将像我们一样拥有认知世界的认知能力,并可以执行一系列任务。 AGI被认为是“人类层面的”。

    Some people consider a third group called Artificial superintelligence (ASI). Nick Bostrom, Oxford philosopher who popularized the term “superintelligence” defines it as “an intellect that is much smarter than the best human brains in practically every field, including scientific creativity, general wisdom and social skills.” The technological singularity is a hypothesis predicted on the creation of artificial superintelligence. This would trigger a kind of tipping point in which enormous changes take place in human society.

    有人认为第三组称为人工超智能(ASI)。 牛津大学哲学家尼克·波斯特罗姆(Nick Bostrom)推广了“超级智能”一词,将其定义为“ 一种智力,它比包括科学创造力,一般智慧和社交技能在内的几乎每个领域的人脑都要聪明得多。 技术的奇异性是对人工超级智能技术产生的假设。 这将触发一种转折点,人类社会将发生巨大的变化。

    2 /机器学习 (2/ Machine Learning)

    Machine learning is a subfield of AI where machines take data and “learn” for themselves improving their responses. ML algorithms find patterns in massive amounts of data, and then use those patterns to make predictions such us what type of show you might like. There are two types of Machine Learning:

    机器学习是AI的一个子领域,机器可以自己获取数据并对其进行“学习”,以改善其响应能力。 机器学习算法会在大量数据中找到模式,然后使用这些模式进行预测,例如我们可能会喜欢哪种类型的节目。 机器学习有两种类型:

    • Supervised learning: The data is labelled to tell the machine exactly what patterns it should look for.

      有监督的学习 :对数据进行标记,以告诉机器准确的寻找什么模式。

    • Unsupervised learning: The data is not labelled to let the machine look for whatever patterns it can find.

      无监督学习 :未标记数据以使机器寻找可以找到的任何模式。

    2 /深度学习 (2/ Deep Learning)

    Deep learning is Machine Learning on steroids and it uses Artificial Neural Networks. It consists of several artificial neurons connected to each other to simulate the brain’s function delivering a final result in the form of a prediction. The higher the number of neurons layers, the deeper the network is. In this case, we add a third option of learning:

    深度学习是对类固醇的机器学习,它使用人工神经网络。 它由几个相互连接的人工神经元组成,以模拟大脑的功能,以预测的形式提供最终结果。 神经元层数越高,网络越深。 在这种情况下,我们添加了第三个学习选项:

    • Reinforcement learning: Learns by trial and error to achieve a clear objective

      强化学习 :通过反复试验学习以实现明确的目标

    完美的夫妻 (Perfect Couple)

    Now that we have a high-level understanding of what AI stands for, there is no doubt that it can add value not only to a company’s portfolio, but also to its workflows. Both Data Scientists/Analysts and Ux Designers jobs can be enhanced by working together:

    现在,我们对AI的含义有了一个高级的了解,毫无疑问,它不仅可以为公司的产品组合而且还可以为其工作流程增加价值。 通过一起工作,可以增强数据科学家/分析师和Ux设计师的工作:

    AI改善UX流程 (AI to improve UX processes)

    The UX Design process follows a Design Thinking approach which is based on 5 phases. AI can be used to make this process much more efficient delivering better outcomes:

    UX设计过程遵循基于5个阶段的“设计思维”方法。 人工智能可用于使此过程更加高效,从而提供更好的结果:

    1/ Empathize: The key to empathize is to understand, and to understand we need to learn about the audience.

    1 /移情:移情的关键是理解,要了解我们需要了解听众。

    [AI to improve Research] UX Researchers collect huge amounts of data. Analyzing it takes a lot of time and it can be a very complex task. AI can find patterns based on users behaviours that would be difficult to identify by a human. It can also cluster users to build segments and to understand what binds them together.This will optimize the UX Researcher work and let the UX Designer provide more meaningful prototypes as well as gather better feedback minimizing product errors.

    [AI改善研究] UX研究人员收集了大量数据。 分析需要很多时间,这可能是一个非常复杂的任务。 AI可以根据用户难以找到的用户行为找到模式。 它还可以将用户聚集在一起以构建细分并了解将它们捆绑在一起的内容。这将优化UX Researcher的工作,并让UX Designer提供更有意义的原型,并收集更好的反馈,从而最大程度地减少产品错误。

    2/ Define: Identifying the users' needs and prioritizing them based on the business goals and the value proposition.

    2 /定义:确定用户需求并根据业务目标和价值主张对用户进行优先排序。

    [AI to customize experiences] Some products have a wide range of user types. AI could detect profiles and past behaviours to select from a set of experiences and provide the most intuitive and positive one for each particular user.

    [AI定制体验]某些产品具有广泛的用户类型。 AI可以检测配置文件和过去的行为以从一组体验中进行选择,并为每个特定用户提供最直观,最积极的体验。

    3/ Ideate: Generating ideas for the design.

    3 / Ideate:为设计生成想法。

    4/ Prototype: Turning ideas into concrete examples.

    4 /原型:将想法变成具体的例子。

    [AI to move ideas to reality] Some existing tools are able to recognize sketches and transform standard hand-drawn designs into high fidelity mockups or source code. This allows UX Designers test high fidelity ideas much more quickly (Ulizard / Airbnb AI)

    [将思想变为现实的AI]一些现有工具能够识别草图并将标准的手绘设计转换为高保真模型或源代码。 这使UX设计人员可以更快地测试高保真度的想法(Ulizard / Airbnb AI)

    5/ Test: Evaluating the design. Iterate.

    5 /测试:评估设计。 重复。

    Infograph: Illustrates the 5 design thinking phases connecting them with AI tools to improve the workflow and a UXD process.

    UX创造有意义的AI驱动产品 (UX to create meaningful AI-powered products)

    It is important to highlight that introducing UX early on in a product development cycle helps to anticipate, mitigate and reduce errors which saves costs down the line. Fixing a problem in development costs more than fixing it in design, and much more if the product has already been launched. In addition, recovering a frustrated customer can be a really hard task.

    重要的是要强调指出,在产品开发周期的早期引入UX 有助于预测,减轻和减少错误,从而节省了成本。 解决开发中的问题要比解决设计中的问题花费更多,如果产品已经发布,则花费更多。 此外,恢复沮丧的客户可能是一项艰巨的任务。

    Also, UXers engaged in ongoing collaboration with Data Scientists help to envision what to make and to define priorities based on the users and business needs. The combination of both skill-sets helps to move from a data-centric culture to a user-centric culture that leverages data to enhance the experience.

    此外, 与数据科学家进行持续合作的UXer有助于根据用户和业务需求设想要做什么,并确定优先级 。 两种技能组合的结合有助于从以数据为中心的文化转变为以数据为基础来增强体验的以用户为中心的文化。

    1/ Empathize and 2/ Define: Before introducing any AI capability, we first need to understand the users' needs and expectations to identify the real problems.

    1 /移情定义2 /定义:在引入任何AI功能之前,我们首先需要了解用户的需求和期望,以识别出真正的问题。

    3/ Ideate: With the problem statements previously defined, the team can sort them into categories (f.i: Analytic, Functional, Interactive, Content) and start answering questions such as: Would an AI-driven solution solve a real user need? Would it be the best way to solve it for both the users and the business? Is it worth investing in it?

    3 / Ideate:利用先前定义的问题陈述,团队可以将其分类(例如:分析,功能,交互,内容),并开始回答以下问题:AI驱动的解决方案是否可以解决用户的实际需求? 对于用户和企业来说,这是解决它的最佳方法吗? 值得投资吗?

    A few examples of AI solutions for each category could be:

    每个类别的AI解决方案的一些示例可能是:

    • Analytic: Risk assessment, sentiment analysis, retroactive analysis

      分析:风险评估,情绪分析,追溯分析
    • Functional: IoT solutions, robots, mechanical apparati

      功能:物联网解决方案,机器人,机械设备
    • Interactive: Personal assistants, chatbots, Google Home, Alexa

      互动式:个人助理,聊天机器人,Google Home,Alexa
    • Content: Natural language processing, text recognition, speech-to-text conversion, Computer vision, augmented reality

      内容:自然语言处理,文本识别,语音到文本转换,计算机视觉,增强现实

    4/ Prototype and 5/ Test: By creating sketches, mockups and animated prototypes, the team has the chance to evaluate the idea, collect feedback, and improve the proposal before investing too much time and money on developing the end solution. A team that creates an AI-driven solution based on user research and testing is the best and most efficient way of improving a customer’s experience. Utilizing this approach is much more likely to produce a solution that satisfies both user needs and business objectives.

    4 /原型5 /测试:通过创建草图,模型和动画原型,团队可以在投入太多时间和金钱来开发最终解决方案之前评估想法,收集反馈并改进提案。 一个基于用户研究和测试创建AI驱动的解决方案的团队是改善客户体验的最佳,最有效的方法。 使用这种方法更有可能产生既满足用户需求又满足业务目标的解决方案。

    结论 (Conclusion)

    AI has increasingly become a strategic focus for many businesses. This strategy is certainly a wise investment. However, AI efforts will likely fail to deliver on anticipated value unless the benefits of AI are focused on solving real customer needs that improve their overall experience.

    人工智能已越来越成为许多企业的战略重点。 此策略无疑是一项明智的投资。 但是,除非AI的利益集中在解决真正的客户需求上,以改善他们的整体体验,否则AI的努力可能无法实现预期的价值。

    Currently, many UX Designers overlook simple opportunities to include AI in their projects because they face challenges understanding AI capabilities. On the other hand, many Data Scientists and Analysts focus only on the datasets without a deep knowledge about the users pain points. A multidisciplinary team consisting of both UX Designers and Data Scientists/Analysts is crucial in defining when and how to enhance a product by leveraging AI. They have the perfect skill-sets not only to help the company save money by mitigating errors, but also to move it to the next level with solutions that follow the latest trends in the market.

    当前,许多UX设计师忽略了将AI纳入其项目的简单机会,因为他们在理解AI功能方面面临挑战。 另一方面,许多数据科学家和分析师只关注数据集,而没有对用户痛点的深入了解。 由UX设计师和数据科学家/分析师组成的多学科团队对于利用AI定义何时以及如何增强产品至关重要。 他们拥有完美的技能集,不仅可以帮助公司通过减少错误来节省资金,还可以通过遵循市场最新趋势的解决方案将其提升到一个新的水平。

    Image for post
    UX Para Minas Pretas (UX For Black Women), a Brazilian organization focused on promoting equity of Black women in the tech industry through initiatives of action, empowerment, and knowledge sharing. Silence against systemic racism is not an option. Build the design community you believe in.UX Para Minas Pretas (UX For Black Women),这是一个巴西组织,致力于通过采取行动,赋权和知识共享的举措来促进科技行业中的黑人女性平等。 对系统性种族主义保持沉默是不可行的。 建立您相信的设计社区。

    翻译自: https://uxdesign.cc/a-ux-designer-lost-in-ai-1e893c61acba

    设计师马丁 ai教程文件

    展开全文
  • 管理文件系统(一)

    2010-06-16 09:18:00
    MSDN 2005 -> Win32 和 COM 开发 -> User Interface -> Windows User Experience -> Windows Shell -> Windows Shell -> Shell Programmer's Guide -> Shell Basics -> Managing the File System 翻译完这篇文章后...

    MSDN 2005 -> Win32 和 COM 开发 -> User Interface -> Windows User Experience -> Windows Shell -> Windows Shell -> Shell Programmer's Guide -> Shell Basics -> Managing the File System

    翻译完这篇文章后,对《浏览名字空间》一文的示例程序进行了修改,增加在右键单击列表项时弹出菜单,提供剪切、复制、粘贴和删除功能。每次粘贴后,会把所有目标文件(文件夹)添加到当前用户的最近文档列表中,从开始菜单中选择“文档”命令就可以看到这个列表。主要使用的API为SHFileOperationSHAddToRecentDocsSHChangeNotify。程序修改后新的界面如下:
    0_1276651262kpe4.gif

    主要代码为:

    1 在弹出右键菜单前更新菜单项状态
     


    int CShellNamespace::UpdateContextMenu(HMENU hMenu)
    {
        BOOL bRet;
        TCHAR* pNames;

        // 如果当前有选中的文件系统对象,则启用剪切/复制/删除,否则禁用
        pNames = GetSelectedItemNames();
        EnableMenuItem(hMenu,IDC_CUT,   MF_BYCOMMAND | (pNames ? MF_ENABLED : MF_GRAYED));
        EnableMenuItem(hMenu,IDC_COPY,  MF_BYCOMMAND | (pNames ? MF_ENABLED : MF_GRAYED));
        EnableMenuItem(hMenu,IDC_DELETE,MF_BYCOMMAND | (pNames ? MF_ENABLED : MF_GRAYED));
        delete []pNames;
       
        // 先前进行过剪切/复制操作,且当前文件夹是文件系统文件夹时,启用"粘贴",否则变灰
        bRet = FALSE;
        if (NULL != m_pSaveObjNames)
        {
            TCHAR szCurPath[MAX_PATH];
            bRet = SHGetPathFromIDList(m_pIdl,szCurPath);
        }
        EnableMenuItem(hMenu,IDC_PASTE,MF_BYCOMMAND | (bRet ? MF_ENABLED : MF_GRAYED));

        return 0;
    }

    GetSelectedItemNames()用于根据当前选中项生成SHFileOperation所要求格式的文件名列表,即多个以空字符结束的文件名字符串串接在一起,最后再多加一个空字符,表示整个列表的结束。如果没有选中任何项,GetSelectedItemNames()会返回空,此时使菜单中的剪切、复制、删除三项变灰。

    SHGetPathFromIDList(m_pIdl,szCurPath)会判断当前文件夹是否是文件系统文件夹。如果不是文件系统文件夹,就不可能是粘贴目标,需要使菜单中的粘贴项变灰。

    2 剪切和复制

    int CShellNamespace::Cut()
    {
        if (m_pSaveObjNames) delete []m_pSaveObjNames;
        m_pSaveObjNames = GetSelectedItemNames();
        m_bCutFlag = TRUE;
        return 0;
    }

    int CShellNamespace::Copy()
    {
        if (m_pSaveObjNames) delete []m_pSaveObjNames;
        m_pSaveObjNames = GetSelectedItemNames();
        m_bCutFlag = FALSE;
        return 0;
    }

     
    执行剪切和复制命令时只是保存选中项的文件名列表,真正的操作要到粘贴时才进行。
     
    3 粘贴
     

    int CShellNamespace::Paste()
    {
        SHFILEOPSTRUCT opStruct={0};
        TCHAR szDstDir[MAX_PATH + 10]={0};
       
        if (!SHGetPathFromIDList(m_pIdl,szDstDir)) return -1;
       
        opStruct.hwnd   = GetParent(m_pListCtrl->m_hWnd);
        opStruct.wFunc  = (m_bCutFlag ? FO_MOVE : FO_COPY);
        opStruct.pFrom  = m_pSaveObjNames;
        opStruct.pTo    = szDstDir;
        opStruct.fFlags = FOF_SIMPLEPROGRESS;

        SHFileOperation(&opStruct);

        // 所有目标文件(夹)添加到最近文档列表
        AddDstObjectsToRecentDocs(szDstDir);
       
        // 目标文件夹内容已经改变
        SHChangeNotify(SHCNE_UPDATEDIR,SHCNF_IDLIST,m_pIdl,0);
        // 源文件夹内容已经改变
        if (m_bCutFlag)
        {
            TCHAR szSrcDir[MAX_PATH];
            StrCpy(szSrcDir,m_pSaveObjNames);
            PathRemoveBackslash(szSrcDir);
            PathRemoveFileSpec(szSrcDir);
            SHChangeNotify(SHCNE_UPDATEDIR,SHCNF_PATH,szSrcDir,0);
            // 剪切后,源文件(夹)已经不存在了,不能再次粘贴
            delete []m_pSaveObjNames;
            m_pSaveObjNames = NULL;
        }

        RefreshList();

        return 0;
    }

    代码很简单,主要是调用SHFileOperation执行文件和文件夹的移动和复制操作。AddDstObjectsToRecentDocs()把所有目标文件和文件夹添加到当前用户的最近文档列表中,会在下面介绍。执行粘贴操作后,目标文件夹增加了新的内容;如果先前执行的是剪切操作,则源文件夹减少了一些内容。这时应该调用SHChangeNotify通知Shell名字空间已经发生的变化。

    4 删除

    int CShellNamespace::Delete()
    {
        SHFILEOPSTRUCT opStruct={0};
        TCHAR* pDelObjNames;
       
        pDelObjNames = GetSelectedItemNames();
        if (NULL == pDelObjNames) return -1;
       
        opStruct.hwnd   = GetParent(m_pListCtrl->m_hWnd);
        opStruct.wFunc  = FO_DELETE;
        opStruct.pFrom  = pDelObjNames;
        opStruct.fFlags = FOF_SIMPLEPROGRESS;
       
        SHFileOperation(&opStruct);
       
        // 当前文件夹内容已经发生变化
        SHChangeNotify(SHCNE_UPDATEDIR,SHCNF_IDLIST,m_pIdl,0);
       
        RefreshList();

        delete []pDelObjNames;

        return 0;
    }

     
    与移动和复制文件操作类似,只是SHFILEOPSTRUCT结构体的wFunc成员值是FO_DELETE,表示删除对象。
     
    5 添加粘贴的目标文件和文件夹到用户最近文档列表中
     


    void CShellNamespace::AddDstObjectsToRecentDocs(const TCHAR* pszDstDir)
    {
        const TCHAR* pNextItem;
        TCHAR szObjName[MAX_PATH];
        TCHAR szDstObjName[MAX_PATH];
       
        pNextItem = m_pSaveObjNames;
        while (_T('/0') != *pNextItem)
        {
            StrCpy(szObjName,pNextItem);
            PathStripPath(szObjName);

            StrCpy(szDstObjName,pszDstDir);
            PathAddBackslash(szDstObjName);
            StrCat(szDstObjName,szObjName);

            SHAddToRecentDocs(SHARD_PATH,szDstObjName);

            pNextItem += (lstrlen(pNextItem) + 1);
        }
    }

     

    主要是调用SHAddToRecentDocs添加名字空间对象的链接到用户的最近文档列表中。m_pSaveObjNames是粘贴的源文件和文件夹列表,注意其格式为:多个以空字符结束的文件名字符串串接在一起,最后再多加一个空字符,表示整个列表的结束。

    翻译的文档原文见本文第二部分  


    菊子曰 这就是菊子曰啦!
    展开全文
  • 管理文件系统(二)

    2010-06-16 09:28:00
    MSDN 2005 -> Win32 和 COM 开发 -> User Interface -> Windows User Experience -> Windows Shell -> Windows Shell -> Shell Programmer's Guide -> Shell Basics -> Managing the File System 我用不好自己的...
  • <p>I have a user profile screen which shows a user's information along with a profile picture. There I have a link called "Change Photo" which triggers a file input to upload a new image. Then I have ...
  • 编辑任意PDF文件

    2015-05-09 17:44:22
    15. Other minor bug fixes to enhance user experience. What's New in Foxit PDF Editor 2.2 ==============================================================================================================...
  • USER="root" DATABASE="jian" #用户表 TABLE_PERSON="person" #用户教育经历表 TABLE_EDUCATION="person_education_experience" #用户工作经历表 TABLE_WORK="person_work_experience" create_database_table()...
  • dom读取xml文件的问题

    2009-07-20 13:12:18
    <field column="experience" name="experience" /> 该xml文件没有等标签 我想读取出dataSource标签中driver url等值,写了一个类如下: File file = new File("data_config.xml"); ...
  • <p>I just tried to execute a Hello World from VSCode ... The whole experience to setup a Go environment to debug a hello world is really dissapointing. <p>What else can I check? Any hints? </div>
  • Using print stylesheets are a nice way to enhance a user’s experience of a site. Our screen stylesheets don’t necessarily turn out that nicely when printed out, so using a few different CSS rules on...
  • Fine Uploader(http://fineuploader.com/)是一个实现 ajax 上传文件的...This project attempts to achieve a user-friendly file-uploading experience over the web. It's built as a Javascript plugin for dev...
  • I don't have much experience dealing with images like this. <p>I'm assuming you can't just do it like: <pre><code>$_FILES['pic']['tmp_name'] = str_replace(chr(0), '', $_FILES['pic']['tmp_name']); ...
  • Fine Uploader(http://fineuploader.com/)是一个实现 ajax 上传文件的 ...This project attempts to achieve a user-friendly file-uploading experience over the web. It's built as a Javascript plugin for...
  • Fine Uploader(http://fineuploader.com/)是一个实现 ajax 上传文件的 ...This project attempts to achieve a user-friendly file-uploading experience over the web. It's built as a Javascript plugin for...
  • 使用AJAX上传XML文件

    2013-07-02 18:05:36
    <p>What I need to do is have a user upload an XML file and have the script run through the XML file and take out the data that is in certain tags in the file and then push the data into a ...
  • In your experience, what would be the most efficient way to achieve this if using 3rd party services was out of the question (other than those provided by AWS) <p>I'm leaning towards ... after the ....
  • .PHP文件中的AJAX

    2014-08-26 14:22:34
    <p>I want to use AJAX and JS on my .php pages to make them have a better user experience and I know this is possible (as I have done it), but I wanted to know if there are any negative/technical ...
  • </strong> I've never heard of such a thing in my 25+ years of computing and programming experience... <p>I have quatro-checked that the path is the same and checked in all kinds of ways that there ...
  • <p>I'm trying to figure out if it is possible to configure a webserver so that requests for files will be served with a delay.... or any other arbitrary number....<p>I have asked around on superuser....
  • <p>My coding and troubleshooting experience is still very adolescent, thus pardon me if the nature of my question is bothersomely trivial. <p>Before asking this question, I've searched for questions ...
  • <p>The goal here is that I want to wrap php (which i know well) into a cfm file (much less experience). The cfm file will be in an admin section, which automatically checks the user auth, and includes...
  • on-the-list:import user="accounts@juju.com" site="www.juju.com" /> <job> <id>335951659</id> <employer>The Judge Group</employer> <title>Java Developer</...
  • I don't have experience with ActionScript and Flash at all, so I can't use <code>flash cookie</code> and want to try with this. <p><strong>EDIT:</strong> As I understand from Sven's answer maybe I...
  • <p>As you probably can tell it's quiet important to get the correct statuscode since I check for it and return if it's succesfull or not to the user. Anyone able to help me? <p>Thanks in advance! ...

空空如也

空空如也

1 2 3 4 5 6
收藏数 104
精华内容 41
关键字:

userexperience文件