Skip to content

4. TP 1:工资单的基本管理

4.1. Introduction

为了应用之前所学的内容,我们现在提出一项任务:编写一个适用于平板电脑的 Android 客户端,用于模拟某协会员工工资单的计算。

该应用将采用客户端/服务器架构:

Image

  • 服务器 [1] 已提供;
  • 需自行构建 Android 客户端 [2]。

4.2. 数据库

4.2.1. 定义

用于生成工资单的静态数据将存储在一个数据库中,我们将其命名为 dbpam。该数据库包含以下表:

EMPLOYES:汇总了关于各保育员的信息

结构

ID
主键
VERSION
版本号——每修改一行即递增
SS
员工社保号——唯一编号
NOM
员工姓名
PRENOM
其名字
ADRESSE
他的地址
VILLE
他的城市
CODEPOSTAL
他的邮政编码
INDEMNITE_ID
表[INDEMNITES]中字段[ID]的外键

其内容可能如下:

Image

COTISATIONS:汇总计算社会保险费所需的百分比

结构

ID
主键
VERSION
版本号——每修改一行即递增
CSGRDS
百分比:普遍社会缴费 + 社会债务偿还缴费
CSGD
百分比:可抵扣的通用社会保险费
SECU
百分比:社会保险、丧偶保险、养老保险
RETRAITE
百分比:补充养老金 + 失业保险

其内容可能如下:

Image

社会保险费率与员工无关。上表仅有一行。

INDEMNITES:汇总了用于计算应付工资的各项要素。
ID
主键
VERSION
版本号——每修改一行即递增
INDICE
处理索引 - 唯一
BASEHEURE
每小时看护服务的净价(欧元)
ENTRETIENJOUR
每小时看护的欧元生活津贴
REPASJOUR
每日看护的餐费补贴(欧元)
INDEMNITESCP
带薪休假补贴。该金额为基本工资的百分比。

其内容可能如下:

Image

需注意,不同保育员的津贴可能有所不同。这些津贴实际上是通过保育员的薪级指数与特定保育员关联的。 例如,薪级指数为2的Marie Jouveinal女士(表EMPLOYES),其时薪为2.1欧元(表INDEMNITES)。

4.2.2. 生成

提供用于生成数据库的脚本 [dbpam_hibernate.sql]:

  

创建数据库 [dbpam_hibernate] (这是 Web 服务器 / jSON 所使用的 BD 的名称),并确保登录名 root(无需密码)能够访问该数据库。您可以按以下步骤操作:

先启动 MySQL,然后启动 [PhpMyAdmin]:

 
  • [1-2]:导入脚本 [dbpam_hibernate.sql] 并运行;

4.2.3. 数据库的Java建模

表 [EMPLOYES]、[INDEMNITES] 和 [COTISATIONS] 的字段由以下类进行建模:

[Employe]


package pam.entities;

import java.io.Serializable;

public class Employe implements Serializable {

  private static final long serialVersionUID = 1L;
  private Long id;
  private int version;
  private String SS;
  private String nom;
  private String prenom;
  private String adresse;
  private String ville;
  private String codePostal;
  private int idIndemnite;
  private Indemnite indemnite;

  public Employe() {
  }

  public Employe(String SS, String nom, String prenom, String adresse, String ville, String codePostal, Indemnite indemnite) {
    ...
  }
   // 获取器和设置器
....
}
  • 第 8-15 行:这些字段对应于表 [EMPLOYES] 的列;
  • 第 16 行:字段 [indemniteId] 对应于表 [EMPLOYES] 的外键 [INDEMNITE_ID];
  • 第17行:员工的津贴。该字段并非总是填写:
    • 当查询 URL [/employes] 时,该字段为空,
    • 但在查询 URL 和 [/salaire] 时会填入;

[Indemnite]


package pam.entities;

import java.io.Serializable;

public class Indemnite implements Serializable {

    private static final long serialVersionUID = 1L;
    private Long id;
    private int version;
    private int indice;
    private double baseHeure;
    private double entretienJour;
    private double repasJour;
    private double indemnitesCp;

    public Indemnite() {
    }

    public Indemnite(int indice, double baseHeure, double entretienJour, double repasJour, double indemnitesCP) {
        ...
    }

     // 获取器和设置器
   ....
}
  • 第8-14行:字段对应于表[INDEMNITES]的列;

[Cotisation]


package pam.entities;

import java.io.Serializable;

public class Cotisation implements Serializable {

    private static final long serialVersionUID = 1L;
    private Long id;
    private int version;
    private double csgrds;
    private double csgd;
    private double secu;
    private double retraite;

    public Cotisation() {
    }

    public Cotisation(double csgrds, double csgd, double secu, double retraite) {
        ...
    }
    // 获取器和设置器
   ...
}
  • 第 8-13 行:字段对应于表 [COTISATIONS] 的列;

4.3. Web 服务器安装 / jSON

4.3.1. 安装

已为您提供 Web 服务器的 Java 二进制文件 / jSON:

 

要启动 Web 服务器 / jSON,请按以下步骤操作:

  • 运行 SGBD MySQL;
  • 确保 BD 和 [dbpam_hibernate] 存在;
  • 打开一个 DOS 窗口;
  • 进入 jar 文件所在的文件夹;
  • 输入命令:
java -jar pam-server-01-all-1.0.jar

这假设二进制文件 [java.exe] 位于您机器上的 PATH 目录中。如果不是,请输入 [java.exe] 的完整路径,例如:

D:\Programs\devjava\java\jdk1.8\bin\java -jar pam-server-01-all-1.0.jar

此时将显示日志:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.1.1.RELEASE)

2014-10-22 16:45:23.347  INFO 1868 --- [           main] pam.boot.BootWeb                         : Starting BootWeb on Gportpers3 with PID 1868 (D:\Temp\14-10-22\pam\server-pam.jar started by ST in D:\Temp\14-10-22\pam)
2014-10-22 16:45:23.414  INFO 1868 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@689ab9e2: startup date [Wed Oct 22 16:45:23 CEST 2014]; root of context hierarchy
...
...
2014-10-22 16:45:31.147  INFO 1868 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
2014-10-22 16:45:31.484  INFO 1868 --- [           main] o.h.h.i.ast.ASTQueryTranslatorFactory    : HHH000397: Using ASTQueryTranslatorFactory
2014-10-22 16:45:33.564  INFO 1868 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-10-22 16:45:33.804  INFO 1868 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/salaire/{SS}/{ht}/{jt}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public pam.restapi.FeuilleSalaireResponse pam.restapi.PamController.getFeuilleSalaire(java.lang.String,double,int)
2014-10-22 16:45:33.805  INFO 1868 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/employes],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public pam.restapi.EmployesResponse pam.restapi.PamController.getEmployes()
2014-10-22 16:45:33.807  INFO 1868 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2014-10-22 16:45:33.807  INFO 1868 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2014-10-22 16:45:33.839  INFO 1868 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] 类型为 [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 的处理程序
2014-10-22 16:45:33.839  INFO 1868 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] 类型为 QZXW2HTML 的处理程序 BW2NsYXNzIG9yZy5zcHJpbmdmcmFtZXdvcmsud2ViLnNlcnZsZXQucmVzb3VyY2UuUmVzb3VyY2VIdHRwUmVxdWVzdEhhbmRsZXJdZQX
2014-10-22 16:45:34.384  INFO 1868 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2014-10-22 16:45:34.535  INFO 1868 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080/http
2014-10-22 16:45:34.538  INFO 1868 --- [           main] pam.boot.BootWeb                         : Started BootWeb in 11.916 seconds (JVM running for 12.725)
2014-10-22 16:45:39.329  INFO 1868 --- [       Thread-2] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@689ab9e2: startup date [Wed Oct 22 16:45:23 CEST 2014]; root of context hierarchy
2014-10-22 16:45:39.331  INFO 1868 --- [       Thread-2] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
2014-10-22 16:45:39.333  INFO 1868 --- [       Thread-2] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
  • 第 16 行:发现 URL [/salaire/{SS}/{ht}/{jt}];
  • 第 17 行:发现 URL [/employes];

4.3.2. Web 服务中的 URL / jSON

Web 服务 / jSON 由 Spring MVC 实现,并暴露两个 URL:


@RequestMapping(value = "/employes", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
public EmployesResponse getEmployes() {
...
@RequestMapping(value = "/salaire/{SS}/{ht}/{jt}", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
public FeuilleSalaireResponse getFeuilleSalaire(@PathVariable("SS") String SS, @PathVariable("ht") double ht, @PathVariable("jt") int jt) {

该 Web 服务支持以下两个 URL 请求:

  • 第 1 行:/employes:用于获取员工列表;
  • 第 4 行:/salaire/SS/ht/jt:用于获取编号为 [SS] 的员工工资单,该员工在 [jt] 天内工作了 [ht] 小时;

以下是展示该功能的屏幕截图。

需要查询的员工:

Image

关闭数据库,重启服务器,然后查询员工:

Image

查询工资:

Image

查询一个不存在人员的工资:

Image

4.3.3. Web服务返回的响应:jSON/jSON

  

Web服务URL / jSON发送的响应类型为[Response<T>]:


package client.android.dao.service;

import java.util.List;

public class Response<T> {

    // ----------------- 属性
    // 操作状态
    private int status;
    // 可能的状态消息
    private List<String> messages;
    // 响应正文
    private T body;

    // 构造函数
    public Response() {

    }

    public Response(int status, List<String> messages, T body) {
        this.status = status;
        this.messages = messages;
        this.body = body;
    }

    // 获取器和设置器
...
}
  • URL [/employes] 返回类型为 Response<List<Employe>>
  • URL 和 [/salaire] 返回类型为 Response<FeuilleSalaire>

[FeuilleSalaire] 类如下:


package pam.entities;

import java.io.Serializable;

public class FeuilleSalaire implements Serializable {

    private static final long serialVersionUID = 1L;
    // 私有字段
    private Employe employe;
    private Cotisation cotisation;
    private ElementsSalaire elementsSalaire;

    // 构造函数
    public FeuilleSalaire() {
    }

    public FeuilleSalaire(Employe employe, Cotisation cotisation, ElementsSalaire elementsSalaire) {
        ...
    }

    // getter 和 setter
   ...
}
  • 第 9 行:类 [Employe] 已在第 4.2.3 节中介绍;
  • 第 10 行:类 [Cotisation] 已在第 4.2.3 节中介绍;

类 [ElementsSalaire](第 11 行)如下:


package pam.entities;

import java.io.Serializable;

public class ElementsSalaire implements Serializable {

    private static final long serialVersionUID = 1L;
    // 私有字段
    private double salaireBase;
    private double cotisationsSociales;
    private double indemnitesEntretien;
    private double indemnitesRepas;
    private double salaireNet;

    // 构造函数
    public ElementsSalaire() {

    }

    public ElementsSalaire(double salaireBase, double cotisationsSociales, double indemnitesEntretien, double indemnitesRepas, double salaireNet) {
        ...
    }

    // 获取器和设置器
    ...
}

4.4. Android 客户端测试

以下是已完成的Android客户端可执行二进制文件:

  

请使用鼠标将上方的二进制文件 [pam-client.apk] 拖放到平板模拟器 [GenyMotion] 上。该文件将被保存并执行。如果尚未启动,请同时启动 Web 服务器 / jSON。 Android客户端的目的是获取Web服务器 / jSON 返回的信息并进行格式化。Android客户端的各个视图如下:

首先需连接至 Web 服务 / jSON:

Image

  • 在 [1] 中,需提供 Web 服务 / jSON 的 URL。 使用模拟器时,请输入 PC 的其中一个地址 IP(但不要输入 127.0.0.1)。 使用平板电脑时,请输入Web服务器主机的Wi-Fi地址 / jSON,并禁用服务器端的防火墙(如有),以免其阻挡传入请求;
  • 在 [2] 上登录;

随后将进入模拟页面:

Image

  • 在 [3] 中,选择一名员工;
  • 在 [4] 页面,输入工时数;
  • 在 [5] 页面,输入天数;
  • 在 [6] 中,执行模拟;

生成的模拟页面如下:

Image

  • 在 [7] 中,显示模拟结果;
  • 在 [8] 中,将其保存;

Image

  • 在 [9] 中,显示模拟列表;
  • 在 [10] 中,删除一个模拟;

Image

  • 转换为 [11],此时已无模拟;
  • 在 [12] 中,返回模拟表单;

Image

  • 在 [13],重新显示表单;
  • 在 [14],返回配置页面;

Image

  • 在 [15],返回初始登录表单。

4.5. 待完成的工作

前面介绍的 Android 客户端框架已提供给您。它是基于第 2 段中描述的 [client-android-skel] 项目构建的。

  

该项目可运行,且已包含必要的视图。只需添加一些代码,应用程序即可完成其应有的功能。操作步骤如下:

  • 运行完整版本以了解需要完成的工作;
  • 运行精简版并研究其代码。该代码遵循了前几页中采用的设计方法;
  • 添加缺失的代码;