Skip to content

4. 作业 1:基本工资单管理

4.1. 简介

为了应用我们之前所学的内容,我们现在提出一项作业,涉及开发一个面向平板电脑的 Android 客户端,旨在模拟某协会员工的工资计算。

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

Image

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

4.2. 数据库

4.2.1. 定义

生成工资单所需的静态数据将存储在一个数据库中,下文我们将该数据库称为 dbpam。该数据库包含以下表:

EMPLOYEES 表:包含关于各类托儿服务提供者的信息

结构

ID
主键
版本
版本号 – 每当行被修改时递增
SS
员工的社会保障号码 – 唯一
姓名
员工姓氏
名字
名字
地址
收件地址
城市
他的/她的城市
邮政编码
他的/她的邮政编码
赔偿编号
[INDEMNITES] 表中 [ID] 字段的外键

其内容可能如下所示:

Image

COTISATIONS 表:包含计算社会保障缴费所需的百分比

结构

ID
主键
版本
版本号 – 每当行被修改时递增
CSGRDS
百分比:一般社会贡献 + 偿还社会债务的贡献
CSGD
百分比:可扣除的一般社会贡献
SECU
百分比:社会保障、遗属、养老
养老金
百分比:补充养老金 + 失业保险

其内容可如下所示:

Image

社会保障缴费率与员工无关。前表仅有一行。

ALLOWANCES 表:包含用于计算应付薪资的各项要素。
ID
主键
VERSION
版本号 – 随着行每次修改而递增
索引
处理索引 – 唯一
每小时费率
每小时待命服务的净价(欧元)
每日维护
每日护理津贴(欧元)
每日餐费
每日护理餐费(欧元)
假期工资
带薪休假津贴。该津贴按基本工资的一定比例计算。

其内容可如下所示:

Image

请注意,不同保育员的津贴可能有所不同。这些津贴通过薪级与特定的保育员相关联。例如,玛丽·朱维纳尔女士的薪级为2(见“员工”表),其时薪为2.1欧元(见“津贴”表)。

4.2.2. 生成

提供了数据库生成脚本 [dbpam_hibernate.sql]:

  

创建 [dbpam_hibernate] 数据库(这是 Web 服务器/jSON 使用的数据库名称),并确保 root 用户(无密码)能够访问该数据库。具体操作如下:

启动 MySQL,然后打开 [PhpMyAdmin]:

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

4.2.3. 数据库的 Java 建模

[EMPLOYEES]、[ALLOWANCES] 和 [CONTRIBUTIONS] 表的元素由以下类进行建模:

[ 员工]


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) {
    ...
  }
   // getters and setters
....
}
  • 第 8–15 行:这些字段对应于 [EMPLOYEES] 表中的列;
  • 第 16 行:[indemniteId] 字段对应 [INDEMNITE_ID] 列,该列是 [EMPLOYEES] 表的外键;
  • 第 17 行:员工的津贴。该字段并非总是被填充:
    • 在请求 URL [/employees] 时不会填充,
    • 但在请求 URL [/salary] 时会填充;

[ 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) {
        ...
    }
 
     // getters and setters
   ....
}
  • 第 8-14 行:这些字段对应于 [INDEMNITES] 表的列;

[ 贡献]


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) {
        ...
    }
    // getters and setters
   ...
}
  • 第 8-13 行:这些字段对应于 [COTISATIONS] 表的列;

4.3. Web 服务器 / JSON 安装

4.3.1. 安装

提供了 Web/JSON 服务器的 Java 二进制文件:

 

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

  • 启动 MySQL 数据库管理系统;
  • 确保数据库 [dbpam_hibernate] 已存在;
  • 打开命令提示符窗口;
  • 导航至 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/**] onto handler of type [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 [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
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 [/salary/{SS}/{ht}/{jt}];
  • 第 17 行:已发现 URL [/employees];

4.3.2. Web 服务/JSON URL

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 行:/employees:用于检索员工列表;
  • 第 4 行:/salary/SS/ht/jt:用于检索员工编号为 [SS]、在 [jt] 天内工作 [ht] 小时的工资单;

以下是相关截图。

我们查询员工信息:

Image

我们备份数据库,重启服务器,然后查询员工表:

Image

我们查询薪资:

Image

我们查询一个不存在人员的薪资:

Image

4.3.3. 来自 Web 服务/JSON 的 JSON 响应

  

Web 服务/JSON URL 返回 [Response<T>] 类型的响应:


package client.android.dao.service;
 
import java.util.List;
 
public class Response<T> {
 
    // ----------------- properties
    // operation status
    private int status;
    // any status messages
    private List<String> messages;
    // the body of the reply
    private T body;
 
    // manufacturers
    public Response() {
 
    }
 
    public Response(int status, List<String> messages, T body) {
        this.status = status;
        this.messages = messages;
        this.body = body;
    }
 
    // getters and setters
...
}
  • URL [/employees] 返回一个 Response<List<Employee>>
  • URL [/salary] 返回 Response<PayStub> 类型;

[PayrollSheet] 类定义如下:


package pam.entities;
 
import java.io.Serializable;
 
public class FeuilleSalaire implements Serializable {
 
    private static final long serialVersionUID = 1L;
    // private fields
    private Employe employe;
    private Cotisation cotisation;
    private ElementsSalaire elementsSalaire;
 
    // manufacturers
    public FeuilleSalaire() {
    }
 
    public FeuilleSalaire(Employe employe, Cotisation cotisation, ElementsSalaire elementsSalaire) {
        ...
    }
 
    // getters and setters
   ...
}
  • 第 9 行:[Employee] 类在第 4.2.3 节中已介绍;
  • 第 10 行:[Contribution] 类在第 4.2.3 节中引入;

[SalaryElements] 类(第 11 行)如下所示:


package pam.entities;
 
import java.io.Serializable;
 
public class ElementsSalaire implements Serializable {
 
    private static final long serialVersionUID = 1L;
    // private fields
    private double salaireBase;
    private double cotisationsSociales;
    private double indemnitesEntretien;
    private double indemnitesRepas;
    private double salaireNet;
 
    // manufacturers
    public ElementsSalaire() {
 
    }
 
    public ElementsSalaire(double salaireBase, double cotisationsSociales, double indemnitesEntretien, double indemnitesRepas, double salaireNet) {
        ...
    }
 
    // getters and setters
    ...
}

4.4. Android 客户端测试

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

  

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

首先,您必须连接到 Web 服务/JSON:

Image

  • 在 [1] 中,输入 Web/JSON 服务的 URL。若使用模拟器,请输入电脑的任意一个 IP 地址(但不要输入 127.0.0.1)。若使用平板设备,请输入 Web/JSON 服务器机器的 Wi-Fi 地址,并禁用服务器的防火墙(如有),因为它可能会阻挡传入的请求;
  • 在 [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] 项目构建的。

  

该项目可运行,且已包含必要的视图。您只需添加代码,使应用程序能够按预期运行。具体步骤如下:

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