【小结】类 WebInfo 站各方面

【总结】类 WebInfo 站各方面

1、Java 程序开发方面

1.1、使用统一的JavaWeb 模板项目: webtemplate

1、使用 Eclipse mars 及以上版本,将自带 Maven 插件

2、模板项目导入:

  • 先修改模板项目中 pom.xml<artifactId>webtemplate</artifactId> <finalName>webtemplate</finalName> 节点的项目名字
  • 在 Eclipse 中 Import->Existing Maven Projects 选择项目文件夹

提示:首次开发 maven 将下载需要的 jar 包,需要等待几分钟

  • 项目导入后:如果有红叉可以在项目上右键 maven->update project;如果是因为 jsp 验证的报错,这种报错可以忽略,如果看的不爽可以关闭 Eclipse 检测 jsp 的功能

1.2、项目版本控制 git

  • 你的电脑要已安装 git,否则 sudo apt-get install git
  • 在项目文件夹目录使用 git init 项目文件夹的名字,这样就创建了本地 git 库
  • 项目文件中使用 git remote add origin 远程仓库GitURL 可添加远程仓库
  • 添加 .gitignore 文件,忽略管理不需要的文件
/target/
/.settings/
.classpath
.project
  • 在 Eclipse 中打开 git 管理面板:Windows -> Show view -> other -> 搜索 git -> 添加 Git Repository 、Git Staging
  • Git Repository 是 Git 仓库管理,用与添加 Git 库
  • Git Staging 是 常用的阶段管理
  • 在 Eclipse :Windows -> Perspective -> Customize Perspective -> git 中可以添加 git push、pull 的快捷按钮

警告:在做镜像站或者别的新项目时,一定不要直接拷贝整个项目文件夹,因为项目在项目文件夹中有 .git .settings 的隐藏文件,直接拷贝可能会产生 Git 和项目的混乱。建立镜像站的正确做法是:建了新文件夹,只拷贝 src 文件夹pom.xml,然后修改 pom.xml 项目名,类似步1、1,init 新 git 库

1.3、在需求分析方面(待讨论)

  • 首先看设计原型图->分析页面需要展示什么数据->这些数据存放在哪些数据表中->各数据表是如何关联的

  • 一定想好页面上每一部分数据的如何获取在进行实际开发

1.4、在DAO方面(待讨论)

1、一张数据表对应一个 DAO,如果有连表查询,将接口定义在主表 DAO 中

2、在设计 DAO 方法时,参数应该尽可能明确,比如:

Domain queryBySlug(String slug);

而不要设计为:

Domain queryBySlug(Domain domain);

下面的方法参数是一个 Domain 实体,可是 Domain 实体中到底有什么是完全不知道的,别人只有查看 mapper 才只有到底涉及到了什么变量,这时很不方便的,也很难复用。所以 DAO 接口的设计要尽量明确、细致

3、每个 DAO 都对应一个 mapper 文件,在 resource/mapper 中,名字和 DAO 名字一样,文件中<mapper namespace="com.moma.adsense.dao.DomainDao">很重要,这决定了映射关系

4、mapper 中的 查询 sql 应当拒绝 select *

select
    domain_nid,domain_name,domain_country,domain_slug,domain_createtime,domain_adsense_status,domain_includetime,domain_modifytime
from
    ads_domain
where
    domain_nid = #{nid}

这中分行写法会让 sql 更加清晰

!容易出错的地方 1!
DAO 的接口肯定会有多参数的情况,但是在与 mapper 映射时,会有参数对应不上的问题,这时需要 @Param 来解决

List<LogicAdsDmn> queryLogicAdsDmnList(@Param("maxNid") long maxNid, @Param("star") int star, @Param("step") int step);

// 如果写成,是无法对应上参数的
List<LogicAdsDmn> queryLogicAdsDmnList(long maxNid,int star,int step);

mapper:

<select id="queryLogicAdsDmnList" resultType="ads_logic_ads_dmn">
  <![CDATA[
  SELECT
    lad_nid,domain_nid,adsense_nid,lad_number,lad_modifytime
    from
      ads_logic_ads_dmn
    where
      lad_nid > #{maxNid}
    order by lad_nid limit #{star},#{step}
  ]]>
</select>

多参数时,在 mapper 中不用定义传入参数的类型;如果返回是 list 那定义的返回值类型应该是 list 的泛型

!容易出错的地方 2!
在 mapper 中 \$ 与 # :
#{} 会将参数编译,有点像使用 JDBC 的 PreparedStatement
\${} 将直接将参数插入 sql 中,可用于 order by \${}

!容易出错的地方 3!
如果要插入 List,DAO:

int insertDomainList(List<Domain> domainList);

mapper:

<insert id="insertDomainList" parameterType="java.util.List">
    <if test="list != null and list.size() > 0 ">
        insert into ads_domain
        (
        domain_nid,domain_name,domain_slug,domain_country,domain_createtime,
        domain_adsense_status,domain_includetime,domain_modifytime
        )
        VALUES
        <foreach collection="list" item="domain" index="index"
            separator=",">
            (
            #{domain.domainNid},#{domain.domainName},#{domain.domainSlug},
            #{domain.domainCountry},#{domain.domainCreatetime},#{domain.domainAdsenseStatus},
            #{domain.domainIncludetime},#{domain.domainModifytime}
            )
        </foreach>
    </if>
</insert>

如果参数只有一个 list parameterType="java.util.List" 固定写法,collection="list"也是固定写法,而不是 collection="domainList" ,这时 Mybatis 的映射机制,会将你的参数封装成 map,map.put("list",domainList)

<if test="list != null and list.size() > 0 "> 先验证下 list,可减少 sql 出错

separator="," 是迭代时的连接符

!容易出错的地方 4!
如果 DAO 的参数是对象,DAO:

List<Adsense> queryByNewId(AdQuery adQuery);

AdQuery

public class AdQuery {
    private String newId;
    private Integer offset;
    private Integer length;
    
    setter/getter
}

mapper

<select id="queryByNewId"  parameterType="AdQuery" resultType="ads_adsense">
        <![CDATA[
        select 
            adsense_nid,adsense_id,adsense_type,adsense_new_id,adsense_modifytime
        from
            ads_adsense
        where
            adsense_new_id like CONCAT('%',#{newId}) and adsense_type != 'h' limit #{offset}, #{length}
        ]]>
</select>

mapper 直接写对象中的域 #{newId} 而不是写成 #{adQuery.newId}

!容易出错的地方 5!
mapper 中 parameterType 和 resultType 对象命名的规则

resultType="ads_adsense" 映射的 javabean 是 com.moma.adsense.entity.Adsense.java。但是为什么不是 resultType="Adsense"? 这是因为在 Adsense.java 中使用了 @Alias("ads_adsense"),给他起了别名;但是什么的不是resultType="com.moma.adsense.entity.Adsense"? 这是因为在 spring-dao.xmlcom.moma.adsense.entity 包进行了扫描注入。所以 parameterType 或者 resultType 使用的对象不在 com.moma.adsense.entity 中时,就要写类的全称

!容易出错的地方 6!

entity 的域类型应当使用包装类型,例如 intIntegerlongLong,这样可以兼容 null
精确的小数值,我(Yifan)建议使用 BigDecimal,数据库也是 BigDecimal,double float 在某些时候会有精度损失

DAO 的重点:DAO 不要考虑业务,不要写入业务逻辑

1.5、在Service方面(待讨论)

1、Service 的接口参数应该使用对象类型,提高方法的聚合性和弹性,例如
接口定义

/* 获取domain */
Domain getDomain(Domain domain);

接口实现

@Override
public Domain getDomain(Domain domain) {
    // TODO Auto-generated method stub
    Long nid = domain.getDomainNid();
    String name = domain.getDomainName();
    String slug = domain.getDomainSlug();

    if (nid != null) {
        domain = domainDao.queryByNid(nid);
    } else if (slug != null) {
        domain = domainDao.queryBySlug(slug);
    } else if (name != null) {
        domain = domainDao.queryByName(name);
    }
    return domain;
}

这样写就聚合了 domainDao 中的三个方法,使得 DAO 中的 接口和 Service 接口不是一一对应对应,这样在程序发生变动时可以减少程序变化
就算只有 slug 的查询

@Override
public Domain getDomain(Domain domain) {
    // TODO Auto-generated method stub
    String slug = domain.getDomainSlug();
    domain = domainDao.queryBySlug(slug);
    return domain;
}
// 也比下面的好

public Domain getDomain(String slug) {
...
}

因为下面的Service 接口设计与 DAO 接口数据依赖关系太重,DAO 的调整一定会导致 Service 的调整

2、Service 应当不直接调用不直接相关的 DAO,应当调用其直接相关的 Service(保证 Service 与 DAO 之间关系不混乱)
意思就是 DomainServier DAO 只注入 DomainDAO,并不注入 AdsenseDAO 用到 adsense 时注入 AdsenseService,为得也是解除 Service 和 dao 依赖

Service 的重点:Service 的方法应该以业务导向,返回的数据是包装过的

1.6、在Controller方面(待讨论)

1、Controller 主要负责请求转发,不要在其中处理业务

2、Controller 中每个方法返回的 model 中都添加,为jsp 区分 controller 来源使用:
controllerName 当前 Controller【名字】
controllerMethod 当前 Controller 调用的【方法名字】
originalURL 【来源】URL

model.addAttribute("controllerName", "domain");
model.addAttribute("controllerMethod", "index");
model.addAttribute("originalURL", "/");

3、想使用任何的 servlet 对象将它定义成方法的参数就可以使用,这是 SpringMVC 很 cool 的地方

// 想使用 HttpServletRequest
public String index(Model model, HttpServletRequest request) {...}

!容易出错的地方 1!
.../domain/baidu.com 这中 slug,baidu.com 应当称为参数,但是 SpringMVC 会将 . 看成分割符而值接收到 baidu
解决方法:value = "/{id-number:.+}"

@RequestMapping(value = "/{domain-name:.+}", method = RequestMethod.GET)
public String index(@PathVariable("domain-name") String dmnName, Model model, HttpServletRequest request,
            HttpServletResponse response) {...}

!容易出错的地方 2!
404 页面的状态码为:200
设置统一404返回

    @RequestMapping(value = "/404page", method = RequestMethod.GET,HttpServletResponse response)
    public String index404(Model model) {
        response.setStatus(HttpServletResponse.SC_NOT_FOUND);// 设置状态码
        logger.info("/index404");
        return "404";
    }

其他地方这样调用

return "redirect:/404page";

1.6、在 JSP 方面

1、基本结构已经给出,不要破坏
2、没有集成 struts2,可使用 jstl 标签
3、在 header.jsp 已经定义了 jspPath 用于项目路径,不包含最后\\jspPath可在所有引入 header.jsp 的文件了使用,可无视报错提示,不要再重复定义 eg:http://localhost:8080/(project-name)

1.7、在开发的其他方面

1、com.moma.adsense.dto 存放数据传输对象,用于装饰数据库实体对象,给实体对象扩展字段
2、com.moma.dmv.util 存放通用的工具类

1.8、推荐的命名方式

DAO

DES DES DES
查询 queryBy queryById 根据 本身id
查询多条件 queryByIdAndTest 根据 本身idTest主键
查询所有 queryAll 返回 List
插入 insertObject insertVote 返回 Boolean;将插入主键
更新 updateObject updateVote 返回 影响的条数 Long
计数 countBy countByTest 根据 Test主键
删除 deleteBy deleteByUtId

service

DES DES DES
获取对象 getObject getTest
保存对象 saveObject saveLogicTestVote 有则插入,没有则更新
插入对象  addObject addVote 插入对象
删除对象 removeObject removeLogicUserMistake

2、Java 自动更新程序开发方面

2.1 自动化更新核心

核心使用的 RMI 技术

2.2 Service 端的注意事项

Spring 配置,见模板项目的 spring-remote-server.xml

<!-- RMI server -->
<bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
    <property name="serviceName" value="update_data" />
    <!-- 服务端接口实现类 -->
    <property name="service" ref="serverRMIService" />
    <!-- 服务端接口 -->
    <property name="serviceInterface" value="com.moma.adsense.rmi.server.ServerRMIService" />
    <!-- RMI服务注册端口,服务器要开放 -->
    <property name="registryPort" value="9090" />
    <property name="servicePort" value="8899" />
</bean>

接口应该继承 Remote

interface ServerRMIService extends Remote

接口中的方法应当都抛出 java.rmi.RemoteException

int addDomainList(List<Domain> domainList) throws java.rmi.RemoteException;

接口实现类 要在 Spring 自动扫描的包中,否则需要配置

@Service("serverRMIService")
public class ServerRMIServiceImpl implements ServerRMIService {...}

Spring 包扫描

<context:component-scan base-package="com.moma.adsense.rmi.server" />

2.3 Client 端的注意事项

Spring 配置,见模板项目的 spring-remote-client.xml

<!-- RMI client -->
<bean id="clientService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
    <!-- 服务端URL -->
    <property name="serviceUrl" value="rmi://52.10.35.214:9090/update_data" />
    <!-- 服务接口 -->
    <property name="serviceInterface" value="com.moma.adsense.rmi.server.ServerRMIService" />
    <property name="lookupStubOnStartup" value="false" />
    <property name="refreshStubOnConnectFailure" value="true"></property>
</bean>

服务端类,使用 BeanFactory 注入对象

@Component
public class AutoUpdateClient implements BeanFactoryAware{
    ServerRMIService updateService = (ServerRMIService) bea.getBean("clientService");
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        // TODO Auto-generated method stub
        AutoUpdateClient.bea = beanFactory;
    }
}

定时任务,在需要定时执行的方法上配置

@Scheduled(cron = "59 59 23 0/2 * ? ")

定时任务,Spring 配置 spring-task.xml

<task:annotation-driven />
<context:component-scan base-package="com.moma.adsense.rmi.client" />

2.4 RMI部署线上的注意事项

1、无法连接向上服务
aws 的 host 被默认为 aws 的内网 ip,在启动 RMI 服务端后,无法与线下连接,查看 hostname ,修改服务器 hosts 文件:添加 hostname 和对外网 ip。可以解决