文章目录
前言
这段时间我们写了一个制衣厂的项目,我们主要是对工厂生产衣服生产的过程进行记录。工厂和客户签署订单,然后通过客户要求的要求的工序让员工进行分包和生产,每一个包的每个都需要员工去认领,当这个包的主工序全部完成之后,这个包就完成了。并且工厂要从供应商购买原料进行生产。工厂里面的员工也需要根据自己这个月认领的包来计算工资。这个项目的难点是:一开始一直没有搞清楚包和工序,工序组,主工序和辅工序之间的工序。后面明白是一个款式有多个工序组,然后把这个款式分多个款式详情,在款式详情中分具体有多少个包,其中每个包对应一种工序组,并且这个工序组如果为主工序,那么这个包完成了才会把包中的数量加到完成数量中,其余的是辅助工序的包则和对应的主工序包合包。
一、项目环境的搭建
我们写的是一个web项目,用的最基础的,没有使用spring框架,但是我们提前使用了maven来导入jia包,使我们可以更方便的导入一些Java依赖,不需要去到处下载。还使用了Lombok来写实体类,使我们的代码更加的简洁,并且更好的进行修改。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
我们还使用了c3p0连接池来连接数据库,这样避免了使用jdbc的每一次都请求连接和断开连接,每一次都用完直接放回连接池就可以了,节约了时间也使线程的复用性更加好。还有我们也使用了postman和单元测试方法对写好的类进行测试,找到代码中的bug和问题。
二、搭建后台系统,使用了分层的架构
1.dao层
在这一层我用来放实体类的接口和其实现类,这一层用来实现数据库的操作增删改查,和数据库进行连接,是我们最底层实现数据增加和修改的地方。
public interface ClientDao {
default int insertClient(Client client){return 0;};
default int deleteClient(Integer id){return 0;};
default int updateClient(Client client){return 0;};
default List<Client> selectAllClient(){return null;}
default Client selectClientById(Integer id){return null;}
default int getCount(Client client){return 0;}
default List<Client> getPageList(int page,int pageSize){return null;}
}
@Override
public List<Client> selectAllClient() {
String sql = "select * from client";
List<Client> clients = new ArrayList<>();
try {
ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Client client = new Client();
client.setId(rs.getInt("id"));
client.setName(rs.getString("name"));
client.setAddress(rs.getString("address"));
clients.add(client);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return clients;
}
2.dto层
dto是我们为了处理多表的数据进行额外创建的一层,它可以使我们在前端传入大量数据时进行封装,可以使我们把复杂的数据简单化,可以让我们更好的理清数据和业务。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CreateOrder {
private int number;
private int clientId;
private int sign;
//两个列表,放款式和款式详情的内容
private List<Styles> stylesList;
private List<StylesDetails> stylesDetailsList;
}
3.entity层
entity也是我们的底层,用来对应我们数据库的每一张表,对应我们在数据库中字段,使我们可以和数据库进行交互。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Client {
private int id; //客户id
private String name; //姓名
private String address; //地址
private int factoryId;
}
3.service层
service层主要是对业务模块进行设计,是非常重要的一层,我们从前端接收数据之后,要进行的具体业务操作都要使用service来实现,使用它来调定义在dao层的接口又要提供接口给controller层,每一个service层都要有各自的业务实现方法。
public class ClientService {
private ClientDao clientDao;
//初始化
public ClientService(){
clientDao = new ClientDaoImpl();
}
public List<Client> selectAllClient(){
return clientDao.selectAllClient();
}
public Client selectClientById(Integer id){
return clientDao.selectClientById(id);
}
public int insertClient(Client client){
return clientDao.insertClient(client);
}
public int deleteClient(Integer id){
return clientDao.deleteClient(id);
}
public int updateClient(Client client){
return clientDao.updateClient(client);
}
public int getCount(Client staff){ return clientDao.getCount(staff);}
public List<Client> getPageList(int page,int pageSize){
return clientDao.getPageList(page,pageSize);
}
}
4.servlet层(控制层)
servlet层也就是我们的控制层,这一次我们需要对前台的数据进行接收,再进行转化,不进行具体的业务操作,而是调用service的接口进行业务的流程控制。不同的业务需要不同的控制器,这样我们就可以对业务流程进行归纳,更好的配合各种不同的业务需求。
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String uri = req.getRequestURI();
String oper = uri.substring(uri.lastIndexOf("/") + 1);
if (oper.contains("add")) {
add(req, resp);
} else if (oper.contains("del")) {
del(req, resp);
} else if (oper.contains("findall")) {
findall(req, resp);
} else if (oper.contains("update")) {
update(req, resp);
} else if (oper.contains("findbyid")) {
findById(req, resp);
}
}
三、使用token进行身份的验证
我们使用token来保存用户登录的信息。用户通过用户名和密码发送请求,我们在服务器端进行验证,验证成功之后我们发一个token签名给用户,用户保存token,并且每次访问都携带token,服务端验证token并且刷新过期时间,成功之后进行返回数据。这么做是因为https协议是无状态的,我们每次连结之后都会迅速断开,这样我们就不知道是谁进行的操作,不能进行身份的验证,不能保证安全性。而token就能达到我们身份验证的要求。
四、进行文件和图片的上传
对于图片和我们,我们在项目中要单独上传,因为他们的大小都比较大,这样可以节省我们上传的时间。在上传时我们需要把图片的名字用uuid进行修饰,这样就不会出现上传名字重复的问题了,然后把生成的新名字存储在数据库中,便于我们之后的调用和访问。
// 创建工厂
DiskFileItemFactory dfif = new DiskFileItemFactory();
// 使用工厂创建解析器对象
ServletFileUpload fileUpload = new ServletFileUpload(dfif);
try {
// 使用解析器对象解析request,得到FileItem列表
List<FileItem> list = fileUpload.parseRequest(request);
// 遍历所有表单项
for (FileItem fileItem : list) {
// 如果当前表单项为普通表单项
if (fileItem.isFormField()) {
// 获取当前表单项的字段名称
String fieldName = fileItem.getFieldName();
// 如果当前表单项的字段名为username
if (fieldName.equals("username")) {
// 打印当前表单项的内容,即用户在username表单项中输入的内容
response.getWriter().print("用户名:" + fileItem.getString() + "<br/>");
}
if (fieldName.equals("id")) {
// 打印当前表单项的内容,即用户在username表单项中输入的内容
id = Integer.parseInt(fileItem.getString());
}
} else {//如果当前表单项不是普通表单项,说明就是文件字段
String name = fileItem.getName();//获取上传文件的名称
// 如果上传的文件名称为空,即没有指定上传文件
if (name == null || name.isEmpty()) {
continue;
}
// 如果客户端使用的是IE6,那么需要从完整路径中获取文件名称
int lastIndex = name.lastIndexOf("\\");
if(lastIndex != -1) {
name = name.substring(lastIndex + 1);
}
String uuid =UUID.randomUUID().toString();;//生成uuid;//生成uuid
String filename = uuid + "_" + name;//新的文件名称为uuid + 下划线 + 原始名称
//上传之后图片的地址
String realPath = "upload/"+ filename;
Orders orders = new Orders();
orders.setId(id);
orders.setFile(realPath);
ordersService.updateOrders(orders);
// 获取真实路径,对应${项目目录}/upload,当然,这个目录必须存在
String savepath = this.getServletContext().getRealPath("/upload");
// 通过uploads目录和文件名称来创建File对象
File file = new File(savepath, filename);
// 把上传文件保存到指定位置
fileItem.write(file);
// 打印上传文件的名称
response.getWriter().print("上传文件名:" + name + "<br/>");
// 打印上传文件的大小
response.getWriter().print("上传文件大小:" + fileItem.getSize() + "<br/>");
// 打印上传文件的类型
response.getWriter().print("上传文件类型:" + fileItem.getContentType() + "<br/>");
五、对输出结果进行封装
我们向前端输出的内容需要进行统一的封装,这样前端收到我们的内容才知道应该如何去处理和解析。而且一些会出现的异常我们也需要自己去处理和解决,这样我们的代码才能有更好的健壮性。
public class Result<T> {
private int code;
private String message;
private T data;
public Result(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static <T> Result<T> ok(T data) {
return new Result<>(200, "成功", data);
}
public static <T> Result<T> phoneBad(T data) {
return new Result(501, "IP或pwd错误,请输入最少11位数的账号,密码最少为6位!", data);
}
public static <T> Result<T> pwdBad(T data) {
return new Result(502, "IP或pwd错误", data);
}
public static <T> Result<T> phoneNull(T data) {
return new Result<>(503, "phone或pwd不能为空", data);
}
public static <T> Result<T> regFail(T data) {
return new Result(504, "注册失败", data);
}
public static <T> Result<T> intFail(T data) {
return new Result(505, "IP已存在", data);
}
public static <T> Result<T> updateFail(T data) {
return new Result(506, "更新失败", data);
}
public static <T> Result<T> pwdNull(T data) {
return new Result(507, "请先设置支付密码", data);
}
public static <T> Result<T> queryFail(T data) {
return new Result(507, "查询失败", data);
}
public static <T> Result<T> mdFail(T data) {
return new Result(508, "余额不足", data);
}
public static <T> Result<T> Fail(T data) {
return new Result(509, "操作失败", data);
}
public static <T> Result<T> parameterFail(T data) {
return new Result(510, "参数错误", data);
}
public static <T> Result<T> formatError(T data) {
return new Result(511, "格式错误", data);
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
总结
在这个项目中要分清楚每一层需要做的事情,是我的是不能别人来做,不是我的时坚决不做(比如一开始把所有的事都丢给servlet,这样是不行的,servlet是控制层,进行接收前台数据然后进行分发和调度,其中具体的执行代码是service的任务)。写项目时数据库一开始就要确定好,要不然后面改起来就非常麻烦,并且主键需要自增,最好每个字段都要进行注释,命名要使用下划线进行分割。数据库中不应该出现一些过于复杂的代码比如一些嵌套子查询之类的,但是一些关于统计的运算可以在数据库中进行,提高代码的效率。而且数据库中需要加入事务来保证代码的完整性和安全性。