精华内容
下载资源
问答
  • JAVA自动更新程序

    2021-02-28 19:16:46
    最近由于一个工程需要做应用程序启动时,自动更新的项目在GOOGLE上找了半天也没见到什么比较好的办法自己动手写了一个通过版本号检查网络上是不是存在新的更新文件,并自动通过HTTP下载文件的程序希望对正在找此类程序...

    最近由于一个工程需要做应用程序启动时,自动更新的项目

    在GOOGLE上找了半天也没见到什么比较好的办法

    自己动手写了一个通过版本号检查网络上是不是存在新的更新文件,并自动通过HTTP下载文件的程序

    希望对正在找此类程序的朋友有帮助

    本地文件需要一个ver.txt  此文件内容为本地软件版本号

    网络上我直接在一个页面上打印出网络存在的版本号

    例如,这个例子里,我在 http://XXX.XXX.XXX/AutoUpdate/ver  这里直接打印出版本号

    源文件:http://211.136.109.100/beiouwolf/AutoUpdate.rar

    import javax.swing.*;

    import java.awt.*;

    import java.net.*;

    import java.io.*;

    public class CheckUpdate extends JFrame {

    JFrame c = this;

    public CheckUpdate() {

    //设置窗体属性

    setAttb();

    JLabel title = new JLabel("正在检查网络上的更新资源");

    this.add(title, BorderLayout.NORTH);

    JTextArea msg = new JTextArea();

    this.add(msg, BorderLayout.CENTER);

    JLabel process = new JLabel();

    this.add(process, BorderLayout.SOUTH);

    //启动更新线程

    new Check(msg, process).start();

    }

    private class Check extends Thread {

    //标识,是否存在新的更新文件

    private boolean isUpdated = false;

    //保存最新的版本

    String netVersion;

    //本地版本文件名

    String LocalVerFileName = "ver.txt";

    //显示信息

    private JTextArea msg;

    private JLabel process;

    public Check(JTextArea msg, JLabel process) {

    this.msg = msg;

    this.process = process;

    }

    public void run() {

    //更新文件版本标识URL

    String versionUrl = "http://XXX.XXX.XXX/AutoUpdate/ver";

    /**//*

    这里是通过HTTP访问一个页面,以取得网络上的版本号

    比如这里就是在这个页面直接打印出 6.19.1.1

    然后把这个版本号比对本地的版本号,如果版本号不同的话,就从网络上下载新的程序并覆盖现有程序

    */

    URL url = null;

    InputStream is = null;

    InputStreamReader isr = null;

    BufferedReader netVer = null;

    //读取网络上的版本号

    try {

    url = new URL(versionUrl);

    is = url.openStream();

    isr = new InputStreamReader(is);

    netVer = new BufferedReader(isr);

    String netVerStr = netVer.readLine();

    String localVerStr = getNowVer();

    if (netVerStr.equals(localVerStr)) {

    msg.append("当前文件是最新版本\n");

    isUpdated = false;

    } else {

    msg.append("存在更新文件,现在开始更新\n");

    isUpdated = true;

    netVersion = netVerStr;

    }

    } catch (MalformedURLException ex) {

    } catch (IOException ex) {

    } finally {

    //释放资源

    try {

    netVer.close();

    isr.close();

    is.close();

    } catch (IOException ex1) {

    }

    }

    //如果版本不同,下载网络上的文件,更新本地文件

    if (isUpdated) {

    //本地需要被更新的文件

    File oldFile = new File("client.exe");

    //缓存网络上下载的文件

    File newFile = new File("temp.exe");

    //网络上的文件位置

    String updateUrl =

    "http://XXX.XXX.XXX/downloads/simpkle.exe";

    HttpURLConnection httpUrl = null;

    BufferedInputStream bis = null;

    FileOutputStream fos = null;

    try {

    //打开URL通道

    url = new URL(updateUrl);

    httpUrl = (HttpURLConnection) url.openConnection();

    httpUrl.connect();

    byte[] buffer = new byte[1024];

    int size = 0;

    is = httpUrl.getInputStream();

    bis = new BufferedInputStream(is);

    fos = new FileOutputStream(newFile);

    msg.append("正在从网络上下载新的更新文件\n");

    //保存文件

    try {

    int flag = 0;

    int flag2 = 0;

    while ((size = bis.read(buffer)) != -1) {

    //读取并刷新临时保存文件

    fos.write(buffer, 0, size);

    fos.flush();

    //模拟一个简单的进度条

    if (flag2 == 99) {

    flag2 = 0;

    process.setText(process.getText() + ".");

    }

    flag2++;

    flag++;

    if (flag > 99 * 50) {

    flag = 0;

    process.setText("");

    }

    }

    } catch (Exception ex4) {

    System.out.println(ex4.getMessage());

    }

    msg.append("\n文件下载完成\n");

    //把下载的临时文件替换原有文件

    CopyFile(oldFile,newFile);

    //把本地版本文件更新为网络同步

    UpdateLocalVerFile();

    } catch (MalformedURLException ex2) {

    } catch (IOException ex) {

    msg.append("文件读取错误\n");

    } finally {

    try {

    fos.close();

    bis.close();

    is.close();

    httpUrl.disconnect();

    } catch (IOException ex3) {

    }

    }

    }

    //启动应用程序

    try {

    msg.append("启动应用程序");

    Thread.sleep(500);

    Process p = Runtime.getRuntime().exec("client.exe");

    } catch (IOException ex5) {

    } catch (InterruptedException ex) {

    }

    //退出更新程序

    System.exit(0);

    }

    //复制文件

    private void CopyFile(File oldFile, File newFile) {

    FileInputStream in = null;

    FileOutputStream out = null;

    try {

    if(oldFile.exists()){

    oldFile.delete();

    }

    in = new FileInputStream(newFile);

    out = new FileOutputStream(oldFile);

    byte[] buffer = new byte[1024 * 5];

    int size;

    while ((size = in.read(buffer)) != -1) {

    out.write(buffer, 0, size);

    out.flush();

    }

    } catch (FileNotFoundException ex) {

    } catch (IOException ex) {

    } finally {

    try {

    out.close();

    in.close();

    } catch (IOException ex1) {

    }

    }

    }

    private void UpdateLocalVerFile() {

    //把本地版本文件更新为网络同步

    FileWriter verOS = null;

    BufferedWriter bw = null;

    try {

    verOS = new FileWriter(LocalVerFileName);

    bw = new BufferedWriter(verOS);

    bw.write(netVersion);

    bw.flush();

    } catch (IOException ex) {

    } finally {

    try {

    bw.close();

    verOS.close();

    } catch (IOException ex1) {

    }

    }

    }

    private String getNowVer() {

    //本地版本文件

    File verFile = new File(LocalVerFileName);

    FileReader is = null;

    BufferedReader br = null;

    //读取本地版本

    try {

    is = new FileReader(verFile);

    br = new BufferedReader(is);

    String ver = br.readLine();

    return ver;

    } catch (FileNotFoundException ex) {

    msg.append("本地版本文件未找到\n");

    } catch (IOException ex) {

    msg.append("本地版本文件读取错误\n");

    } finally {

    //释放资源

    try {

    br.close();

    is.close();

    } catch (IOException ex1) {

    }

    }

    return "";

    }

    }

    private void setAttb() {

    //窗体设置

    this.setTitle("Auto Update");

    this.setSize(200, 150);

    this.setLayout(new BorderLayout());

    this.setDefaultCloseOperation(EXIT_ON_CLOSE);

    // 窗体居中

    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

    Dimension frameSize = this.getSize();

    if (frameSize.height > screenSize.height) {

    frameSize.height = screenSize.height;

    }

    if (frameSize.width > screenSize.width) {

    frameSize.width = screenSize.width;

    }

    this.setLocation((screenSize.width - frameSize.width) / 2,

    (screenSize.height - frameSize.height) / 2);

    }

    public static void main(String[] args) {

    CheckUpdate checkupdate = new CheckUpdate();

    checkupdate.setVisible(true);

    }

    }

    展开全文
  • java web远程升级

    2021-03-09 03:16:48
    java web远程升级[2021-02-02 02:31:06]简介:php去除nbsp的方法:首先创建一个PHP代码示例文件;然后通过“preg_replace("/(\s|\&nbsp\;| |\xc2\xa0)/", " ", strip_tags($val));”方法去除所有nbsp即可。推荐...

    java web远程升级

    [2021-02-02 02:31:06]  简介:

    744442.html

    php去除nbsp的方法:首先创建一个PHP代码示例文件;然后通过“preg_replace("/(\s|\&nbsp\;| |\xc2\xa0)/", " ", strip_tags($val));”方法去除所有nbsp即可。推荐:《PHP视频教

    d119fab843d2a9cb7136d01e261dc96c.gif

    web开发选择会php也会选择java的,php即写即用的,php写东西快,php的表面思路更清晰,php占用内存少,同时java组件多,java线程池,连接池,异步化方便,java是真正意义上的逻辑清晰。web开

    d119fab843d2a9cb7136d01e261dc96c.gif

    服务器

    tomcat介绍:

    web服务器只能完成静态资源的请求;

    web容器能够进行动态资源的请求;

    tomcat就是一个最简单的web容器,是apache的j

    d119fab843d2a9cb7136d01e261dc96c.gif

    java web是一个技术的总和,把Web看成一个容器而已,主要使用JavaEE技术来实现,在加上各种中间件,可以通过JavaWeb制作一个软件,一个ERP,一个网页,甚至是一个网络游戏都可以。Java We

    d119fab843d2a9cb7136d01e261dc96c.gif

    java常用的web服务器有:1、IIS是允许在公共Intranet或Internet上发布信息的Web服务器;2、Kangle Web服务器是一款跨平台、功能强大、安全稳定、易操作的高性能Web服务器和反向

    d119fab843d2a9cb7136d01e261dc96c.gif

    服务器

    Tomcat是基于java语言的web服务器软件,本文主要介绍如何在centos7.5上配置java环境并安装tomcat

    1.安装Java环境:

    访问orcal官网下

    d119fab843d2a9cb7136d01e261dc96c.gif

    中国网科技7月24日讯 今日,工信部发布今年第三批侵害用户权益行为的APP通报。通报称,截至目前,尚有58款APP未完成整改。对于出现问题的APP,应在7月30日前完成整改落

    d119fab843d2a9cb7136d01e261dc96c.gif

    01 Gradle

    Java世界中主要有三大构建工具:Ant、Maven和Gradle。经过几年的发展,Ant几乎销声匿迹,还剩Maven和Gradle两种,maven是我目前工作中用的版本管理工具。maven

    d119fab843d2a9cb7136d01e261dc96c.gif

    服务器

    Linux查看History记录加时间戳小技巧

    熟悉bash的都一定知道使用history可以输出你曾经输入过的历史命令,例如

    [root@servyou_web

    d119fab843d2a9cb7136d01e261dc96c.gif

    服务器

    1.安装JDK

    1.1 检查当前虚拟机环境有没有JDK rpm -qa|grep java

    1.2 卸载 rpm -e --nodeps xxxxxx(自己的openjdk)

    1.3 安装JD

    d119fab843d2a9cb7136d01e261dc96c.gif

    建站服务器

    继jenkins笔记(1)进行实战java代码发布,此利用github私有仓库拉去,jenkins利用maven编译源码后讲包发布致web服务器。

    一、git私

    d119fab843d2a9cb7136d01e261dc96c.gif

    作为人生的第一辆车,很多用户会把预算定在10万元区间,购买一辆紧凑SUV。 如果选择合资品牌,可选余地较小,配置让人无话可说,内饰更“无精致感”可言,进入车内落差感就会

    d119fab843d2a9cb7136d01e261dc96c.gif

    建站服务器

    什么是LNMP? LNMP(别名LEMP)是指由Linux, Nginx, mysql/MariaDB, PHP/Perl/Python组合成的动态Web应用程序和服务器,它是一组We

    d119fab843d2a9cb7136d01e261dc96c.gif

    云计算

    作者:kelvinjin2009 来源:程序师

    原文链接:

    http://www.techug.com/post/java-and-docker-memory-limits.html

    Ja

    d119fab843d2a9cb7136d01e261dc96c.gif

    建站服务器

    远程连接到Hyper-V HOST 为了日常运维管理操作,使用远程PowerShell工作。Windows 10上安装了RSAT(远程管理工具 )。然后安装

    d119fab843d2a9cb7136d01e261dc96c.gif

    展开全文
  • 前言目前selenium版本已经升级到3.0了,网上的大部分教程是基于2.0写的,所以在学习前先要弄清楚版本号,这点非常重要。本系列依然以selenium2为基础,目前selenium3本人没做过研究就不多说了。Selenium是一个用于...

    前言

    目前selenium版本已经升级到3.0了,网上的大部分教程是基于2.0写的,所以在学习前先要弄清楚版本号,这点非常重要。本系列依然以selenium2为基础,目前selenium3本人没做过研究就不多说了。

    Selenium是一个用于Web应用程序测试的工具。

    Selenium 测试直接运行在浏览器中,就像真正的用户在操作一样。

    支持的浏览器包括IE,Mozilla和Firefox等。

    什么是Selenium?

    Selenium 主要用于web应用程序的自动化测试,但并不局限于此,它还支持所有基于Web的管理任务自动化。

    Selenium 的特点如下:

    1.开源,免费

    2.多浏览器支持:FireFox、Chrome、IE、Opera、Edge

    3.多平台只吃:Linux、Windows、MAC

    4.多语言支持:Java、Python、Ruby、C#、Java script、C++

    5.对Web 页面有良好的支持

    6.简单(API简单)、灵活(用开发语言驱动)

    7.支持分布式测试用例执行

    这个工具的主要功能包括:测试与浏览器的兼容性,测试你的应用程序看是否能够很好得工作在不同浏览器和操作系统之上。测试系统功能,创建衰退测试检验软件功能和用户需求。

    接下来的内容以Selenium 2 为主

    环境 java6~7 + Firefox35

    需要有的工具: 开发工具eclipse +selenium2 jar包+firefox35浏览器(火狐)

    java环境不多说了,网上一大堆,不难,很好搭建出来的

    先说下小编的系统:windows10 (64位)

    首先要安装Eclipse

    Eclipse:https://www.eclipse.org/downloads/

    开发Java者必然会用Eclipse工具,eclipse是做Java开发的不二之选,当然了,小编我就是用的java语言来写的脚本咯,以后可能也会转python,有时间就看看哈~

    好了,废话不多说,下面让我们来看下这个Eclipse这个工具是干啥的,怎么工作的吧、

    下载好后 我们进行目录解压会得到一下eclipse目录,目录结构如下:

    4af1a20bf2cd316e042feaaf6974ab08.png

    双击eclipse.exe启动程序。 如果是首次启动需要你置顶Java的项目目录。或者你希望Java项目创建于哪个目录下。

    82e01d92cc018f29bb34340bb25dcadb.png

    小编就选择默认了哈~在E盘上了

    点击“ok”

    下面就是要编写你的第一个java程序啦!是不是很想写一个看看哇,嘿嘿(小编很想明天再告诉大家,但我怕众人砸我,刚弄好了,啥都没说呢,就没了,那叫什么来着 还没开始就结束了,哈哈,老王就是这么的单纯哈)

    编写第一个Java程序

    Java听着都不陌生是吧~好多人都是听过 从XX入门到放弃,selenium来讲咱们会些基础就够了,以后用到哪里再说,不管你是哪种语言你的入门一个程序都是 “Hello Word”咱们也不例外哈~

    以打开的Eclipse为例,选择菜单栏 File--NEW--Java Project 弹出如下窗口

    a7fb2e9ffd91e71e71a18d9dd5eade47.png

    再“Project name”选项中输入项目名称,这里输入“mypor”,如果想看看下一步创建有什么选项点击“Next”,想省事直接点击“Finish”创建项目完成。

    继续在项目上右键New--Package

    70a76e0c679e0e5839a2f4986f15d66d.png

    包(Package)的概念可以理解为程序的集合

    a92a9f8d8640815295a8c44bcc67ac8c.png

    再“Name”选项中输入报名为“com.mypro.jase”,为什么包的名字要取的倒过来的域名,这个。。。我也不清楚,反正大家都这么命名,随之~~~

    继续在包上右键New--Class

    74d5f40fa30f3e94637e79f3bb56942f.png

    这个才是我们最终要编写的java程序的文件,java一切皆对象,对象一定是属于某个类的,所以需要创建class文件

    e0a2cdabae06254eab4f380e91f71b69.png

    在“Name”选项中输入类名,我这里输入的类名为“Hello”,Java对类的命名一般要求首字母大写,然后点击"Finish"按钮创建 Hello.java文件

    在Hello.java编写第一个Java程序

    2bcd9a0b9925653b3923c7aa6c66ff37.png

    输入完成后,点击左上角按钮进行保存

    71941cb21448d8c09f39869f0d57d42f.png

    保存后,点击运行按钮0c5aea61e25d3674a26de31a85b97556.png将会在控制台看到“hello world”的输入,完成了你java中的第一个程序1093f5d74d4437a4f02f74c6d3f7ce60.png

    好的,今天先到这里,下次继续分享~现在才刚刚开始哟,如哪里写的不明确,直接评论~~~

    欢迎关注老王公众号

    展开全文
  • Java 单体服务开发指南

    千次阅读 多人点赞 2021-07-19 15:27:30
    Java 开发手册》)1、错误码2、异常处理3、日志规约四、单元测试(参考《阿里 Java 开发手册》)五、安全规约(参考《阿里 Java 开发手册》)六、MySQL数据库(参考《阿里 Java 开发手册》)1、建表规约2、SQL语句3...


    在这里插入图片描述

    一、代码组织模式

    在这里插入图片描述

    1、多仓库

    • 优点

      • 每一个服务都有一个独立的仓库,职责单一
      • 代码量和复杂性受控,服务由不同的团队独立维护、边界清晰。
      • 单个服务也易于自治开发测试部署和扩展,不需要集中管理集中协调。
    • 缺点

      • 项目代码不容易规范。每个团队容易各自为政,随意引入依赖,code review 无法集中开展,代码风格各不相同。
      • 项目集成和部署会比较麻烦。虽然每个项目服务易于集成和部署,但是整个应用集成和部署的时候由于仓库分散就需要集中的管理和协调。
      • 开发人员缺乏对整个项目的整体认知。开发人员一般只关心自己的服务代码,看不到项目整体,造成缺乏对项目整体架构和业务目标整体性的理解。
      • 项目间冗余代码多。每个服务一个服务一个仓库,势必造成团队在开发的时候走捷径,不断地重复造轮子而不是去优先重用其他团队开发的代码。

    2、单体仓库

    • 知名企业案例

      • Google
      • Facebook
      • Twitter
      • B 站
    • 优点

      • 易于规范代码。所有的代码在一个仓库当中就可以标准化依赖管理,集中开展 code review,规范化代码的风格。
      • 于集成和部署。所有的代码在一个仓库里面,配合自动化构建工具,可以做到一键构建、一键部署,一般不需要特别的集中管理和协调。
      • 易于理解项目整体。开发人员可以把整个项目加载到本地的 IDE 当中,进行 code review,也可以直接在本地部署调试,方便开发人员把握整体的技术架构和业务目标。
      • 易于重用。所有的代码都在一个仓库中,开发人员开发的时候比较容易发现和重用已有的代码,而不是去重复造轮子,开发人员(通过 IDE 的支持)容易对现有代码进行重构,可以抽取出一些公共的功能进一步提升代码的质量和复用度。
    • 缺点

      • 随着公司业务团队规模的变大,单一的代码库会变得越来越庞大复杂性也呈极度的上升
      • 一般都有独立的代码管理和集成团队进行支持
      • 有配套的自动化构建工具来支持

    二、编程规约(参考《阿里 Java 开发手册》)

    1、命名风格

    2、常量定义

    3、代码格式

    4、OOP 规约

    5、日期时间

    6、集合处理

    7、并发处理

    8、控制语句

    9、注释规约

    10、前后端规约

    11、其它

    三、异常日志(参考《阿里 Java 开发手册》)

    1、错误码

    2、异常处理

    3、日志规约

    四、单元测试(参考《阿里 Java 开发手册》)

    五、安全规约(参考《阿里 Java 开发手册》)

    六、MySQL数据库(参考《阿里 Java 开发手册》)

    1、建表规约

    2、SQL语句

    3、ORM 映射

    4、索引规约

    七、工程结构(参考《阿里 Java 开发手册》)

    1、应用分层

    在这里插入图片描述

    2、二方库依赖

    3、服务器

    八、设计规约(参考《阿里 Java 开发手册》)

    九、服务开发框架

    1、接口参数校验

    • 技术方案
      • Hibernate Validator
      • 自定义注解
      • 全局异常处理

    Hibernate Validator 是 SpringBoot 内置的校验框架,只要集成了 SpringBoot 就自动集成了它,我们可以通过在对象上面使用它提供的注解来完成参数校验。

    常用注解:

    • @Null:被注释的属性必须为null;
    • @NotNull:被注释的属性不能为null;
    • @AssertTrue:被注释的属性必须为true;
    • @AssertFalse:被注释的属性必须为false;
    • @Min:被注释的属性必须大于等于其value值;
    • @Max:被注释的属性必须小于等于其value值;
    • @Size:被注释的属性必须在其min和max值之间;
    • @Pattern:被注释的属性必须符合其regexp所定义的正则表达式;
    • @NotBlank:被注释的字符串不能为空字符串;
    • @NotEmpty:被注释的属性不能为空;
    • @Email:被注释的属性必须符合邮箱格式。

    有时候框架提供的校验注解并不能满足我们的需要,此时我们就需要自定义校验注解。比如还是上面的添加品牌,此时有个参数 showStatus,我们希望它只能是 0 或者 1,不能是其他数字,此时可以使用自定义注解来实现该功能。

    • 控制器接口参数校验
    @GetMapping(path = "/list")
    ListAccountResponse listAccounts(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestParam int offset, @RequestParam @Min(0) int limit);
    
    // GetOrCreate is for internal use by other APIs to match a user based on their phonenumber or email.
    @PostMapping(path= "/get_or_create")
    GenericAccountResponse getOrCreateAccount(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid GetOrCreateRequest request);
    
    @GetMapping(path = "/get")
    GenericAccountResponse getAccount(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestParam @NotBlank String userId);
    
    @PutMapping(path = "/update")
    GenericAccountResponse updateAccount(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid AccountDto newAccount);
    
    @GetMapping(path = "/get_account_by_phonenumber")
    GenericAccountResponse getAccountByPhonenumber(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestParam @PhoneNumber String phoneNumber);
    
    • DTO 参数校验
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public class AccountDto {
        @NotBlank
        private String id;
        private String name;
        @Email(message = "Invalid email")
        private String email;
        private boolean confirmedAndActive;
        @NotNull
        private Instant memberSince;
        private boolean support;
        @PhoneNumber
        private String phoneNumber;
        @NotEmpty
        private String photoUrl;
    }
    
    • 自定义注解
    @Documented
    @Constraint(validatedBy = PhoneNumberValidator.class)
    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PhoneNumber {
        String message() default "Invalid phone number";
        Class[] groups() default {};
        Class[] payload() default {};
    }
    
    public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
    
        @Override
        public boolean isValid(String phoneField, ConstraintValidatorContext context) {
            if (phoneField == null) return true; // can be null
            return phoneField != null && phoneField.matches("[0-9]+")
                    && (phoneField.length() > 8) && (phoneField.length() < 14);
        }
    }
    

    2、统一异常处理

    使用全局异常处理来处理校验逻辑的思路很简单,首先我们需要通过 @ControllerAdvice 注解定义一个全局异常的处理类,然后自定义一个校验异常,当我们在 Controller 中校验失败时,直接抛出该异常,这样就可以达到校验失败返回错误信息的目的了

    使用到的注解:

    • @ControllerAdvice:类似于@Component 注解,可以指定一个组件,这个组件主要用于增强@Controller注解修饰的类的功能,比如说进行全局异常处理。
    • @ExceptionHandler:用来修饰全局异常处理的方法,可以指定异常的类型。

    在这里插入图片描述

    • RestControllerAdvice
    @RestControllerAdvice
    public class GlobalExceptionTranslator {
    
        static final ILogger logger = SLoggerFactory.getLogger(GlobalExceptionTranslator.class);
    
        @ExceptionHandler(MissingServletRequestParameterException.class)
        public BaseResponse handleError(MissingServletRequestParameterException e) {
            logger.warn("Missing Request Parameter", e);
            String message = String.format("Missing Request Parameter: %s", e.getParameterName());
            return BaseResponse
                    .builder()
                    .code(ResultCode.PARAM_MISS)
                    .message(message)
                    .build();
        }
    
        @ExceptionHandler(MethodArgumentTypeMismatchException.class)
        public BaseResponse handleError(MethodArgumentTypeMismatchException e) {
            logger.warn("Method Argument Type Mismatch", e);
            String message = String.format("Method Argument Type Mismatch: %s", e.getName());
            return BaseResponse
                    .builder()
                    .code(ResultCode.PARAM_TYPE_ERROR)
                    .message(message)
                    .build();
        }
    
        @ExceptionHandler(MethodArgumentNotValidException.class)
        public BaseResponse handleError(MethodArgumentNotValidException e) {
            logger.warn("Method Argument Not Valid", e);
            BindingResult result = e.getBindingResult();
            FieldError error = result.getFieldError();
            String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
            return BaseResponse
                    .builder()
                    .code(ResultCode.PARAM_VALID_ERROR)
                    .message(message)
                    .build();
        }
    
        @ExceptionHandler(BindException.class)
        public BaseResponse handleError(BindException e) {
            logger.warn("Bind Exception", e);
            FieldError error = e.getFieldError();
            String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
            return BaseResponse
                    .builder()
                    .code(ResultCode.PARAM_BIND_ERROR)
                    .message(message)
                    .build();
        }
    
        @ExceptionHandler(ConstraintViolationException.class)
        public BaseResponse handleError(ConstraintViolationException e) {
            logger.warn("Constraint Violation", e);
            Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
            ConstraintViolation<?> violation = violations.iterator().next();
            String path = ((PathImpl) violation.getPropertyPath()).getLeafNode().getName();
            String message = String.format("%s:%s", path, violation.getMessage());
            return BaseResponse
                    .builder()
                    .code(ResultCode.PARAM_VALID_ERROR)
                    .message(message)
                    .build();
        }
    
        @ExceptionHandler(NoHandlerFoundException.class)
        public BaseResponse handleError(NoHandlerFoundException e) {
            logger.error("404 Not Found", e);
            return BaseResponse
                    .builder()
                    .code(ResultCode.NOT_FOUND)
                    .message(e.getMessage())
                    .build();
        }
    
        @ExceptionHandler(HttpMessageNotReadableException.class)
        public BaseResponse handleError(HttpMessageNotReadableException e) {
            logger.error("Message Not Readable", e);
            return BaseResponse
                    .builder()
                    .code(ResultCode.MSG_NOT_READABLE)
                    .message(e.getMessage())
                    .build();
        }
    
        @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
        public BaseResponse handleError(HttpRequestMethodNotSupportedException e) {
            logger.error("Request Method Not Supported", e);
            return BaseResponse
                    .builder()
                    .code(ResultCode.METHOD_NOT_SUPPORTED)
                    .message(e.getMessage())
                    .build();
        }
    
        @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
        public BaseResponse handleError(HttpMediaTypeNotSupportedException e) {
            logger.error("Media Type Not Supported", e);
            return BaseResponse
                    .builder()
                    .code(ResultCode.MEDIA_TYPE_NOT_SUPPORTED)
                    .message(e.getMessage())
                    .build();
        }
    
        @ExceptionHandler(ServiceException.class)
        public BaseResponse handleError(ServiceException e) {
            logger.error("Service Exception", e);
            return BaseResponse
                    .builder()
                    .code(e.getResultCode())
                    .message(e.getMessage())
                    .build();
        }
    
        @ExceptionHandler(PermissionDeniedException.class)
        public BaseResponse handleError(PermissionDeniedException e) {
            logger.error("Permission Denied", e);
            return BaseResponse
                    .builder()
                    .code(e.getResultCode())
                    .message(e.getMessage())
                    .build();
        }
    
        @ExceptionHandler(Throwable.class)
        public BaseResponse handleError(Throwable e) {
            logger.error("Internal Server Error", e);
            return BaseResponse
                    .builder()
                    .code(ResultCode.INTERNAL_SERVER_ERROR)
                    .message(e.getMessage())
                    .build();
        }
    }
    
    
    • 统一异常捕获
      @ExceptionHandler(ServiceException.class)
        public BaseResponse handleError(ServiceException e) {
            logger.error("Service Exception", e);
            return BaseResponse
                    .builder()
                    .code(e.getResultCode())
                    .message(e.getMessage())
                    .build();
        }
    
       @ExceptionHandler(Throwable.class)
        public BaseResponse handleError(Throwable e) {
            logger.error("Internal Server Error", e);
            return BaseResponse
                    .builder()
                    .code(ResultCode.INTERNAL_SERVER_ERROR)
                    .message(e.getMessage())
                    .build();
        }
    
    • BaseResponse
      • 继承
      • 封装消息+捎带
      • 正常响应
      • 异常响应
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class BaseResponse {
        private String message;
        @Builder.Default
        private ResultCode code = ResultCode.SUCCESS;
    
        public boolean isSuccess() {
            return code == ResultCode.SUCCESS;
        }
    }
    
    • Web MVC ErrorController
    
    @Controller
    @SuppressWarnings(value = "Duplicates")
    public class GlobalErrorController implements ErrorController {
    
        static final ILogger logger = SLoggerFactory.getLogger(GlobalErrorController.class);
    
        @Autowired
        ErrorPageFactory errorPageFactory;
        @Autowired
        SentryClient sentryClient;
        @Autowired
        StaffjoyProps staffjoyProps;
        @Autowired
        EnvConfig envConfig;
    
        @Override
        public String getErrorPath() {
            return "/error";
        }
    
        @RequestMapping("/error")
        public String handleError(HttpServletRequest request, Model model) {
    
            Object statusCode = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
            Object exception = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
    
            ErrorPage errorPage = null;
            if (statusCode != null && (Integer)statusCode == HttpStatus.NOT_FOUND.value()) {
                errorPage = errorPageFactory.buildNotFoundPage();
            } else {
                errorPage = errorPageFactory.buildInternalServerErrorPage();
            }
    
            if (exception != null) {
                if (envConfig.isDebug()) {  // no sentry aop in debug mode
                    logger.error("Global error handling", exception);
                } else {
                    sentryClient.sendException((Exception)exception);
                    UUID uuid = sentryClient.getContext().getLastEventId();
                    errorPage.setSentryErrorId(uuid.toString());
                    errorPage.setSentryPublicDsn(staffjoyProps.getSentryDsn());
                    logger.warn("Reported error to sentry", "id", uuid.toString(), "error", exception);
                }
            }
    
            model.addAttribute(Constant.ATTRIBUTE_NAME_PAGE, errorPage);
    
            return "error";
        }
    
    }
    

    3、DTO 和 DMO 互转

    DTO:

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public class AccountDto {
        @NotBlank
        private String id;
        private String name;
        @Email(message = "Invalid email")
        private String email;
        private boolean confirmedAndActive;
        @NotNull
        private Instant memberSince;
        private boolean support;
        @PhoneNumber
        private String phoneNumber;
        @NotEmpty
        private String photoUrl;
    }
    
    

    DMO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    @Entity
    
    public class Account {
        @Id
        @GenericGenerator(name = "system-uuid", strategy = "uuid")
        @GeneratedValue(generator = "system-uuid")
        private String id;
        private String name;
        private String email;
        private boolean confirmedAndActive;
        private Instant memberSince;
        private boolean support;
        private String phoneNumber;
        private String photoUrl;
    }
    

    DTO 和 DMO 互转:

      public AccountDto update(AccountDto newAccountDto) {
            Account newAccount = this.convertToModel(newAccountDto);
    
            Account existingAccount = accountRepo.findAccountById(newAccount.getId());
            if (existingAccount == null) {
                throw new ServiceException(ResultCode.NOT_FOUND, String.format("User with id %s not found", newAccount.getId()));
            }
            entityManager.detach(existingAccount);
    
            if (!serviceHelper.isAlmostSameInstant(newAccount.getMemberSince(), existingAccount.getMemberSince())) {
                throw new ServiceException(ResultCode.REQ_REJECT, "You cannot modify the member_since date");
            }
    
            if (StringUtils.hasText(newAccount.getEmail()) && !newAccount.getEmail().equals(existingAccount.getEmail())) {
                Account foundAccount = accountRepo.findAccountByEmail(newAccount.getEmail());
                if (foundAccount != null) {
                    throw new ServiceException(ResultCode.REQ_REJECT, "A user with that email already exists. Try a password reset");
                }
            }
    
            if (StringUtils.hasText(newAccount.getPhoneNumber()) && !newAccount.getPhoneNumber().equals(existingAccount.getPhoneNumber())) {
                Account foundAccount = accountRepo.findAccountByPhoneNumber(newAccount.getPhoneNumber());
                if (foundAccount != null) {
                    throw new ServiceException(ResultCode.REQ_REJECT, "A user with that phonenumber already exists. Try a password reset");
                }
            }
    
            if (AuthConstant.AUTHORIZATION_AUTHENTICATED_USER.equals(AuthContext.getAuthz())) {
                if (!existingAccount.isConfirmedAndActive() && newAccount.isConfirmedAndActive()) {
                    throw new ServiceException(ResultCode.REQ_REJECT, "You cannot activate this account");
                }
                if (existingAccount.isSupport() != newAccount.isSupport()) {
                    throw new ServiceException(ResultCode.REQ_REJECT, "You cannot change the support parameter");
                }
                if (!existingAccount.getPhotoUrl().equals(newAccount.getPhotoUrl())) {
                    throw new ServiceException(ResultCode.REQ_REJECT, "You cannot change the photo through this endpoint (see docs)");
                }
                // User can request email change - not do it :-)
                if (!existingAccount.getEmail().equals(newAccount.getEmail())) {
                    this.requestEmailChange(newAccount.getId(), newAccount.getEmail());
                    // revert
                    newAccount.setEmail(existingAccount.getEmail());
                }
            }
    
            newAccount.setPhotoUrl(Helper.generateGravatarUrl(newAccount.getEmail()));
    
            try {
                accountRepo.save(newAccount);
            } catch (Exception ex) {
                String errMsg = "Could not update the user account";
                serviceHelper.handleException(logger, ex, errMsg);
                throw new ServiceException(errMsg, ex);
            }
    
            serviceHelper.syncUserAsync(newAccount.getId());
    
            LogEntry auditLog = LogEntry.builder()
                    .authorization(AuthContext.getAuthz())
                    .currentUserId(AuthContext.getUserId())
                    .targetType("account")
                    .targetId(newAccount.getId())
                    .originalContents(existingAccount.toString())
                    .updatedContents(newAccount.toString())
                    .build();
    
            logger.info("updated account", auditLog);
    
            // If account is being activated, or if phone number is changed by current user - send text
            if (newAccount.isConfirmedAndActive() &&
                    StringUtils.hasText(newAccount.getPhoneNumber()) &&
                    !newAccount.getPhoneNumber().equals(existingAccount.getPhoneNumber())) {
                serviceHelper.sendSmsGreeting(newAccount.getId());
            }
    
            this.trackEventWithAuthCheck("account_updated");
    
            AccountDto accountDto = this.convertToDto(newAccount);
            return accountDto;
        }
    
        public void updatePassword(String userId, String password) {
            String pwHash = passwordEncoder.encode(password);
    
            int affected = accountSecretRepo.updatePasswordHashById(pwHash, userId);
            if (affected != 1) {
                throw new ServiceException(ResultCode.NOT_FOUND, "user with specified id not found");
            }
    
            LogEntry auditLog = LogEntry.builder()
                    .authorization(AuthContext.getAuthz())
                    .currentUserId(AuthContext.getUserId())
                    .targetType("account")
                    .targetId(userId)
                    .build();
    
            logger.info("updated password", auditLog);
    
            this.trackEventWithAuthCheck("password_updated");
        }
    
      private AccountDto convertToDto(Account account) {
            return modelMapper.map(account, AccountDto.class);
        }
    
        private Account convertToModel(AccountDto accountDto) {
            return modelMapper.map(accountDto, Account.class);
        }
    
    

    4、强类型接口设计

    在这里插入图片描述

    • 强弱兼备
      • API
        • BaseResponse
          • 状态码检查
        • svcResponse
      • controller
    • Spring Feign
    • 序列化及反序列化

    Account Client:

    @FeignClient(name = AccountConstant.SERVICE_NAME, path = "/v1/account", url = "${staffjoy.account-service-endpoint}")
    // TODO Client side validation can be enabled as needed
    // @Validated
    public interface AccountClient {
    
        @PostMapping(path = "/create")
        GenericAccountResponse createAccount(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid CreateAccountRequest request);
    
        @PostMapping(path = "/track_event")
        BaseResponse trackEvent(@RequestBody @Valid TrackEventRequest request);
    
        @PostMapping(path = "/sync_user")
        BaseResponse syncUser(@RequestBody @Valid SyncUserRequest request);
    
        @GetMapping(path = "/list")
        ListAccountResponse listAccounts(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestParam int offset, @RequestParam @Min(0) int limit);
    
        // GetOrCreate is for internal use by other APIs to match a user based on their phonenumber or email.
        @PostMapping(path= "/get_or_create")
        GenericAccountResponse getOrCreateAccount(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid GetOrCreateRequest request);
    
        @GetMapping(path = "/get")
        GenericAccountResponse getAccount(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestParam @NotBlank String userId);
    
        @PutMapping(path = "/update")
        GenericAccountResponse updateAccount(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid AccountDto newAccount);
    
        @GetMapping(path = "/get_account_by_phonenumber")
        GenericAccountResponse getAccountByPhonenumber(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestParam @PhoneNumber String phoneNumber);
    
        @PutMapping(path = "/update_password")
        BaseResponse updatePassword(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid UpdatePasswordRequest request);
    
        @PostMapping(path = "/verify_password")
        GenericAccountResponse verifyPassword(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid VerifyPasswordRequest request);
    
        // RequestPasswordReset sends an email to a user with a password reset link
        @PostMapping(path = "/request_password_reset")
        BaseResponse requestPasswordReset(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid PasswordResetRequest request);
    
        @PostMapping(path = "/request_email_change")
        BaseResponse requestEmailChange(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid EmailChangeRequest request);
    
        // ChangeEmail sets an account to active and updates its email. It is
        // used after a user clicks a confirmation link in their email.
        @PostMapping(path = "/change_email")
        BaseResponse changeEmail(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid EmailConfirmation request);
    }
    
    

    继承关系:
    在这里插入图片描述

    客户端调用范例:

      GenericAccountResponse genericAccountResponse = null;
            try {
                genericAccountResponse = this.accountClient.getAccount(AuthConstant.AUTHORIZATION_WHOAMI_SERVICE, userId);
            } catch (Exception ex) {
                String errMsg = "unable to get account";
                handleErrorAndThrowException(ex, errMsg);
            }
            if (!genericAccountResponse.isSuccess()) {
                handleErrorAndThrowException(genericAccountResponse.getMessage());
            }
            AccountDto account = genericAccountResponse.getAccount();
    

    封装消息+捎带:
    在这里插入图片描述

    5、分环境配置

    • 类型
      • dev
      • test
      • uat
      • prod

    环境定义:

    public class EnvConstant {
        public static final String ENV_DEV = "dev";
        public static final String ENV_TEST = "test";
        public static final String ENV_UAT = "uat"; // similar to staging
        public static final String ENV_PROD = "prod";
    }
    

    环境配置:

    // environment related configuration
    @Data
    @Builder
    public class EnvConfig {
    
        private String name;
        private boolean debug;
        private String externalApex;
        private String internalApex;
        private String scheme;
    
        @Getter(AccessLevel.NONE)
        @Setter(AccessLevel.NONE)
        private static Map<String, EnvConfig> map;
    
        static {
            map = new HashMap<String, EnvConfig>();
            EnvConfig envConfig = EnvConfig.builder().name(EnvConstant.ENV_DEV)
                    .debug(true)
                    .externalApex("staffjoy-v2.local")
                    .internalApex(EnvConstant.ENV_DEV)
                    .scheme("http")
                    .build();
            map.put(EnvConstant.ENV_DEV, envConfig);
    
            envConfig = EnvConfig.builder().name(EnvConstant.ENV_TEST)
                    .debug(true)
                    .externalApex("staffjoy-v2.local")
                    .internalApex(EnvConstant.ENV_DEV)
                    .scheme("http")
                    .build();
            map.put(EnvConstant.ENV_TEST, envConfig);
    
            // for aliyun k8s demo, enable debug and use http and staffjoy-uat.local
            // in real world, disable debug and use http and staffjoy-uat.xyz in UAT environment
            envConfig = EnvConfig.builder().name(EnvConstant.ENV_UAT)
                    .debug(true)
                    .externalApex("dusan-uat.local")
                    .internalApex(EnvConstant.ENV_UAT)
                    .scheme("http")
                    .build();
            map.put(EnvConstant.ENV_UAT, envConfig);
    
    //        envConfig = EnvConfig.builder().name(EnvConstant.ENV_UAT)
    //                .debug(false)
    //                .externalApex("staffjoy-uat.xyz")
    //                .internalApex(EnvConstant.ENV_UAT)
    //                .scheme("https")
    //                .build();
    //        map.put(EnvConstant.ENV_UAT, envConfig);
    
            envConfig = EnvConfig.builder().name(EnvConstant.ENV_PROD)
                    .debug(false)
                    .externalApex("dunsan.com")
                    .internalApex(EnvConstant.ENV_PROD)
                    .scheme("https")
                    .build();
            map.put(EnvConstant.ENV_PROD, envConfig);
        }
    
        public static EnvConfig getEnvConfg(String env) {
            EnvConfig envConfig = map.get(env);
            if (envConfig == null) {
                envConfig = map.get(EnvConstant.ENV_DEV);
            }
            return envConfig;
        }
    }
    
    

    开发测试环境禁用 Sentry 异常日志:

    @Aspect
    @Slf4j
    public class SentryClientAspect {
    
        @Autowired
        EnvConfig envConfig;
    
        @Around("execution(* io.sentry.SentryClient.send*(..))")
        public void around(ProceedingJoinPoint joinPoint) throws Throwable {
            // no sentry logging in debug mode
            if (envConfig.isDebug()) {
                log.debug("no sentry logging in debug mode");
                return;
            }
            joinPoint.proceed();
        }
    }
    

    Sentry 是统一的异常管理平台,支持异常事件的收集、展示、告警等功能。
    在这里插入图片描述

    6、异步调用处理

    ThreadPoolTaskExecutor:
    在这里插入图片描述

    AsyncExecutor 配置:

    Configuration
    @EnableAsync
    @Import(value = {StaffjoyRestConfig.class})
    @SuppressWarnings(value = "Duplicates")
    public class AppConfig {
        public static final String ASYNC_EXECUTOR_NAME = "asyncExecutor";
    
        @Bean(name=ASYNC_EXECUTOR_NAME)
        public Executor asyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setTaskDecorator(new ContextCopyingDecorator());
            executor.setCorePoolSize(3);
            executor.setMaxPoolSize(5);
            executor.setQueueCapacity(100);
            executor.setWaitForTasksToCompleteOnShutdown(true);
            executor.setThreadNamePrefix("AsyncThread-");
            executor.initialize();
            return executor;
        }
    
    }
    

    Async 标注:

       @Async(AppConfig.ASYNC_EXECUTOR_NAME)
        public void trackEventAsync(String userId, String eventName) {
            if (envConfig.isDebug()) {
                logger.debug("intercom disabled in dev & test environment");
                return;
            }
    
            Event event = new Event()
                    .setUserID(userId)
                    .setEventName("v2_" + eventName)
                    .setCreatedAt(Instant.now().toEpochMilli());
    
            try {
                Event.create(event);
            } catch (Exception ex) {
                String errMsg = "fail to create event on Intercom";
                handleException(logger, ex, errMsg);
                throw new ServiceException(errMsg, ex);
            }
    
            logger.debug("updated intercom");
        }
    
    

    线程上下文拷贝:

    // https://stackoverflow.com/questions/23732089/how-to-enable-request-scope-in-async-task-executor
    public class ContextCopyingDecorator implements TaskDecorator {
        @Override
        public Runnable decorate(Runnable runnable) {
            RequestAttributes context = RequestContextHolder.currentRequestAttributes();
            return () -> {
                try {
                    RequestContextHolder.setRequestAttributes(context);
                    runnable.run();
                } finally {
                    RequestContextHolder.resetRequestAttributes();
                }
            };
        }
    }
    
    @Configuration
    @EnableAsync
    @Import(value = {StaffjoyRestConfig.class})
    @SuppressWarnings(value = "Duplicates")
    public class AppConfig {
    
        public static final String ASYNC_EXECUTOR_NAME = "asyncExecutor";
    
        @Bean(name=ASYNC_EXECUTOR_NAME)
        public Executor asyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            // for passing in request scope context
            executor.setTaskDecorator(new ContextCopyingDecorator());
            executor.setCorePoolSize(3);
            executor.setMaxPoolSize(5);
            executor.setQueueCapacity(100);
            executor.setWaitForTasksToCompleteOnShutdown(true);
            executor.setThreadNamePrefix("AsyncThread-");
            executor.initialize();
            return executor;
        }
    
    }
    

    7、Swagger 配置

             <!-- Swagger -->
                <dependency>
                    <groupId>io.springfox</groupId>
                    <artifactId>springfox-swagger2</artifactId>
                    <version>2.9.2</version>
                </dependency>
                <dependency>
                    <groupId>io.springfox</groupId>
                    <artifactId>springfox-swagger-ui</artifactId>
                    <version>2.9.2</version>
                </dependency>
    
    @Configuration
    @EnableSwagger2
    public class SwaggerConfig {
        @Bean
        public Docket api() {
            return new Docket(DocumentationType.SWAGGER_2)
                    .select()
                    //为当前包下controller生成API文档
                    .apis(RequestHandlerSelectors.basePackage("com.7d.PmsBrand.controller"))
                    .paths(PathSelectors.any())
                    .build()
                    .apiInfo(apiEndPointsInfo())
                    .useDefaultResponseMessages(false);
        }
    
        private ApiInfo apiEndPointsInfo() {
            return new ApiInfoBuilder().title("PmsBrand REST API")
                    .description("7d Account REST API")
                    .contact(new Contact("7d", "https://zuozewei.blog.csdn.net", "zuozewei@hotmail.com"))
                    .license("The MIT License")
                    .licenseUrl("https://opensource.org/licenses/MIT")
                    .version("V2")
                    .build();
        }
    }
    
    

    给 Controller 添加 Swagger 注解:

    
    /**
     * 品牌管理Controller
     */
    
    @Api(tags = "PmsBrandController", description = "商品品牌管理")
    @Controller
    @RequestMapping("/brand")
    public class PmsBrandController {
        @Autowired
        private PmsBrandService brandService;
    
        private static final Logger LOGGER = LoggerFactory.getLogger(PmsBrandController.class);
    
        @ApiOperation("获取所有品牌列表")
        @RequestMapping(value = "listAll", method = RequestMethod.GET)
        @ResponseBody
        public CommonResult<List<PmsBrand>> getBrandList() {
            return CommonResult.success(brandService.listAllBrand());
        }
    
        @ApiOperation("添加品牌")
        @RequestMapping(value = "/create", method = RequestMethod.POST)
        @ResponseBody
        public CommonResult createBrand(@RequestBody PmsBrand pmsBrand) {
            CommonResult commonResult;
            int count = brandService.createBrand(pmsBrand);
            if (count == 1) {
                commonResult = CommonResult.success(pmsBrand);
                LOGGER.debug("createBrand success:{}", pmsBrand);
            } else {
                commonResult = CommonResult.failed("操作失败");
                LOGGER.debug("createBrand failed:{}", pmsBrand);
            }
            return commonResult;
        }
    
        @ApiOperation("更新指定id品牌信息")
        @RequestMapping(value = "/update/{id}", method = RequestMethod.POST)
        @ResponseBody
        public CommonResult updateBrand(@PathVariable("id") Long id, @RequestBody PmsBrand pmsBrandDto, BindingResult result) {
            CommonResult commonResult;
            int count = brandService.updateBrand(id, pmsBrandDto);
            if (count == 1) {
                commonResult = CommonResult.success(pmsBrandDto);
                LOGGER.debug("updateBrand success:{}", pmsBrandDto);
            } else {
                commonResult = CommonResult.failed("操作失败");
                LOGGER.debug("updateBrand failed:{}", pmsBrandDto);
            }
            return commonResult;
        }
    
        @ApiOperation("删除指定id的品牌")
        @RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
        @ResponseBody
        public CommonResult deleteBrand(@PathVariable("id") Long id) {
            int count = brandService.deleteBrand(id);
            if (count == 1) {
                LOGGER.debug("deleteBrand success :id={}", id);
                return CommonResult.success(null);
            } else {
                LOGGER.debug("deleteBrand failed :id={}", id);
                return CommonResult.failed("操作失败");
            }
        }
    
        @ApiOperation("分页查询品牌列表")
        @RequestMapping(value = "/list", method = RequestMethod.GET)
        @ResponseBody
        public CommonResult<CommonPage<PmsBrand>> listBrand(@RequestParam(value = "pageNum", defaultValue = "1")
                                                            @ApiParam("页码") Integer pageNum,
                                                            @RequestParam(value = "pageSize", defaultValue = "3")
                                                            @ApiParam("每页数量") Integer pageSize) {
            List<PmsBrand> brandList = brandService.listBrand(pageNum, pageSize);
            return CommonResult.success(CommonPage.restPage(brandList));
        }
    
        @ApiOperation("获取指定id的品牌详情")
        @RequestMapping(value = "/{id}", method = RequestMethod.GET)
        @ResponseBody
        public CommonResult<PmsBrand> brand(@PathVariable("id") Long id) {
            return CommonResult.success(brandService.getBrand(id));
        }
    }
    
    
    

    修改 MyBatis Generator 注释的生成规则:

    CommentGenerator为MyBatis Generator的自定义注释生成器,修改addFieldComment方法使其生成Swagger的@ApiModelProperty注解来取代原来的方法注释,添加addJavaFileComment方法,使其能在import中导入@ApiModelProperty,否则需要手动导入该类,在需要生成大量实体类时,是一件非常麻烦的事。

    /**
     * 自定义注释生成器
     */
    
    public class CommentGenerator extends DefaultCommentGenerator {
        private boolean addRemarkComments = false;
        private static final String EXAMPLE_SUFFIX="Example";
        private static final String API_MODEL_PROPERTY_FULL_CLASS_NAME="io.swagger.annotations.ApiModelProperty";
    
        /**
         * 设置用户配置的参数
         */
        @Override
        public void addConfigurationProperties(Properties properties) {
            super.addConfigurationProperties(properties);
            this.addRemarkComments = StringUtility.isTrue(properties.getProperty("addRemarkComments"));
        }
    
        /**
         * 给字段添加注释
         */
        @Override
        public void addFieldComment(Field field, IntrospectedTable introspectedTable,
                                    IntrospectedColumn introspectedColumn) {
            String remarks = introspectedColumn.getRemarks();
            //根据参数和备注信息判断是否添加备注信息
            if(addRemarkComments&&StringUtility.stringHasValue(remarks)){
    //            addFieldJavaDoc(field, remarks);
                //数据库中特殊字符需要转义
                if(remarks.contains("\"")){
                    remarks = remarks.replace("\"","'");
                }
                //给model的字段添加swagger注解
                field.addJavaDocLine("@ApiModelProperty(value = \""+remarks+"\")");
            }
        }
    
        /**
         * 给model的字段添加注释
         */
        private void addFieldJavaDoc(Field field, String remarks) {
            //文档注释开始
            field.addJavaDocLine("/**");
            //获取数据库字段的备注信息
            String[] remarkLines = remarks.split(System.getProperty("line.separator"));
            for(String remarkLine:remarkLines){
                field.addJavaDocLine(" * "+remarkLine);
            }
            addJavadocTag(field, false);
            field.addJavaDocLine(" */");
        }
    
        @Override
        public void addJavaFileComment(CompilationUnit compilationUnit) {
            super.addJavaFileComment(compilationUnit);
            //只在model中添加swagger注解类的导入
            if(!compilationUnit.isJavaInterface()&&!compilationUnit.getType().getFullyQualifiedName().contains(EXAMPLE_SUFFIX)){
                compilationUnit.addImportedType(new FullyQualifiedJavaType(API_MODEL_PROPERTY_FULL_CLASS_NAME));
            }
        }
    }
    
    
    

    运行代码生成器重新生成 mbg 包中的代码:

    运行com.7d.mall.tiny.mbg.Generator 的 main方法,重新生成 mbg 中的代码,可以看到 PmsBrand 类中已经自动根据数据库注释添加了@ApiModelProperty注解

    在这里插入图片描述

    8、前后端分离跨域

    CORS全称Cross-Origin Resource Sharing,意为跨域资源共享。当一个资源去访问另一个不同域名或者同域名不同端口的资源时,就会发出跨域请求。如果此时另一个资源不允许其进行跨域资源访问,那么访问的那个资源就会遇到跨域问题。

    覆盖默认的CorsFilter

    添加GlobalCorsConfig配置文件来允许跨域访问。

    设置 SpringSecurity 允许 OPTIONS 请求访问

    在SecurityConfig类的configure(HttpSecurity httpSecurity) 方法中添加如下代码。

    .antMatchers(HttpMethod.OPTIONS)//跨域请求会先进行一次options请求
    .permitAll()
    

    9、统一访问日志记录

    AOP 通过在 controller 层建一个切面来实现接口访问的统一日志记录。

    添加日志信息封装类 WebLog

    用于封装需要记录的日志信息,包括操作的描述、时间、消耗时间、url、请求参数和返回结果等信息。

    /**
     * Controller层的日志封装类
     */
    
    public class WebLog {
        /**
         * 操作描述
         */
        private String description;
    
        /**
         * 操作用户
         */
        private String username;
    
        /**
         * 操作时间
         */
        private Long startTime;
    
        /**
         * 消耗时间
         */
        private Integer spendTime;
    
        /**
         * 根路径
         */
        private String basePath;
    
        /**
         * URI
         */
        private String uri;
    
        /**
         * URL
         */
        private String url;
    
        /**
         * 请求类型
         */
        private String method;
    
        /**
         * IP地址
         */
        private String ip;
    
        /**
         * 请求参数
         */
        private Object parameter;
    
        /**
         * 请求返回的结果
         */
        private Object result;
    
        //省略了getter,setter方法
    }
    
    
    

    添加切面类 WebLogAspect:

    定义了一个日志切面,在环绕通知中获取日志需要的信息,并应用到controller层中所有的public方法中去。

    /**
     * 统一日志处理切面
     */
    
    @Aspect
    @Component
    @Order(1)
    public class WebLogAspect {
        private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);
    
        @Pointcut("execution(public * com.dunsan.mall.tiny.controller.*.*(..))")
        public void webLog() {
        }
    
        @Before("webLog()")
        public void doBefore(JoinPoint joinPoint) throws Throwable {
        }
    
        @AfterReturning(value = "webLog()", returning = "ret")
        public void doAfterReturning(Object ret) throws Throwable {
        }
    
        @Around("webLog()")
        public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
            long startTime = System.currentTimeMillis();
            //获取当前请求对象
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            //记录请求信息
            WebLog webLog = new WebLog();
            Object result = joinPoint.proceed();
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            if (method.isAnnotationPresent(ApiOperation.class)) {
                ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
                webLog.setDescription(apiOperation.value());
            }
            long endTime = System.currentTimeMillis();
            String urlStr = request.getRequestURL().toString();
            webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
            webLog.setIp(request.getRemoteUser());
            webLog.setMethod(request.getMethod());
            webLog.setParameter(getParameter(method, joinPoint.getArgs()));
            webLog.setResult(result);
            webLog.setSpendTime((int) (endTime - startTime));
            webLog.setStartTime(startTime);
            webLog.setUri(request.getRequestURI());
            webLog.setUrl(request.getRequestURL().toString());
            LOGGER.info("{}", JSONUtil.parse(webLog));
            return result;
        }
    
        /**
         * 根据方法和传入的参数获取请求参数
         */
        private Object getParameter(Method method, Object[] args) {
            List<Object> argList = new ArrayList<>();
            Parameter[] parameters = method.getParameters();
            for (int i = 0; i < parameters.length; i++) {
                //将RequestBody注解修饰的参数作为请求参数
                RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
                if (requestBody != null) {
                    argList.add(args[i]);
                }
                //将RequestParam注解修饰的参数作为请求参数
                RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
                if (requestParam != null) {
                    Map<String, Object> map = new HashMap<>();
                    String key = parameters[i].getName();
                    if (!StringUtils.isEmpty(requestParam.value())) {
                        key = requestParam.value();
                    }
                    map.put(key, args[i]);
                    argList.add(map);
                }
            }
            if (argList.size() == 0) {
                return null;
            } else if (argList.size() == 1) {
                return argList.get(0);
            } else {
                return argList;
            }
        }
    }
    
    
    

    10、打包方式

    • jar
    • docker

    服务配置文件处理方式:
    对于各个项目分环境部署,最麻烦的就是配置文件的问题,不同的环境需要加载不同的配置,好在 Spring Boot 框架加载配置是非常方便的,我们可以针对不同的环境分别配置不同的配置文件,这里有两个地方要注意一下:

    • 构建镜像的时候,尽量实现一个镜像支持所有环境(即所有配置都打到一个镜像里面去),在容器启动时指定加载哪个环境配置即可,例如:在部署容器时指定 args: ["–spring.profiles.active=prod"] 参数启动。
    • 尽量不要每个环境打出来一个镜像版本,传统方式在构建的时候指定 -D prod 配置 Profile 来指定加载哪个配置,来生成不同的产物 jar,容器化部署后不需要这样,那样后期控制各镜像版本发布会比较麻烦。

    镜像可以分为基础镜像和应用镜像:
    基础镜像要求体积尽量小,方便拉取,同时安装一些必要的软件,方便后期进入容器内排查问题,我们需要准备好服务运行的底层系统镜像,比如 Centos、Ubuntu 等常见 Linux 操作系统,然后基于该系统镜像,构建服务运行需要的环境镜像,比如一些常见组合:Centos + Jdk、Centos + Jdk + Tomcat、Centos + nginx 等,由于不同的服务运行依赖的环境版本不一定一致,所以还需要制作不同版本的环境镜像,例如如下基础镜像版本。

    • Centos6.5 + Jdk1.8: registry.docker.com/baseimg/centos-jdk:6.5_1.8
    • Centos7.5 + Jdk1.8: registry.docker.com/baseimg/centos-jdk:7.5_1.8
    • Centos7.5 + Jdk1.7: registry.docker.com/baseimg/centos-jdk:7.5_1.7
    • Centos7 + Tomcat8 + Jdk1.8: registry.docker.com/baseimg/centos-tomcat-jdk:7.5_8.5_1.8
    • Centos7 + Nginx: registry.docker.com/baseimg/centos-tomcat-jdk:7.5_1.10.2

    这样,就可以标识该基础镜像的系统版本及软件版本,方便后边选择对应的基础镜像来构建应用镜像

    有了上边的基础镜像后,就很容易构建出对应的应用镜像了,例如一个简单的应用镜像 Dockerfile 如下:

    FROM registry.docker.com/baseimg/centos-jdk:7.5_1.8
    
    COPY app-name.jar /opt/project/app.jar
    EXPOSE 8080
    ENTRYPOINT ["/java", "-jar", "/opt/project/app.jar"]
    

    当然,这里我建议使用另一种方式来启动服务,将启动命令放在统一 shell 启动脚本执行,例如如下Dockerfile 示例:

    FROM registry.docker.com/baseimg/centos-jdk:7.5_1.8
    
    COPY app-name.jar /opt/project/app.jar
    COPY entrypoint.sh /opt/project/entrypoint.sh
    EXPOSE 8080
    ENTRYPOINT ["/bin/sh", "/opt/project/entrypoint.sh"]
    

    将服务启动命令配置到 entrypoint.sh,这样我们可以扩展做很多事情,比如启动服务前做一些初始化操作等,还可以向容器传递参数到脚本执行一些特殊操作,而且这里变成脚本来启动,这样后续构建镜像基本不需要改 Dockerfile 了。

    #!/bin/bash
    # do other things here
    java -jar $JAVA_OPTS /opt/project/app.jar $1  > /dev/null 2>&1
    

    上边示例中,我们就注入 $JAVA_OPTS 环境变量,来优化 JVM 参数,还可以传递一个变量,这个变量大家应该就猜到了,就是服务启动加载哪个配置文件参数,例如:–spring.profiles.active=prod

    十、技术选型(参考)

    1、代码生成工具

    • MyBatis Generator

    MyBatis Generator是 MyBatis 的代码生成器,支持为 MyBatis 的所有版本生成代码。非常容易及快速生成 Mybatis 的Java POJO文件及数据库 Mapping 文件。

    2、核心框架

    • SpringBoot 2.x

    SpringBoot 它使用“习惯优于配置”(项目中存在大量的配置,此外还内置一个习惯性的配置,让你无须手动进行配置)的理念让 Java 项目快速运行起来。使用 SpringBoot 很容易创建一个独立运行(运行 Jar ,内嵌 Servlet 容器)、准生产级别的基于 Spring 的框架项目,使用 SpringBoot 你可以不用或者只需要很少的 Spring 配置。
    用白话来理解,就是 SpringBoot 其实不是什么新框架,它默认配置了很多框架的使用方式,就像 Maven 整合了所有的 Jar 包,SpringBoot 整合了几乎所有的框架。

    官网:https://spring.io/projects/spring-boot

    3、日志框架

    • Logback

    LogBack 是 Log4j 的改良版本,比 Log4j 拥有更多的特性,同时也带来很大性能提升,同时天然支持SLF4J。
    LogBack 官方建议配合 Slf4j 使用,这样可以灵活地替换底层日志框架。


    官网:http://logback.qos.ch/

    4、持久层框架

    • Mybatis 3

    MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

    5、连接池

    • 阿里 druid

    Druid 是一个关系型数据库连接池,它是阿里巴巴的一个开源项目。Druid 支持所有 JDBC 兼容数据库,包括了Oracle、MySQL、PostgreSQL、SQL Server、H2等。
    Druid 在监控、可扩展性、稳定性和性能方面具有明显的优势。通过 Druid 提供的监控功能,可以实时观察数据库连接池和SQL查询的工作情况。使用 Druid 连接池在一定程度上可以提高数据访问效率。

    官网:https://druid.apache.org/

    6、SQL拦截工具

    • P6Spy

    p6spy 是一个开源项目,通常使用它来跟踪数据库操作,查看程序运行过程中执行的sql语句。

    官网:https://github.com/p6spy/p6spy

    7、多数据源启动器

    • dynamic-datasource-spring-boot-starter

    dynamic-datasource-spring-boot-starter 是一个基于 springboot 的快速集成多数据源的启动器。
    其支持 Jdk 1.7+, SpringBoot 1.4.x、1.5.x、 2.0.x。

    官网:https://github.com/baomidou/dynamic-datasource-spring-boot-starter

    8、分页插件

    • MyBatis PageHelper

    MyBatis PageHelper 实现了通用的分页查询,其支持的数据有,mysql、Oracle、DB2、PostgreSQL等主流的数据库。

    github: https://github.com/pagehelper/Mybatis-PageHelper

    PageHelper.startPage(pageNum, pageSize);
    //之后进行查询操作将自动进行分页
    List<PmsBrand> brandList = brandMapper.selectByExample(new PmsBrandExample());
    //通过构造PageInfo对象获取分页信息,如当前页码,总页数,总条数
    PageInfo<PmsBrand> pageInfo = new PageInfo<PmsBrand>(list);
    

    9、API文档

    • swagger2.0

    Swagger是一款Restful 接口的文档在线自动生成、功能测试框架。一个规范和完整的框架,用于生成、描述、调用和可视化Restful 风格的Web服务,加上Swagger-UI,可以有很好的呈现。

    十一、开发环境(推荐)

    1、开发插件

    • Lombok

    Lombok 项目是一个 Java 库,它会自动插入您的编辑器和构建工具中,从而使您的Java更加生动有趣。
    永远不要再写另一个 getter 或 equals 方法,带有一个注释的您的类有一个功能全面的生成器,自动化您的日志记录变量等等。

    官网:https://projectlombok.org/

    • Hutool

    Hutool 是一个小而全的Java工具类库,它帮助我们简化每一行代码,避免重复造轮子。如果你有需要用到某些工具类的时候,不妨在 Hutool 里面找找。

    官网:https://www.hutool.cn/

    2、JDK

    • SUN JDK1.8及以上

    3、构建工具

    • Maven 3.5.4及以上

    Maven 作为一个构建工具,不仅能帮我们自动化构建,还能够抽象构建过程,提供构建任务实现;它跨平台,对外提供了一致的操作接口,这一切足以使它成为优秀的、流行的构建工具。
    Maven 不仅是构建工具,还是一个依赖管理工具和项目管理工具,它提供了中央仓库,能帮助我们自动下载构件。

    官网:https://maven.apache.org/

    4、Git 不限

    5、数据库

    • MySQL 5.7及以上
    • Navicat Premium 11.2.7及以上

    MySQL是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。
    MySQL是一种关系数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
    MySQL所使用的 SQL 语言是用于访问数据库的最常用标准化语言。MySQL 软件采用了双授权政策,分为社区版和商业版,由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,一般中小型网站的开发都选择 MySQL 作为网站数据库。
    ----- 摘抄自百度百科


    官网:https://www.mysql.com/

    6、IDE

    • IntelliJ IDEA 2020.1
    • 推荐插件:
      • Free MyBatis plugin:对MyBatis的xml具有强大的提示功能,同时可以关联mapper接口和mapper.xml中的sql实现。
      • Lombok plugin:Lombok为Java语言添加了非常有趣的附加功能,你可以不用再为实体类手写getter,setter等方法,通过一个注解即可拥有。
      • MyBatis Log Plugin:把Mybatis输出的SQL日志还原成完整的SQL语句。
      • RestfulToolkit:一套Restful服务开发辅助工具集,提供了项目中的接口概览信息,可以根据URL跳转到对应的接口方法中去,内置了HTTP请求工具,对请求方法做了一些增强功能。
      • GsonFormat:这款插件可以把JSON格式的字符串转化为实体类,当我们要根据JSON字符串来创建实体类的时候用起来很方便。
      • Grep Console:一款帮你分析控制台日志的插件,可以对不同级别的日志进行不同颜色的高亮显示,还可以用来按关键字搜索日志内容。
      • Alibaba Java Coding Guidelines:阿里巴巴《Java 开发手册》配套插件,可以实时检测代码中不符合手册规约的地方,助你码出高效,码出质量。
      • Maven Helper:解决Maven依赖冲突的好帮手,可以快速查找项目中的依赖冲突,并予以解决
      • Statistic:一款代码统计工具,可以用来统计当前项目中代码的行数和大小。
      • Vue.js:Vue.js支持插件,可以根据模板创建.vue文件,也可以对Vue相关代码进行智能提示。
      • element:Element-UI支持插件,可以对Element-UI中的标签进行智能提示,有了它就不用盲写相关代码了!

    7、其他工具

    • Postman:API接口调试工具。
    • PowerDesigner:数据库设计工具,平时用来设计数据库表,设计完成之后可以直接导出数据库表
    • RedisDesktop:Redis可视化工具,平时用来查看和管理Redis缓存中的数据,有时候需要清空缓存的时候就用到它了。
    • Robomongo:MongoDB可视化工具,平时用来查看和管理MongoDB中的数据。
    • X-shell:一款强大的安全终端模拟软件,可以用来连接和管理远程Linux服务器。-
    • ProcessOn:作图工具,可以用来制作思维导图和流程图。
    • Snipaste:一款好用的截屏工具。

    十二、代码提交规范

    1、基本原则

    • Git 代码完整提交正确姿势,建议先 Commit,再 Pull,最后 Push;
    • 代码提交前,保证本地编译通过;
    • 代码提交时,保证代码、文件完整提交,不要把本地测试代码、配置提交上去了;
    • 代码每次独立的功能、模块修改,都 Commit 到本地(不急每次都 Push);
    • 创建本地开发分支,完成后合并到特性分支,特性分支不可 push。

    2、提交注释规则

    格式:[type: description] #[相关任务编号]

    2.1、type

    • fix: 修复bug
    • add: 新功能
    • update: 更新
    • style : 代码格式改变
    • test: 增加测试代码
    • revert: 撤销上一次的commit
    • build: 构建工具或构建过程等的变动,如:gulp 换成了 webpack,webpack 升级等

    2.2、description

    • description 是对本次提交的简短描述;
    • 不超过50个字符;
    • 推荐以动词开头,如: 设置、修改、增加、删减、撤销等。

    3、示例

    fix:修复登录正确提示不准确缺陷 #demo-1243
    add:添加登录拦截校验功能 #demo-1240
    update:删除登陆弹出框提示 #demo-1241
    test:增加控制接口测试用例 #demo-1242
    

    关联任务单/缺陷单编号,例如:“demo-124”;

    《java开发手册》v1.7.0 嵩山版:

    • https://github.com/zuozewei/blog-example/blob/master/Java-api-test/12-Alibaba%20Java%20Coding%20Guidelines/%E3%80%8Ajava%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C%E3%80%8Bv1.7.0%20%E5%B5%A9%E5%B1%B1%E7%89%88.pdf

    参考资料:

    • [1]:《java开发手册》v1.7.0 嵩山版》
    • [2]:《Spring Boot & Kubernetes 云原生微服务实践》



    展开全文
  • 原标题:使用Microsoft VS Code的Java开发人员:立即升级到Java 11来自:https://www.linuxmi.com/微软向使用其Visual Studio Code (VS Code)源代码编辑器的Java开发人员发出了“行动呼吁”,以确保他们的代码能在...
  • (精选97道Java核心面试题) 常量池有哪些,数据结构,自己设计一个常量池 String为啥设计为final,好处是啥,其中的equals方法如何实现的 jdk序列化怎么实现,有测试过他的性能吗,serialVersionUID的作用是什么,...
  • 用.NET平台下的C#语言开发了比较长一段时间,最近项目开始用JAVA开发了,本文通过自己开发过程中的一些感受说下它们在具体开发过程的不同点,由于经验知识还有限,本篇文章只能从比较表面的以及自己常用的功能点来...
  • Java基础 JDK 和 JRE 有什么区别? == 和 equals 的区别是什么? 两个对象的 hashCode()相同,则 equals()也一定为 true,对吗? final 在 java 中有什么作用? java 中的 Math.round(-1.5) 等于多少? String 属于...
  • 前言:用java开发的客户端,还在不断开发完善中,客户有几十个时,如果挨个通知下载更新,就太麻烦了,于是实现了一个更新功能,以节省更新的人力物力。实现思路:从服务器获取最新版本号,和当前版本判断,从而判断...
  • 具有完备的集群管理能力,多扩多层次的安全防护和准入机制、多租户应用支撑能力、透明的服务注册和发现机制、內建智能负载均衡器、强大的故障发现和自我修复能力、服务滚动升级和在线扩容能力、可扩展的资源自动调度...
  • 升级Linux中的JAVA版本

    千次阅读 2021-02-28 13:46:21
    首先查看本机中的JAVA版本如何需要卸载掉现有的JAVA版本的话,可以使用rpm -qa | grep java 和 rpm -e xxx --nodeps进行卸载登录到JAVA官方下载界面,提供了rpm包和tar.gz包两种包。rpm包的话直接安装就可以了,不用...
  • java自动升级打怪小游戏斗罗大陆

    千次阅读 2021-12-10 22:52:09
    public class Dragon extends ... import java.sql. *; import java.util.*; /*import java.math.*; */ public class Games { public static void main(String[] args) { Scanner sc = new Scanner(System.in)
  • 分布式系统架构下,服务发布是一件很麻烦的事情,特别是在构建自动发布流程和灰度测试的策略两个核心方面。通常情况下如果不涉及数据层面的灰度流程,服务可以灰度上线,或者滚动上线,这两种方式很常用;如果涉及到...
  • 现在很多开发的人员只关注他们短期的工作前景,做的越久,发现自己越来越跟不上技术,所以,把时间和精力花在学习最能带来回报的新技术上是件非常重要的事情。这里是我们列举一些你需要马上开始学习的技术,让你不会...
  • java程序员如何进行物联网开发

    千次阅读 2021-03-18 14:40:58
    树莓派作为它作为全新的物联网开发方式,其的家族越来越强大,从1A到当前的4B系统列为主,另外还有zero系列,计算模块,不久前推出了pi pico开发板,其价格比较低,适合各种人学习从儿童编程到开发者都可以使用,儿童...
  • 如果正常关闭服务端,该文件是自动删除的。我参照上面方法解决的问题,用的是MyEclipse6.0,tomcat6.0。通常情况下,会认为是tomcat的缓存,会直接把整个localhost文件夹删除。但是上面的方法也是可取的,在localhost...
  • 从零开始vim搭建Java开发环境[视频]

    千次阅读 2021-03-06 15:59:25
    《玩转手机中的linux系统termux并搭建java开发环境》 玩着玩着发现vim真香!不仅能搭建简单的Java开发环境,甚至中大型spring boot项目也能驾驭。最终成品不会输成熟IDE多少。 为了新手尽可能少踩坑,本文我尽可能...
  • 升级是需要一定的经验的,不是野蛮升级然后测试兼容性,到时候蹦出一堆问题,难以招架。 开源组件版本号解释 最常见的命名规范: 主版本号 . 子版本号 [. 修正版本号 [. 编译版本号 ]] 主版本号:第一个数字,产品...
  • 1.1 Java 概述 1.1.1 什么是 JavaJava是一门面向对象编程语言,不仅吸收了 C++ 语言的各种优点,还摒弃了 C++ 里难以理解的多继承、指针等概念,因此 Java 语言具有功能强大和简单易用两个特征。Java 语言作为...
  • 锁的升级 Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个...
  • 在同步方法中它是一个ACC——SYNCHRONIZED标记位,相当于一个flag,它自动去走一个同步方法调用的策略 ,这个原理是比较简单的【问题16】 16.什么时候用synchronized和ReentrantLock有考虑吗? 它们俩的区别还是比较...
  • 如果遇到 Error from LanguageClient: ‘list’ object has no attribute ‘get’ 问题,解决办法见: 《macOS使用SpaceVim配置java开发环境爬坑》 参考文献 《Getting started with ctags + Vim on MacOS》
  • 来看Docker思维脑图:介绍-架构-安装-组成-命令-镜像-容器 可提供原件xmind 最后 整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记...
  • Sun 公司在 1995年年初发布了 JAVA 语言,JAVA 语言存在历史近二十几年,已发展成为人类计算机史上影响深远的编程语言。最主要的是连源代码都放到了互联网上免费使用,对所有人公开。JAVA 语言是一门非常纯粹的面向...
  • 分布式系统架构下,服务发布是一件很麻烦的事情,特别是在构建自动发布流程和灰度测试的策略两个核心方面。通常情况下如果不涉及数据层面的灰度流程,服务可以灰度上线,或者滚动上线,这两种方式很常用;如果涉及到...
  • 2021最新最全的Java开发学习路线阶段一 (夯实基础)一、Java基础语法二、Java面向对象编程三、Java核心类库四、XML与JSON五、算法与数据结构六、数据库七、JDBC技术八、H5前端九、JavaEE基础十、项目实战复习十一、...
  • 乐字节教育是集线上教育与线下培训于一体的全栈式教育机构,致力于研发高端IT技术,培养...然而,如此激烈的竞争,意味着Java开发人员必须时刻保持领先地位。为此,他们必须随时了解和洞悉Java生态系统中的最新动态。Ja
  • 最近一段时间发现经常看到很多人,对Spring源码比较感兴趣,日常开发中,无论你做什么什么项目,大部分都离不开Spring生态的那一套东西,所以很多人对Spring底层源码实现很感兴趣,但是有些从来没有接触过源码的...
  • 搭建Java开发环境

    2021-05-26 17:42:10
    一、Java 相关知识: Java 三个分支 J2SE(Standard Edition,核心)-->... Java1.5 以后进行重大升级,版本号开始以 java5 java6 java7 java 8 ..java 12 进行结尾 二、Java 是一门 跨平台语...
  • 对于后端开发来说,应用层是我们的主战场,也是整个系统最复杂的部分(当然,前端也不简单),所有的业务逻辑都汇聚在此。所以,对于应用层,我们需要进行进一步拆分,而不仅仅是在这里写业务逻辑就完事了。 对应用...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 126,821
精华内容 50,728
关键字:

java开发自动升级

java 订阅