2020.07.22 update
1 overview
1.1 introduction
A simple small salary management system, front-end JavaFX+ back-end Spring Boot, not much function, the main energy on the UI and some front-end logic above, the back-end is actually very simple.
Main functions:
- User registration/login
- Verification code retrieve password
- Users modify information and profile pictures
- Salaries are shown in bar chart form
- The administrator manages users and records wages
1.2 Response Process
1.3 presentation
Login interface:
User Interface:
Administrator interface:
2 the environment
2.1 Local Development Environment
- Manjaro 20.0.3
- The IDEA of 2020.1.1
- Its 11.0.7. U10-1
- OepnJFX 11.0.3. U1-1
- Spring Boot 2.3.0
- MySQL 8.0.20
2.2 Server Environment
- CentOS 8.1.1911
- OpenJDK 11
- Tomcat 9.0.33
- MySQL 8.0.17
3 Front-end code
3.1 Front-end Overview
The front end is mainly divided into five parts: controller module, view module, network module, animation module and tool module.
- Controller module: responsible for interactive events
- View module: Responsible for updating the UI
- Network module: sends the data request to the background
- Animation module: shift, zoom, fade in/out, rotate animation
- Tool module: encryption, check network connectivity, center interface, etc
3.2 an overview
3.2.1. Code directory tree
constant
Package: String constants and enumerated constants required by the projectcontroller
Package: Controller class, responsible for UI and user interactionentity
Package: Entity classlog
Package: logging classnetwork
Packet: Responsible for network requests, including request generation and request deliverytransition
Package: handles animationsutils
Package: utility classesview
Package: responsible for UI initialization and expected updates
3.2.2 Resource Directory Tree
css
: The style used by the interfacefxml
: a special XML file that defines the interface and functions in the binding Controller, that is, binding eventsimage
: Static imagekey
: certificate file for HTTPS connections in OkHttpproperties
: Some constant properties in the project
3.2.3 Project dependency
The main dependence is as follows:
- Gson: For converting between entity classes and maps and JSON strings
- Log4j2: log
- Lombok: Artifact not explained, but some voices say don’t use it, refer to here or here, depending on the individual
- OkHttp3: Network request
- Apache Commons: utility class
- OpenJFX11: OpenJFX core
3.3 Constant Module
Contains strings and enumeration constants required by the program:
CSSPath
: CSS path used to style the Scene, as inscene.getStylesheets.add(path)
FXMLPath
: FXML path used forFXMLLoader
loadingFXML
Files, such asFXMLLoader.load(getClass.getResource(path).openStream())
AllURL
: URL to send the request to the back endBuilderKeys
: the OkHttpFormBody.Builder
The constant key name used inPaneName
: Pane The name of the Pane used to switch between different Panes on the same SceneReturnCode
: Indicates the return code of the backend, which must be negotiated with the backendViewSize
: Interface size
To focus on the path problem, the author’s CSS and FXML files are under resources:
Where the FXML path is used in the project as follows:
URL url = getClass().getResource(FXMLPath.xxxx);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(url);
loader.load(url.openStream());
Copy the code
The fetch path is taken from the root path, such as messagebox.fxml in the figure above:
private static final String FXML_PREFIX = "/fxml/";
private static final String FXML_SUFFIX = ".fxml";
public static final String MESSAGE_BOX = FXML_PREFIX + "MessageBox" + FXML_SUFFIX;
Copy the code
If the FXML file is placed directly under the resources root directory, you can use:
getClass().getResource("/xxx.fxml");
Copy the code
Direct access.
The CSS in the same way:
private static final String CSS_PREFIX = "/css/";
private static final String CSS_SUFFIX = ".css";
public static final String MESSAGE_BOX = CSS_PREFIX + "MessageBox" + CSS_SUFFIX;
Copy the code
The URL of the network request suggests writing the path to the configuration file, such as reading from the configuration file here:
Properties properties = Utils.getProperties();
if(properties ! =null)
{
String baseUrl = properties.getProperty("baseurl") + properties.getProperty("port") + "/" + properties.getProperty("projectName");
SIGN_IN_UP_URL = baseUrl + "signInUp";
/ /...
}
Copy the code
3.4 Controller Module
The controller module is used to process user interaction events, which can be divided into three categories:
- Login registration interface Controller (Start package)
- User Interface Controller (Worker package)
- Admin Interface Controller (Admin package)
3.4.1 Login registration Page
This is the interface the program enters at the beginning, where it will bind some basic close, minimize, title bar drag events:
public void onMousePressed(MouseEvent e)
{
stageX = stage.getX();
stageY = stage.getY();
screexX = e.getScreenX();
screenY = e.getScreenY();
}
public void onMouseDragged(MouseEvent e)
{
stage.setX(e.getScreenX() - screexX + stageX);
stage.setY(e.getScreenY() - screenY + stageY);
}
public void close(a)
{
GUI.close();
}
public void minimize(a)
{
GUI.minimize();
}
Copy the code
Login interface controller is also very simple, a login/registration function plus a jump to retrieve the password interface, the code is not posted.
As for retrieving password interface, need to do more, you will first need to determine whether the user to enter the phone in the back-end database exists, also, check whether the two input password is consistent, but also to determine whether a text message sent successfully, and check the user input verification code with the back-end returned authentication code are consistent (message authentication code part actually don’t need the back-end processing, It was originally put in the front end, but was put in the back end because it might leak some important information.
3.4.2 User Interface
Next comes the interface the user enters after logging in, with fade and move animations:
public void userEnter(a)
{
new Transition()
.add(new Move(userImage).x(-70))
.add(new Fade(userLabel).fromTo(0.1)).add(new Move(userLabel).x(95))
.add(new Scale(userPolygon).ratio(1.8)).add(new Move(userPolygon).x(180))
.add(new Scale(queryPolygon).ratio(1.8)).add(new Move(queryPolygon).x(180))
.play();
}
public void userExited(a)
{
new Transition()
.add(new Move(userImage).x(0))
.add(new Fade(userLabel).fromTo(1.0)).add(new Move(userLabel).x(0))
.add(new Scale(userPolygon).ratio(1)).add(new Move(userPolygon).x(0))
.add(new Scale(queryPolygon).ratio(1)).add(new Move(queryPolygon).x(0))
.play();
}
Copy the code
The effect is as follows:
The actual processing is to put and
.add(new Move(userImage).x(-70))
Copy the code
X is the lateral displacement.
This is followed by fade and displacement text:
.add(new Fade(userLabel).fromTo(0.1)).add(new Move(userLabel).x(95))
Copy the code
FromTo represents the change in transparency, from 0 to 1, which is equivalent to fading in.
Finally, zoom in 1.8x and move the polygon right:
.add(new Scale(userPolygon).ratio(1.8)).add(new Move(userPolygon).x(180))
Copy the code
Ratio means magnification, in this case 1.8 times.
Similarly, the upper right also needs to be enlarged and moved:
.add(new Scale(queryPolygon).ratio(1.8)).add(new Move(queryPolygon).x(180))
Copy the code
The Transition, Scale and Fade used are customized animation processing classes. For details, see “3.8 Animation Module “.
3.5 Entity class module
A simple Worker:
@Getter
@Setter
@NoArgsConstructor
public class Worker {
private String cellphone;
private String password;
private String name = "No name";
private String department = "No department";
private String position = "No position";
private String timeAndSalary;
public Worker(String cellphone,String password)
{
this.cellphone = cellphone;
this.password = password; }}Copy the code
The annotations use Lombok, and the introduction of Lombok is here, and the full usage is here.
TimeAndSalary is a Map converted to String using Gson, with the key for the corresponding month and the value for salary. Please refer to the tool class module for specific conversion methods.
3.6 Log Module
The log module uses Log4j2, resources log4j2.xml as follows:
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="Time:%d{HH:mm:ss} Level:%-5level %nMessage:%msg%n"/>
</Console>
</appenders>
<loggers>
<logger name="test" level="info" additivity="false">
<appender-ref ref="Console"/>
</logger>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
Copy the code
This is the most general configuration, where pattern is the output format, where
%d{HH:mm:ss}
: Time formatlevel
: Log leveln
: a newlinemsg
: Log message
Front-end logs are simplified. If you need more configurations, search for them.
3.7 Network Module
The core of the network module is implemented using OkHttp, which is mainly divided into two packages:
request
: encapsulates various requests sent to the back endrequestBuilder
: Creates the Request Builder classOKHTTP
: encapsulates the OkHttp utility class, external only a static send method, only one parameter, request package class, using requestBuilder generation. The send method returns an Object. How does Object correspond to the return method where OKHTTP is used
3.7.1 request packet
Encapsulates a variety of network requests:
All requests inherit from BaseRequest, whose public methods include:
setUrl
: Sets the URL to be sentsetCellphone
: Adds cellphone parameterssetPassword
: Add password. Note that the password will be encrypted by sha-512setWorker
: Adds the Worker parametersetWorkers
: Accepts a List<Worker> that the administrator uses to save all workerssetAvatar
: Add profile picture parameterssetAvatars
: accepts a HashMap<String,String> with a key of phone, identifying a unique Worker, and a value of String that the image has been Base64 converted to
The only abstract method is:
public abstract Object handleResult(ReturnCode code):Copy the code
According to the result returned by different request processing, the back end returns a ReturnCode, which encapsulates the status code, error message and return value, and converts it from Gson to String. After the front end gets String, Gson converts it to ReturnCode, and obtains the status code and return value from it.
The rest of the request classes inherit from BaseRequest and implement different processing result methods, such as Get requests:
public class GetOneRequest extends BaseRequest {
@Override
public Object handleResult(ReturnCode code)
{
switch (code)
{
case EMPTY_CELLPHONE:
MessageBox.emptyCellphone();
return false;
case INVALID_CELLPHONE:
MessageBox.invalidCellphone();
return false;
case CELLPHONE_NOT_MATCH:
MessageBox.show("Fetch failed, phone number does not match");
return false;
case EMPTY_WORKER:
MessageBox.emptyWorker();
return false;
case GET_ONE_SUCCESS:
return Conversion.JSONToWorker(code.body());
default:
MessageBox.unknownError(code.name());
return false; }}}Copy the code
To obtain a Worker, the possible return values are as follows (return enumeration values defined in ReturnCode, which need to be unified at the front and back ends) :
EMPTY_CELLPHOE
: Indicates that the phone number in the sent GET request is emptyINVALID_CELLPHONE
: Illegal phone number, the judging code is:String reg = "^[1][358][0-9]{9}$"; return ! (Pattern.compile(reg).matcher(cellphone).matches());
CELLPHONE_NOT_MATCH
: The phone number does not match, that is, the database does not have the corresponding WorkerEMPTY_WORKER
: The Worker exists in the database, but an empty Worker is returned due to backend processing failure when converting to StringGET_ONE_SUCCESS
Convert String to Worker using the utility class- Others: Unknown error
3.7.2 requestBuilder package
Contains the Builder corresponding to request:
In addition to the default constructor and build methods, there are only set methods, such as:
public class GetOneRequestBuilder {
private final GetOneRequest request = new GetOneRequest();
public GetOneRequestBuilder(a)
{
request.setUrl(AllURL.GET_ONE_URL);
}
public GetOneRequestBuilder cellphone(String cellphone)
{
if(Check.isEmpty(cellphone))
{
MessageBox.emptyCellphone();
return null;
}
request.setCellphone(cellphone);
return this;
}
public GetOneRequest build(a)
{
returnrequest; }}Copy the code
With the URL set in the default constructor, all that remains is to set the phone to get the Worker.
3.7.3 OKHTTP
This is a static utility class that encapsulates OkHttp. The only public static method is as follows:
public static Object send(BaseRequest content)
{
Call call = client.newCall(new Request.Builder().url(content.getUrl()).post(content.getBody()).build());
try
{
ResponseBody body = call.execute().body();
if(body ! =null)
return content.handleResult(Conversion.stringToReturnCode(body.string()));
}
catch (IOException e)
{
L.error("Reseponse body is null");
MessageBox.show("Server disconnected, response null.");
}
return null;
}
Copy the code
In the case of synchronous POST requests, BaseRequest is used as the base class because it is easy to get the URL and request body in the Call, and asynchronous requests can be considered if there is a large amount of data. In addition, as mentioned above, the back end returns a ReturnCode converted to String by Gson, so after obtaining the body, it is first converted to ReturnCode before processing.
3.7.4 HTTPS
As for HTTPS, since it is deployed on Tomcat, the certificate needs to be set up in Tomcat, and the following three parts need to be set up in OkHttp:
sslSocketFactory
: SSL socket factoryHostnameVerifier
: Verifies the host nameX509TrustManager
: Certificate trust manager class
3.7.4.1 OkHttp configuration
There are three parts to set up, but let’s look at the simplest part, using the HostnameVerifier interface:
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(1500, TimeUnit.MILLISECONDS)
.hostnameVerifier((hostname, sslSession) -> {
if ("www.test.com".equals(hostname)) {
return true;
} else {
HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();
return verifier.verify(hostname, sslSession);
}
}).build();
Copy the code
Return true if the host name is www.test.com (which can also be used with a public IP address), otherwise use the default HostnameVerifier. If the service logic is complex, dynamic verification can be performed based on the configuration center and black/white list.
Next, X509TrustManager handles this:
private static X509TrustManager trustManagerForCertificates(InputStream in)
throws GeneralSecurityException
{
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(in);
if (certificates.isEmpty()) {
throw new IllegalArgumentException("expected non-empty set of trusted certificates");
}
char[] password = "www.test.com".toCharArray(); // Any password will work.
KeyStore keyStore = newEmptyKeyStore(password);
int index = 0;
for (Certificate certificate : certificates) {
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificate);
}
// Use it to build an X509 trust manager.
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, password);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if(trustManagers.length ! =1| |! (trustManagers[0] instanceof X509TrustManager)){
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
}
private static KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); // Add custom password here, default
InputStream in = null; // By convention, 'null' creates an empty key store.
keyStore.load(in, password);
return keyStore;
} catch (IOException e) {
throw newAssertionError(e); }}Copy the code
Return a trust manager that trusts the certificate read from the input stream. SSLHandsakeException is thrown if the certificate is not signed. It is recommended to use third-party signed certificates rather than self-signed ones (such as those generated using OpenSSL or Acme.sh), especially in production environments. The example notes also say:
Finally, SSL socket factory processing:
private static SSLSocketFactory createSSLSocketFactory(a) {
SSLSocketFactory ssfFactory = null;
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null.new TrustManager[]{trustManager}, new SecureRandom());
ssfFactory = sc.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
return ssfFactory;
}
Copy the code
The complete OkHttpClient construct is as follows:
X509TrustManager trustManager = trustManagerForCertificates(OKHTTP.class.getResourceAsStream("/key/pem.pem"));
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(1500, TimeUnit.MILLISECONDS)
.sslSocketFactory(createSSLSocketFactory(), trustManager)
.hostnameVerifier((hostname, sslSession) -> {
if ("www.test.com".equals(hostname)) {
return true;
} else {
HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();
return verifier.verify(hostname, sslSession);
}
})
.readTimeout(10, TimeUnit.SECONDS).build();
Copy the code
Pem indicates the certificate file under resources.
3.7.4.2 Setting a Certificate for the Server
Use WAR for deployment. Search for JAR deployment mode, Tomcat server, and other Web servers by yourself.
Conf /server.xml in the Tomcat configuration file:
Go to
and copy it and change the name to the corresponding domain name:
Then download the file from the certificate vendor (usually with the document, according to the document deployment), Tomcat is two files, one is PFX, one is password file, continue to modify server.xml, search 8443, find the following location:
is HTTP/1.1 based on NIO implementation, and
is HTTP/2 based on APR implementation.
It is easier to use HTTP/1.1 and simply modify server. XML. It is more difficult to use HTTP/2 and install APR, apr-util and tomcat-native. Take HTTP/1.1 as an example and modify as follows:
<Connector port="8123" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="200" SSLEnabled="true"
scheme="https" secure="true"
keystoreFile="/xxx/xxx/xxx/xxx.pfx" keystoreType="PKCS12"
keystorePass="YOUR PASSWORD" clientAuth="false"
sslProtocol="TLS">
</Connector>
Copy the code
Change the certificate location and password. To be more secure, you can specify a TLS version, such as TLS1.2:
<Connector .
sslProtocol="TLS" sslEnabledProtocols="TLSv1.2"
>
Copy the code
3.7.5 Image processing
Images were originally intended to be processed using OkHttp’s MultipartBody, but the images were not very good and it seemed unnecessary. Besides, the data of entity classes were all transmitted in the form of strings. Therefore, the author thought that we could transfer all the images in strings uniformly. The Base64 function, which requires external dependencies, has been changed to JDK Base64:
public static String avatarToString(Path path)
{
try
{
return new String(encoder.encode(Files.readAllBytes(path)));
}
catch (IOException e)
{
MessageBox.avatarToStringFailed();
L.error(e);
return null; }}public static void stringToAvatar(String base64Code, String cellphone){
try
{
if(! Files.exists(TEMP_PATH)) Files.createDirectory(TEMP_PATH);if(! Files.exists(getPath(cellphone))) Files.createFile(getPath(cellphone)); Files.write(getPath(cellphone), decoder.decode(base64Code)); }catch(IOException e) { MessageBox.stringToAvatarFailed(); L.error(e); }}Copy the code
Base64 is a method of representing binary data based on 64 printable characters. It can convert binary data (pictures/videos, etc.) into characters or decode corresponding characters into original binary data.
The author measured that the conversion speed of this method is not slow. As long as there is a correct conversion function, the server side can easily convert, but the support for large files is not good:
This is good enough for general images, but for real files it is recommended to use MultipartBody.
3.8 Animation Module
Contains four types of animation:
- Fade in/out
- The displacement
- The zoom
- rotating
These four classes implement the CustomTransitionOperation interface:
import javafx.animation.Animation;
public interface CustomTransitionOperation {
double defaultSeconds = 0.4;
Animation build(a);
void play(a);
}
Copy the code
Among them:
defaultSeconds
Represents the number of seconds the animation lasts by defaultbuild
Used forTransition
In the various animation classes for unitybuild
operationplay
For playing animations
The four animation classes are similar, taking the rotating animation class as an example:
public class Rotate implements CustomTransitionOperation{
private final RotateTransition transition = new RotateTransition(Duration.seconds(1));
public Rotate(Node node)
{
transition.setNode(node);
}
public Rotate seconds(double seconds)
{
transition.setDuration(Duration.seconds(seconds));
return this;
}
public Rotate to(double to)
{
transition.setToAngle(to);
return this;
}
@Override
public Animation build(a) {
return transition;
}
@Override
public void play(a) { transition.play(); }}Copy the code
Seconds Sets the number of seconds and to sets the rotation Angle. All animation classes are controlled by Transition:
public class Transition {
private final ArrayList<Animation> animations = new ArrayList<>();
public Transition add(CustomTransitionOperation animation)
{
animations.add(animation.build());
return this;
}
public void play(a)
{ animations.forEach(Animation::play); }}Copy the code
Inside is a collection of animation classes. Each time you add the corresponding animation and then add it to the collection, and finally play it in a unified manner. Example usage is as follows:
new Transition()
.add(new Move(userImage).x(-70))
.add(new Fade(userLabel).fromTo(0.1)).add(new Move(userLabel).x(95))
.add(new Scale(userPolygon).ratio(1.8)).add(new Move(userPolygon).x(180))
.add(new Scale(workloadPolygon).ratio(1.8)).add(new Move(workloadPolygon).x(180))
.play();
Copy the code
3.9 Tool Module
AvatarUtils
: used for local generation of temporary images and image conversion processingCheck
: Checks whether it is empty and validConversion
: Conversion class, via Gson inWorker/String
.Map/String
.List/String
To convert betweenUtils
: Encrypt, set the operating environment, centerStage
, check network connectivity, etc
Utils and Conversion.
3.9.1 Conversion
Class to convert String to List/Worker/Map using Gson, e.g. String to Map:
public static Map<String,Double> stringToMap(String str)
{
if(Check.isEmpty(str))
return null; Map<? ,? > m = gson.fromJson(str,Map.class); Map<String,Double> map =new HashMap<>(m.size());
m.forEach((k,v)->map.put((String)k,(Double)v));
return map;
}
Copy the code
Most Conversion functions are similar, nullating first, and then performing the corresponding type Conversion. The Conversion here is basically the same as the back end, which also needs to use the Conversion class for Conversion operation.
3.9.2 Utils
Get the properties file as follows:
// Get the properties file
public static Properties getProperties(a)
{
Properties properties = new Properties();
// The project properties file is split into config_dev.properties,config_test.properties,config_prod.properties
String fileName = "properties/config_"+ getEnv() +".properties";
ClassLoader loader = Thread.currentThread().getContextClassLoader();
try(InputStream inputStream = loader.getResourceAsStream(fileName))
{
if(inputStream ! =null)
{
// Prevent garbled characters
properties.load(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
return properties;
}
L.error("Can not load properties properly.InputStream is null.");
return null;
}
catch (IOException e)
{
L.error("Can not load properties properly.Message:"+e.getMessage());
return null; }}Copy the code
Another is a way to check network connectivity:
public static boolean networkAvaliable(a)
{
try(Socket socket = new Socket())
{
socket.connect(new InetSocketAddress("www.baidu.com".443));
return true;
}
catch (IOException e)
{
L.error("Can not connect network.");
e.printStackTrace();
}
return false;
}
public static boolean backendAvaliable(a)
{
try(Socket socket = new Socket())
{
if(isProdEnvironment())
socket.connect(new InetSocketAddress("www.test.com".8888));
else
socket.connect(new InetSocketAddress("127.0.0.1".8080));
return true;
}
catch (IOException e)
{
L.error("Can not connect back end server.");
L.error(ExceptionUtils.getStackTrace(e));
}
return false;
}
Copy the code
The socket is used to determine whether the network is connected and whether the back end is connected.
Finally, there is the method of centering Stage. Although Stage is equipped with a centerOnScreen, the result is not good. The author’s actual measurement is horizontally centered but vertically upward, not vertically horizontally centered.
So manually set x and Y for the Stage based on the screen width and size of the Stage.
public static void centerMainStage(a)
{
Rectangle2D screenRectangle = Screen.getPrimary().getBounds();
double width = screenRectangle.getWidth();
double height = screenRectangle.getHeight();
Stage stage = GUI.getStage();
stage.setX(width/2 - ViewSize.MAIN_WIDTH/2);
stage.setY(height/2 - ViewSize.MAIN_HEIGHT/2);
}
Copy the code
3.10 View Module
GUI
: global variable sharing and controlScene
The switch ofMainScene
: global controller that initializes and binds keyboard eventsMessageBox
: Information box, provided externallyshow()
Static method, etc
The main method in the GUI is switchToXxx, for example:
public static void switchToSignInUp(a)
{
if(GUI.isUserInformation())
{
AvatarUtils.deletePathIfExists();
GUI.getUserInformationController().reset();
}
mainParent.requestFocus();
children.clear();
children.add(signInUpParent.lookup(PaneName.SIGN_IN_UP));
scene.getStylesheets().add(CSSPath.SIGN_IN_UP);
Label minimize = (Label) (mainParent.lookup("#minimize"));
minimize.setText("-");
minimize.setFont(new Font("System".20));
minimize.setOnMouseClicked(v->minimize());
}
Copy the code
Jump to the login registration interface, which is a public static method, first determine whether it is a user information interface, then make the Parent get focus (for keyboard event response), then add the corresponding AnchorPane to Children, add CSS, and finally modify the button text and event.
We also added some keyboard event responses to the MainScene, such as Enter:
ObservableMap<KeyCombination,Runnable> keyEvent = GUI.getScene().getAcclerators();
keyEvent.put(new KeyCodeCombination(KeyCode.ENTER),()->
{
if (GUI.isSignInUp())
GUI.getSignInUpController().signInUp();
else if (GUI.isRetrievePassword())
GUI.getRetrievePasswordController().reset();
else if(GUI.isWorker())
GUI.switchToUserInformation();
else if(GUI.isAdmin())
GUI.switchToUserManagement();
else if(GUI.isUserInformation())
{
UserInformationController controller = GUI.getUserInformationController();
if(controller.isModifying())
controller.saveInformation();
else
controller.modifyInformation();
}
else if(GUI.isSalaryEntry()) { GUI.getSalaryEntryController().save(); }});Copy the code
4 Front-end UI
4.1 FXML
The interface is basically controlled by these FXML files. There is not much content in this part. It is basically designed by the Scene Builder of IDEA, and a little part is controlled by code.
- The root node is AnchorPane, one for each FXML
fx:id
In order to switch - The event is bound to the corresponding control, such as a mouse entry event bound to a Label, set on the Label
onMouseEntered="#xxx"
, where the method is the corresponding controller (fx:controller="xxx.xxx.xxx.xxxController"
). <Image>
The URL attribute in the@
, such as<Image url="@.. /.. /image/xxx.png">
4.2 CSS
JFX integrates some CSS beautification features, such as:
-fx-background-radius: 25px;
-fx-background-color:#e2ff1f;
Copy the code
The usage requires that the ID be set in FXML first.
Notice the difference between the two ids:
fx:id
id
Fx :id refers to the control’s FX: ID, usually used in conjunction with @fXML in controllers, such as a Label with fx: ID set to label1
<Label fx:id="label1" layoutX="450.0" layoutY="402.0" text="Label">
<font>
<Font size="18.0" />
</font>
</Label>
Copy the code
@fxml can be used in the corresponding Controller with the same name as fx:id:
@FXML
private Label label1;
Copy the code
Id refers to the ID of the CSS, and can be used in the CSS reference, for example, the above Label also set id (can be the same, can also be different) :
<Label fx:id="label1" id="label1" layoutX="450.0" layoutY="402.0" text="Label">
<font>
<Font size="18.0" />
</font>
</Label>
Copy the code
Then refer to the CSS file as if it were a normal ID:
#label1
{
-fx-background-radius: 20px; / * rounded corners * /
}
Copy the code
JFX also supports CSS pseudo-classes, such as the following minimized and closed mouse-over effects that are implemented using pseudo-classes:
#minimize:hover
{
-fx-opacity: 1;
-fx-background-radius: 10px;
-fx-background-color: # 323232;
-fx-text-fill: #ffffff;
}
#close:hover
{
-fx-opacity: 1;
-fx-background-radius: 10px;
-fx-background-color: #dd2c00;
-fx-text-fill: #ffffff;
}
Copy the code
Of course, some of the more complicated ones are not supported. I’ve tried things like Transition, but they’re not supported.
Finally, we need to introduce CSS into the Scene:
Scene scene = new Scene();
scene.getStylesheets().add("xxx/xxx/xxx/xxx.css");
Copy the code
The usage in the program is:
scene.getStylesheets().add(CSSPath.SIGN_IN_UP);
Copy the code
4.3 Stage Construction process
The following uses the prompt box as an example to illustrate the construction process of Stage.
try {
Stage stage = new Stage();
Parent root = FXMLLoader.load(getClass().getResource(FXMLPath.MESSAGE_BOX));
Scene scene = new Scene(root, ViewSize.MESSAGE_BOX_WIDTH,ViewSize.MESSAGE_BOX_HEIGHT);
scene.getStylesheets().add(CSSPath.MESSAGE_BOX);
Button button = (Button)root.lookup("#button");
button.setOnMouseClicked(v->stage.hide());
Label label = (Label)root.lookup("#label");
label.setText(message);
stage.initStyle(StageStyle.TRANSPARENT);
stage.setScene(scene);
Utils.centerMessgeBoxStage(stage);
stage.show();
root.requestFocus();
scene.getAccelerators().put(new KeyCodeCombination(KeyCode.ENTER), stage::close);
scene.getAccelerators().put(new KeyCodeCombination(KeyCode.BACK_SPACE), stage::close);
} catch (IOException e) {
/ /...
}
Copy the code
First, create a Stage, and then use FXMLLoader to load the FXML file on the corresponding path. After obtaining Parent, use the Parent to generate Scene, and then add style for Scene.
FindViewById (fx:id); findViewById (fx:id); findViewById (fx:id) Once the control is processed, center and display the Stage, bind the keyboard event and let the Parent get the focus.
5 Back-end
5.1 Back-end Overview
The back end takes Spring Boot framework as the core and is deployed in WAR mode. The whole end is divided into three layers:
- Controller layer: Responsible for receiving requests from the front end and calling business layer methods
- Business layer: handles the main business, such as CRUD, image processing, etc
- Persistence layer: Data persistence, Hibernate+Spring Data JPA
Generally speaking, there are no lofty things used and the logic is relatively simple.
5.2 an overview
5.2.1 Code directory tree
5.2.2 rely on
The main dependence is as follows:
- Spring Boot Starter Data JPA: Data persistence
- Guava: used to
Iterable<Worker>
Convert to set - Lombok: Same front-end
- Gson: JSON conversion class
- Apache Commons: For exception handling + random string generation
- TencentCloud SDK Java: SMS verification code API
- Jasypt Spring Boot Starter: encrypts configuration files
5.3 Controller Layer
The controller can be divided into three types: one processing picture, one processing CRUD request, and one processing SMS request. The controller accepts POST request and ignores GET request. The general processing flow is that after receiving the parameter, it first performs judgment operations, such as nulling and judging whether it is valid, etc., then calls the methods of the business layer and encapsulates the returned result, while logging, and finally converts the returned result into a string using Gson. Most of the code is relatively simple not to paste, say about the SMS verification code part.
Verification code module uses Tencent cloud interface, here on the official website, search SMS function.
New users are given 100 SMS messages by default:
You need to create a signature and body template before sending the template. You can use the template after passing the verification.
You can try the SMS function according to the quick Start. If you can receive the SMS successfully, you can click here to see the API (Java version).
The following example is simplified from the document example:
@PostMapping("sendSms")
public @ResponseBody
String sendSms(@RequestParam String cellphone)
{
String randomCode = RandomStringUtils.randomNumeric(6);
if(Check.isEmpty(cellphone))
{
L.sendSmsFailed("null",randomCode,"cellphone is empty");
return toStr(ReturnCode.EMPTY_CELLPHONE);
}
if(Check.isInvalidCellphone(cellphone))
{
L.sendSmsFailed(cellphone,randomCode,"cellphone is not valid.");
return toStr(ReturnCode.INVALID_CELLPHONE);
}
ReturnCode s = ReturnCode.SEND_SMS_SUCCESS;
try
{
SmsClient client = new SmsClient(new Credential(secretId,secretKey),"");
SendSmsRequest request = new SendSmsRequest();
request.setSmsSdkAppid(appId);
request.setSign(sign);
request.setTemplateID(templateId);
String [] templateParamSet = {randomCode};
request.setTemplateParamSet(templateParamSet);
String [] phoneNumbers = {"+ 86"+cellphone};
request.setPhoneNumberSet(phoneNumbers);
SendSmsResponse response = client.SendSms(request);
if(response ! =null && response.getSendStatusSet()[0].getCode().equals("Ok")) { L.sendSmsSuccess(cellphone,randomCode); s.body(randomCode); }}catch (Exception e) {
L.sendSmsFailed(cellphone,randomCode,e);
s = ReturnCode.UNKNOWN_ERROR;
}
return toStr(s);
}
Copy the code
AppId,sign, and templateID are the corresponding appId, signature id, and body templateID respectively. After the application is approved, the application will be allocated, and a six-digit verification code will be generated randomly.
Request. SetPhoneNumberSet () parameters for the need to send cell phone number String array, pay attention to the need to add the area code. If the message is sent successfully, the mobile phone will receive it. If the message fails, modify it according to the abnormal information.
The only thing to notice is when data like appID is injected via @value, as in:
@Controller
@RequestMapping("/")
public class SmsController {
@Value("${tencent.secret.id}")
privateString secretId; . }Copy the code
However, since the sign part contains Chinese, encoding conversion is required:
@Value("${tencent.sign}")
private String sign;
@PostConstruct
public void init(a)
{
sign = new String(sign.getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8);
}
Copy the code
5.4 Service Layer and Persistence Layer
For example, the saveOne method of the business layer saves a Worker, and then converts it into a Worker directly using the save method provided by CrudRespository
:
public ReturnCode saveOne(String json) {
ReturnCode s = ReturnCode.SAVE_ONE_SUCCESS;
Worker worker = Conversion.JSONToWorker(json);
if (Check.isEmpty(worker)) {
L.emptyWorker();
s = ReturnCode.EMPTY_WORKER;
}
else
workerRepository.save(worker);
return s;
}
Copy the code
The saveAll method of CrudRepository
is Iterable
, so you can save the List directly.
public ReturnCode saveAll(List<Worker> workers)
{
workerRepository.saveAll(workers);
return ReturnCode.SAVE_ALL_SUCCESS;
}
Copy the code
The String sent from the front end needs to be converted to List in the control layer.
5.5 log
The log uses Spring Boot’s own logging system, with a simple configuration of the log path. In addition, the log format is customized (for clean output, I felt that the configuration file was not implemented well enough, so I customized a utility class).
For example, log interception is as follows:
Custom headers and fixed output for each line, followed by a prompt for method, level, time, and other information.
In total, there are 7 classes except the formatter, of which L is the main class, the outer class only needs to call L’s methods, most of which are public static methods, and the remaining 6 are classes called by L:
If the backup is successful, call:
public Success
{
public static void backup(a)
{
l.info(new FormatterBuilder().title(getTitle()).info().position().time().build());
}
/ /...
}
Copy the code
FormatterBuilder FormatterBuilder FormatterBuilder FormatterBuilder FormatterBuilder FormatterBuilder FormatterBuilder FormatterBuilder FormatterBuilder FormatterBuilder FormatterBuilder FormatterBuilder FormatterBuilder FormatterBuilder FormatterBuilder FormatterBuilder
public FormatterBuilder info(a)
{
return level("info");
}
public FormatterBuilder time(a)
{
content("time",getCurrentTime());
return this;
}
private FormatterBuilder level(String level)
{
content("level",level);
return this;
}
public FormatterBuilder cellphone(String cellphone)
{
content("cellphone",cellphone);
return this;
}
public FormatterBuilder message(String message)
{
content("message",message);
return this;
}
Copy the code
5.6 tools
Four:
- Backup: Backup the periodic database
- Check: Checks the validity and whether it is empty
- Conversion: Conversion class, almost identical to the front-end, using Gson in
String
withList/Map/Worker
To convert between - ReturnCode: ReturnCode enumeration class
To focus on backup, the code is not long and the entire class is posted directly:
@Component
@EnableScheduling
public class Backup {
private static final long INTERVAL = 1000 * 3600 * 12;
@Value("${backup.command}")
private String command;
@Value("${backup.path}")
private String strPath;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.url}")
private String url;
@Value("${backup.dataTimeFormat}")
private String dateTimeFormat;
@Scheduled(fixedRate = INTERVAL)
public void startBackup(a)
{
try
{
String[] commands = command.split(",");
String dbname = url.substring(url.lastIndexOf("/") +1);
commands[2] = commands[2] + username + " --password=" + password + "" + dbname + ">" + strPath +
dbname + "_" + DateTimeFormatter.ofPattern(dateTimeFormat).format(LocalDateTime.now())+".sql";
Path path = Paths.get(strPath);
if(! Files.exists(path)) Files.createDirectories(path); Process process = Runtime.getRuntime().exec(commands); process.waitFor();if(process.exitValue() ! =0)
{
InputStream inputStream = process.getErrorStream();
StringBuilder str = new StringBuilder();
byte []b = new byte[2048];
while(inputStream.read(b,0.2048) != -1)
str.append(new String(b));
L.backupFailed(str.toString());
}
L.backupSuccess();
}
catch(IOException | InterruptedException e) { L.backupFailed(e.getMessage()); }}}Copy the code
First use @Value to get the Value in the configuration file, and then add @scheduled to the backup method. @scheduled is a Spring Boot annotation used to provide Scheduled tasks that are Scheduled to be executed at a specified time or at intervals (in this case, half a day). There are three main ways to configure the execution time:
- cron
- fixedRate
- fixedDelay
I’m not going to expand it here, but I can use it here.
Add @enablesCheduling to the class before using it. During backup, first use URL to obtain database name, and then assemble backup command. Note that if local use Win to develop backup command will be different from Linux:
// Win (untested, developed by author on Linux)
command[0]=cmd
command[1]=/c
command[2]=mysqldump -u username --password=your_password dbname > backupPath+File.separator+dbname+datetimeFormmater+".sql"
// Linux (Manjaro+ server CentOS test passed)
command[0]=/bin/sh
command[1]=-c
command[2]=/usr/bin/mysqldump -u username --password=your_password dbname > backupPath+File.separator+dbname+datetimeFormmater+".sql"
Copy the code
If a backup path exists, Java’s own Process is used to do the backup. If errors occur, getErrorStream() is used to get error messages and log.
5.7 Configuration Files
5.7.1 Classification of configuration files
One total profile + three are profiles for a specific environment (development, test, production). You can switch profiles with spring.profiles. Active =dev, for example, spring.profiles. Additional custom configurations require additional fields in the addition-spring-configuration-metadata. json (optional, but prompted by the IDE), such as:
"properties": [
{
"name": "backup.path",
"type": "java.lang.String",
"defaultValue": null
},
]
Copy the code
5.7.2 encryption
In 2020, it’s not a good idea to use plaintext passwords in configuration files, is it?
It’s time to encrypt.
The Jasypt Spring Boot component is used.
Usage is not described in detail here, see my other blog for details, poke here.
However, the author measured that the latest version 3.0.2 (this article was written in 2020.06.05, 2020.05.31, the author has updated version 3.0.3, but the author has not tested it) would have the following problems:
Description:
Failed to bind properties under 'spring.datasource.password' to java.lang.String:
Reason: Failed to bind properties under 'spring.datasource.password' to java.lang.String
Action:
Update your application's configuration
Copy the code
The solution and detailed description of the problem are here.
6 Deployment and Packaging
6.1 Front-end Packaging
First, let’s talk about the front-end packaging process, simply say that the JAR can run cross-platform, but if it is a specific platform, such as Win, want to create an EXE without additional JDK environment still need some additional operations, here is a brief introduction to the packaging process.
JFX: Native can be packaged with MVN JFX :native for JDK8. This can be easily packaged as DMG or EXE. If you know how to use javafX-Maven-plugin or IDEA with an artifact type exe or DMG
6.1.1 IDEA Package at a time
Maven plug-in is used for packaging. The commonly used Maven package plug-in is as follows:
- Mave-jar-plugin: the default package jar plugin. The generated JR is very small, but you need to place the lib in the same directory as the JAR
- Maven-shade-plugin: Provides two basic functions: package dependent JAR packages into the current JAR package, rename dependent JAR packages and filter trade-off
- Maven-assembly-plugin: Supports custom packaging, more of a reassembly of the project directory
This project is packaged using maven-shade-plugin.
The latest version of Maven can be found on Github:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>xxxx.xxx.xxx.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Copy the code
Just modify the main class:
<mainClass>xxxx.xxx.xxx.Main</mainClass>
Copy the code
You can then pack with one click from Maven in the right pane of your IDEA:
This gives you JAR packages under Target that can run across platforms as long as you provide a JDK environment.
java -jar xxx.jar
Copy the code
The following two steps are to use Exe4J and Enigma Virtual Box into a single EXE method, only for Win, using Linux/Mac can skip or search for other methods.
6.1.2 Exe4J Secondary Packaging
6.1.2.1 exe4j
Exe4j can integrate Java applications into Java executable file generation tools under Win, whether for servers or for GUI or command line applications. In a nutshell, this project uses it to convert a JAR to an EXE. Exe4j requires a JRE, which needs to be generated from JDK9, so it needs to be packaged using Exe4j as a JRE.
6.1.2.2 generated jre
The functions of each module can be viewed here:
After testing the required modules of this program are as follows:
java.base,java.logging,java.net.http,javafx.base,javafx.controls,javafx.fxml,javafx.graphics,java.sql,java.management
Copy the code
Switch to the JDK directory and use jlink to generate the JRE:
jlink --module-path jmods --add-modules
java.base,java.logging,java.net.http,javafx.base,javafx.controls,javafx.fxml,javafx.graphics,java.sql,java.management
--output jre
Copy the code
OpenJDK11 does not come with JavaFX, so download JFX jmods for Win platform and move it to JDK jMOds directory. The generated JRE is 91M in size:
It is not recommended to use all modules if it is really not clear which modules to use:
jlink --module-path jmods --add-modules java.base,java.compiler,java.datatransfer,java.xml,java.prefs,java.desktop,java.instrument,java.logging,java.management, java.security.sasl,java.naming,java.rmi,java.management.rmi,java.net.http,java.scripting,java.security.jgss,java.transac tion.xa,java.sql,java.sql.rowset,java.xml.crypto,java.se,java.smartcardio,jdk.accessibility,jdk.internal.vm.ci,jdk.manag ement,jdk.unsupported,jdk.internal.vm.compiler,jdk.aot,jdk.internal.jvmstat,jdk.attach,jdk.charsets,jdk.compiler,jdk.cry pto.ec,jdk.crypto.cryptoki,jdk.crypto.mscapi,jdk.dynalink,jdk.internal.ed,jdk.editpad,jdk.hotspot.agent,jdk.httpserver,j dk.internal.le,jdk.internal.opt,jdk.internal.vm.compiler.management,jdk.jartool,jdk.javadoc,jdk.jcmd,jdk.management.agen t,jdk.jconsole,jdk.jdeps,jdk.jdwp.agent,jdk.jdi,jdk.jfr,jdk.jlink,jdk.jshell,jdk.jsobject,jdk.jstatd,jdk.localedata,jdk. management.jfr,jdk.naming.dns,jdk.naming.rmi,jdk.net,jdk.pack,jdk.rmic,jdk.scripting.nashorn,jdk.scripting.nashorn.shell ,jdk.sctp,jdk.security.auth,jdk.security.jgss,jdk.unsupported.desktop,jdk.xml.dom,jdk.zipfs,javafx.web,javafx.swing,java fx.media,javafx.graphics,javafx.fxml,javafx.controls,javafx.base --output jreCopy the code
Size 238M:
6.1.2.3 exe4j packaging
Exe4j use reference here, the first interface should look like this:
Configuration files are not available for first run, next will do.
Select JAR in EXE mode:
Fill in name and output directory:
Here the type is GUI Application, fill in the name of the executable file, select the icon path, and check allow a single application instance to run:
Redirection here, you can select the output directory of standard output stream and standard error stream, default if you do not need:
64-bit Win needs to check generate 64-bit executable file:
Next, the Java class and JRE path Settings:
Select the JAR generated by IDEA and fill in the main classpath:
Set the lowest and highest supported jre versions:
The next step is to specify the JRE search path, first removing the default three locations:
Next, select the JRE generated earlier, place the JRE in the same directory as the JAR, and fill the path with the JRE in the current directory:
Exe4j has Finished exe4j has Finished
This is generated using exe4j:
If there are no missing modules, it should be able to start normally. If there are missing modules, it will generate an error. Log in the current exe path by default.
6.1.3 Enigma Virtual Box packaging for three times
When packaged with Exe4j, you can run it directly, but the JRE is too large and you have to install an EXE. Fortunately, I used the Enigma Virtual Box packaging tool to package all the files into a single EXE.
Exe4j: exe4j: exe4j: exe4j
Then create a new JRE directory and add the JRE generated in the previous step:
Finally select compressed file:
The packaged single EXE is 65MB in size, which saves space compared to the 89MB JRE that exe4J also carries with it.
6.2 Back-end Deployment
The back-end deployment mode is also simple. WAR deployment mode is adopted. If the project is packaged in JAR package, it can be converted into WAR package by itself. Since the Web server is Tomcat, you can directly place the WAR package under webapps. You can search for other Web servers by yourself.
You can also deploy using Docker, but you need to use a JAR instead of a WAR and search for it yourself.
7 run
This project has been packaged. The front end includes JAR and EXE, and the back end includes JAR and WAR. Run the back end first (start database service first) :
Use the jar:
java -jar Backend.jar
Copy the code
Use war to put it directly into Tomcat webapps and then into bin:
./startup.sh
Copy the code
For Windows, you can run exe directly. For Linux, you can also run jar:
java -jar Frontend.jar
Copy the code
If the operation fails, you can open the project with IDEA and run it directly in IDEA or package it by yourself.
8 Precautions
8.1 Path Problems
Never use relative or absolute paths directly for resource files, such as:
String path1 = "/xxx/xxx/xxx/xx.png";
String path2 = "xxx/xx.jpg";
Copy the code
This has many problems, such as the possibility of running in IDEA directly and into jar package results are inconsistent, the path can not be read, and there may be platform problems, Linux path separator is known to be inconsistent with Windows. Therefore, the following methods are used to obtain resource files:
String path = getClass().getResource("/image/xx.png");
Copy the code
Image is located directly under the Resources folder. Other things are similar, so the/here stands for resources.
8.2 the HTTPS
HTTPS is not provided by default, the certificate file is not mounted, and the local port 8080 is used.
If you want to customize HTTPS, modify the front-end
com.test.network.OKHTTP
resources/key/pem.pem
At the same time, the back-end needs to modify Tomcat server.xml.
There are a number of articles about OkHttp using HTTPS, but most of them only describe how to configure HTTPS on the front end, not how to deploy it on the back end. Please refer to this article, which includes Tomcat configuration tutorials.
8.3 Configuring File Encryption
The configuration file is encrypted by the jasypt-spring-boot open-source component. The password can be set in three ways:
- Command line arguments
- Application environment variables
- System environment variable
At present, the latest version is 3.0.3 (2020.05.31 updated 3.0.3. When the author used 3.0.2 version for encryption before, there was no problem in the local test, but when deployed to the server, there was always a message that the password could not be found, so we had no choice but to use the older 2.x version. However, after the new version was released, I tried to deploy Tomcat to the local server. There was no problem, but Tomcat was not deployed to the server.) It is recommended to deploy the latest version:
After all, the span is quite large, although this is a small bug fix, but IT is still recommended to try, it is estimated that there will not be a 3.0.2 problem.
In addition, remember to encode and convert fields containing Chinese:
str = new String(str.getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8);)
Copy the code
In addition, the author has written the test file, directly replace the original ciphertext of the configuration file first, and fill in the plaintext to encrypt again:
Note if there is no set in the configuration file jasypt encryptor. If the password can be set in the run configuration VM Options (not recommended write password directly in the configuration file and, of course, the default is to use PBE encryption, Asymmetric encryption can be used with jasypt.encryptor.private-key-string or jasypt.encryptor.private-key-location) :
8.4 Keyboard Events
Keyboard events can be added using the following code:
scene.getAccelerators().put(new KeyCodeCombination(KeyCode.ENTER), ()->{xxx});
//getAccelerators returns ObservableMap
,>
Copy the code
Parent needs to get focus before responding:
parent.requestFocus();
Copy the code
8.5 database
MySQL > alter database app_test (test_user); MySQL > alter database test_password (test_password);
8.6 authentication code
By default, there is no built-in verification code function, which is not open due to privacy issues.
If you use Tencent cloud SMS API like the author, directly modify the corresponding attributes in the configuration file, it is recommended to encrypt.
If you use other apis, interconnect them by yourself. The front-end components need to be modified include:
com.test.network.OKHTTP
com.test.network.request.SendSmsRequest
com.test.network.requestBuilder.SendSmsRequestBuilder
com.test.controller.start.RetrievePasswordController
The backend needs to be modified:
com.test.controller.SmsController
If necessary, you can refer to the author’s Tencent cloud SMS API or search for other SMS verification API. Some apis written in configuration files require strong information such as keys
Nine source
Front and back end complete code and packaging:
- Github
- Yards cloud
10 Deficiencies of the project
In fact, the whole project still has many shortcomings, such as:
- Part of the front end Scene switch is faulty
- You can use Jackson instead of Gson in exchange for faster conversion speed
- No caching mechanism
- Front-end logs cannot be sent to the back-end for analysis
- You can use binary instead of JSON for faster transport
However, update is not considered at present. If there are readers who have their own ideas, they can modify as needed. Here are the ideas for modification.
11 reference
1. Introduction and use of CSDN-Maven-shade-plugin
2. Use of maven-assembly-plugin, one of the three packaging methods of CSDN-MAVEN
3. Zhihu – Make JRE including Java 11 and JavaFX
4. CSDN- Use Exe4J to convert Java files into exe files to run the detailed tutorial
5, making – jasypt – spring – the boot issue
6, w3cschool – deployment headaches
7. Linux Tomcat+Openssl one-way/two-way authentication
If you think the article looks good, please like it.
At the same time, welcome to pay attention to wechat public number: Lingzhi Road.