[TOC]
Scarecrow figure bed artifact
The server is now expired and the project cannot be accessed
Source code address:
Little background source https://github.com/w77996/mini-straw https://github.com/w77996/hi-straw program source codeCopy the code
Experience the
On the one hand, I practiced a small program, but didn’t apply it much after learning it. On the other hand, I wrote some notes. The project has been done intermittently for two months
Small program
- Small program authorization, login
- The parent component communicates with the child component
- Small program sharing, comments and suggestions, customer service functions, file upload
- Use of flex layout
- Use of Promise, business Model encapsulation
- Slot use for animation effects
Project directory structure
Mini - straw ├ ─ ─ component -- component | ├ ─ ─ the file - the file component | ├ ─ ─ image - button - image button component | ├ ─ ─ search - search page components | ├ ─ ─ the tag label components ├ ─ ─ images, the images directory | ├ ─ ─ icon, the icon image | ├ ─ ─ TAB, the TAB images ├ ─ ─ model - encapsulated model ├ ─ ─ pages - page | ├ ─ ─ the about - | about page ├ ─ ─ auth - authorization page | ├ ─ ─ the file - page file | ├ ─ ─ the index - page | ├ ─ ─ launch - start page | ├ ─ ─ my - personal center └ ─ ─ utils - toolsCopy the code
Background:
Spring Boot + Druid + Mybatis + JWT
- Wechat login, JWT authorization
- Annotations and the use of AOP
- Maven multi-environment package, docker use
- Automatic shell script deployment
- Nginx reverse proxy and HTTPS configuration
- Seven niuyun file operation
Project directory structure
Hi - straw ├ ─ ─ common - public module ├ ─ ─ the config - configuration module ├ ─ ─ the controller, the controller interface ├ ─ ─ the core, the core business module | ├ ─ ─ annontaion - annotations | ├ ─ ─ aop, aop implementation | ├ ─ ─ constant, constant | ├ ─ ─ the filter - interceptor | ├ ─ ─ JWT, JWT related | └ ─ ─ result - results back ├ ─ ─ the entity, the entity classes | ├ ─ ─ dto - data transmission | └ ─ ─ vo - page transmission ├ ─ ─ the exception - exception ├ ─ ─ mapper, dao layer ├ ─ ─ the service, the service layer └ ─ ─ util - toolsCopy the code
Small program details
[TOC]
mini-straw
The project structure
Mini - straw ├ ─ ─ component -- component | ├ ─ ─ the file - the file component | ├ ─ ─ image - button - image button component | ├ ─ ─ search - search page components | ├ ─ ─ the tag label components ├ ─ ─ images, the images directory | ├ ─ ─ icon, the icon image | ├ ─ ─ TAB, the TAB images ├ ─ ─ model - encapsulated model ├ ─ ─ pages - page | ├ ─ ─ the about - | about page ├ ─ ─ auth - authorization page | ├ ─ ─ the file - page file | ├ ─ ─ the index - page | ├ ─ ─ launch - start page | ├ ─ ─ my - personal center └ ─ ─ utils - toolsCopy the code
Opening the page
1. Check the network status
Wx.getnetworktype ({}) : wifi/ 2G / 3G / 4G/Unknown/None
Wx. getNetworkType({success: res => {if (res.networkType == "none") {
wx.showToast({
title: 'Ow ~~ Network is not available',
icon: 'none',
duration: 2000
})
return; }}})Copy the code
2. Check the entitlement status
Use wx.getSetting({}) to get authorization status, and fetch data.authSetting[‘ scope.userinfo ‘] to determine authorization status after obtaining data
Wx.getsetting ({success: data => {if (data.authSetting['scope.userInfo'Wx.getuserinfo ({success: data => {console.log())"userInfo {}", data)
let userInfo = data.userInfo;
wx.setStorageSync('userInfo', userInfo); This. _userLoginGetCode(userInfo); }}); wx.setStorageSync('authorized'.true);
} else {
console.log("Not authorized") // Jump to the authorization pagelet timer = setTimeout(() => {
wx.redirectTo({
url: '/pages/auth/auth'})}, 2000)}}});Copy the code
If authorized, wx.getUserInfo({}) is called to obtain the wechat user information. After obtaining the information, wx.login({}) is called to obtain the code of the applet, through which the openId and token of the user are obtained from the background.
// get code _userLoginGetCode(userInfo) {console.log()"Initiate _userLoginGetCode request");
wx.login({
success(res) {
console.log("wx.login {}", res);
if(res.code) {// Initiate network request const code = res.code; userInfo.code = code; userModel.getTokenByCode(userInfo).then((res) => { console.log("userModel getUserInfo {}", res);
wx.setStorageSync("token", res.data.data.token);
let timer = setTimeout(() =>
wx.switchTab({
url: '/pages/index/index',}), 2000)}); }else {
console.log('Login failed! ' + res.errMsg)
}
}
})
},
Copy the code
3. Go to the page
- jump
/pages/auth/auth
The page useswx.redirectTo({})
- jump
/pages/index/index
The page useswx.switchTab({})
because/pages/index/index
Is a small program TAB page, usewx.redirectTo({})
Can’t jump
Authorization page
Authorization needs to make button button, add open-type=’getUserInfo’ attribute, bindGetUserInfo calls custom method onGetUserInfo.
<button class="auth-button" open-type='getUserInfo' bindgetuserinfo="onGetUserInfo"> good < / button >Copy the code
OnGetUserInfo Receives the authorization status and obtained user information and obtains the user openId and token from the background using the code.
onGetUserInfo: function(e) {
console.log(e)
const userInfo = e.detail.userInfo;
if(userInfo) {// Obtain the user openId and token from the background through 'code'. this._userLoginGetCode(userInfo); }},Copy the code
The home page
1. Picture button slot component
The images-button component in the Component directory has simple image slots that can be used for share buttons, user login buttons, and file upload buttons. Plain =”{{true}}” means button background transparent
<button open-type="{{openType}}" plain="{{true}}" class="container">
<slot name="img"></slot>
</button>
Copy the code
Add multipleSlots: True open-type=”{{openType}}” The parent component passes the parameter to the child component, which can get openType data from the parent component in the properties property. The this.properties. OpenType can get the property value
Options: {// Open slot multipleSlots:true}, /** * component property list */ properties: {openType: {type: String
}
},
Copy the code
The index page introduces the component. You need to add the component path to the index.json file
{
"usingComponents": {
"btn-cmp": "/component/image-button/index"}}Copy the code
2. Upload files
Wx.chooseimage ({}) is used to select images, and wx.uploadfile ({}) is used to upload images to the server
OnUpload (event) {let_this = this; Wx. chooseImage({count: 1, // default 9 sizeType: ['original'.'compressed'], // You can specify whether the image is original or compressed. By default, both are availablesourceType: ['album'.'camera'], // You can specify whether the source is photo album or camera, and default to both success:function(res) {// Returns a list of local file paths for the selected photo. TempFilePath can display the image as the SRC attribute of the IMG taglet tempFilePaths = res.tempFilePaths;
console.log(tempFilePaths)
wx.uploadFile({
header: {
"Authorization": "Bearer " + wx.getStorageSync("token")
},
url: config.apiBaseUrl + '/file/upload',
filePath: tempFilePaths[0],
name: 'file',
success: (res) => {
wx.showToast({
title: "Upload successful ~",
icon: 'none',
duration: 2000
})
},
fail: (res) => {
wx.showToast({
title: res.data.msg,
icon: 'none',
duration: 2000
})
}
})
}
})
}
Copy the code
List of pp.
1. Display and hide the Search component
Fixed list page search header position, click header to display the search component, click cancel in the search component to hide the search component, here design child component to transmit messages to the parent component
- Introducing the Search component
"usingComponents": {..."search-cmp": "/component/search/index"
}
Copy the code
- Use the searchPage parameter to determine
search
Component, which defaults to false and updates the searchPage to true when clicking the header, to displaysearch
component
<view wx:if="{{! searchPage}}" class="container">... </view> <search-cmp wx:if="{{searchPage}}" ></search-cmp>
Copy the code
search
Click Cancel to send one to the parentthis.triggerEvent('cancel', {}, {});
Event, in XMLsearch-cmp
addcancel
Notification binding for events
The search-CMP component in the #file page
<search-cmp wx:if="{{searchPage}}" bind:cancel="onCancel"></search-cmp>
Copy the code
The parent component file page binds the cancel event notification from the child component, calls the onCancel method, gets the event response from the onCancel method, changes the searchPage parameter to false, and hides the search component
//cancel searching page
onCancel(event) {
console.info(event)
this.triggerEvent('cancel', {}, {});
},
Copy the code
2. File list
1. Get the list information and pass it to the File component
File =”{{item}}”; fileList =”{{item}}”
<view wx:if="{{fileList}}">
<block wx:for="{{fileList}}" wx:key="{{item.id}}" file="{{item}}">
<file-cmp file="{{item}}" bind:del="onDelete"></file-cmp>
<view class="line"></view>
</block>
</view>
Copy the code
To the File component in Component, add the property File to properties to receive data from the parent component
/** */ properties: {file: Object}Copy the code
The file component uses {{file.filename}} in the XML page to retrieve object information and render the corresponding data on the page
3. Paste board operations
<image src="images/copy.png" bindtap="onCopy"></image>
Copy the code
Click on the response method onCopy, which calls wx.setclipBoardData ({}) to copy the data to the stickboard
onCopy: function (event) {
console.info(event)
let _this = this;
wx.setClipboardData({
data: _this.properties.file.filePath,
success: function(res) {
wx.showToast({
title: 'Image address copied successfully'}}}));Copy the code
4. Delete the vm
<image src="images/del.png" bindtap="onDelete"></image>
Copy the code
This. TriggerEvent (‘del’, {fileId}, {}); Send the file ID to the parent component
onDelete: function (event) {
console.info(event)
let fileId = this.properties.file.id;
this.triggerEvent('del', {fileId}, {});
},
Copy the code
The parent component file page binds del events from the child component
<file-cmp file="{{item}}" bind:del="onDelete"></file-cmp>
Copy the code
Call onDelete to initiate a network request to complete the logic of deleting files, and refresh the file list after successful deletion
OnDelete (event) {console.info()"DEL")
console.info(event)
let fileId = event.detail.fileId;
fileModel.delFileById(fileId).then((res) => {
console.info(res);
wx.showToast({
title: 'Deleted successfully', }) this.setData({ pageNum: 1, fileList: [] }); this._getFileList(); })},Copy the code
My page
1. Comments and suggestions
Small program with user feedback function, use button to jump to the web page, users can fill in relevant feedback,open-type set to feedback
<button class="about-btn" plain="true" open-type="feedback">
<text class="about-btn-text"</button> </button>Copy the code
2. Small program customer service
The open-type in button of the mini program has the ability of opening. Enable the customer service function in the wechat public platform, add customer service personnel, add button in the code to pop up the customer service chat interface, and set the open-type to contact
<button class="about-btn" plain="true" open-type="contact" bindcontact="handleContact">
<text class="about-btn-text"</button> </button>Copy the code
3. Small program sharing
Slot is used here, and open-type in button is set to share
<btn-cmp open-type="share">
<image slot="img" src="images/share.png" />
</btn-cmp>
Copy the code
animation
Set opacity of text from 0 to 1 to gradually display, mainly using opacity to set component transparency. Create an animationTip that lasts for 800ms, and then set the animation time in setTimeout(function () {})
Var animationTip = wx.createAnimation({// duration: 800, timingFunction:'ease'}); this.animationTip = animationTip; animationTip.opacity(0).step() this.setData({ animationTip: animationTip.export() })setTimeout(function () {
animationTip.opacity(1).step()
this.setData({
animationTip: animationTip.export()
})
}.bind(this), 500)
Copy the code
The deployment of
- Modify the
utils
In the directoryconfig.apiBaseUrl
, change it to its own domain name, upload it to the wechat public account platform, and publish it in version management
const config ={
apiBaseUrl: "Your own domain name or server address"
}
Copy the code
Details of background functions
hi-straw
The project structure
Hi - straw ├ ─ ─ common - public module ├ ─ ─ the config - configuration module ├ ─ ─ the controller, the controller interface ├ ─ ─ the core, the core business module | ├ ─ ─ annontaion - annotations | ├ ─ ─ aop, aop implementation | ├ ─ ─ constant, constant | ├ ─ ─ the filter - interceptor | ├ ─ ─ JWT, JWT related | └ ─ ─ result - results back ├ ─ ─ the entity, the entity classes | ├ ─ ─ dto - data transmission | └ ─ ─ vo - page transmission ├ ─ ─ the exception - exception ├ ─ ─ mapper, dao layer ├ ─ ─ the service, the service layer └ ─ ─ util - toolsCopy the code
Database design
CREATE TABLE `t_straw_file` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` int(11) UNSIGNED DEFAULT NULL COMMENT 'user ID',
`file_path` varchar(255) DEFAULT NULL COMMENT 'File path',
`file_name` varchar(100) DEFAULT NULL COMMENT 'File name',
`file_size` varchar(10) DEFAULT NULL COMMENT 'File size',
`props` varchar(255) DEFAULT NULL,
`status` tinyint(5) UNSIGNED DEFAULT '0' COMMENT '0. Normal -1. Delete ',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT'Creation time',
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4 COMMENT 'User File List';
Copy the code
CREATE TABLE `t_straw_user` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_name` varchar(20) DEFAULT NULL COMMENT 'Username',
`nickname` varchar(20) DEFAULT NULL COMMENT 'User name',
`user_logo` varchar(250) DEFAULT NULL COMMENT 'the user logo',
`phone_num` varchar(20) DEFAULT NULL COMMENT 'Mobile phone Number',
`open_id` varchar(55) DEFAULT NULL COMMENT 'WeChat openId',
`union_id` varchar(20) DEFAULT NULL COMMENT 'WeChat union_id',
`password` varchar(50) DEFAULT NULL COMMENT 'password',
`uuid` varchar(20) DEFAULT NULL COMMENT 'Custom generated UUID',
`last_login` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'Last landing Time',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation time',
PRIMARY KEY (`id`),
UNIQUE KEY `open_id` (`open_id`) USING HASH
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COMMENT 'User table';
Copy the code
CREATE TABLE `t_straw_user_file_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) UNSIGNED NOT NULL COMMENT 'user ID',
`file_size` int(11) UNSIGNED DEFAULT '0' COMMENT 'User file size',
`left_size` int(11) UNSIGNED DEFAULT '5242880' COMMENT 'Remaining file size',
`total_size` int(11)UNSIGNED DEFAULT '5242880' COMMENT 'User file size',
`file_num` int(11) UNSIGNED DEFAULT '0' COMMENT 'Number of files',
`is_vip` tinyint(5) UNSIGNED DEFAULT '0' COMMENT 'VIP,1. Yes 0. No',
PRIMARY KEY (`id`),
UNIQUE KEY `user_id` (`user_id`) USING HASH
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8mb4 COMMENT'User File Information';
Copy the code
CREATE TABLE `t_straw_user_info` (
`user_id` int(11) UNSIGNED NOT NULL COMMENT 'user ID',
`sex` tinyint(5) UNSIGNED DEFAULT NULL COMMENT 'gender',
`location` varchar(55) DEFAULT NULL COMMENT 'location',
`platform` varchar(55) DEFAULT NULL COMMENT 'platform',
`birthday` datetime DEFAULT NULL COMMENT 'birthday',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT 'User Details table';
Copy the code
Code,
preparation
- Linux server and MySql database
- Seven Niuyun account, you can go to seven Niuyun official website to apply for an account
- HTTPS certificate Aliyun obtains a free certificate
- Wechat public platform small program developer account
Seven niuyun file upload
Code com. W77996. Straw. Util. QiNiuUtil
1. Qiniu Cloud account application
Apply for an account on qiuniuyun official website, obtain AccessKey and SecretKey, and set qiuniuyun picture bucket
/** * accessKey */ @value ("${QiNiu.accessKey}") private String accessKey; /** * @value ("${QiNiu.secretKey}") private String secretKey; /** * bucket */ @value ("${QiNiu.bucket}")
private String bucket;
Copy the code
2. Qiniuyun SDK was introduced
Pom. XML file is introduced into the qiuniuyun warehouse
< the dependency > < groupId > com. Qiniu < / groupId > < artifactId > qiniu - Java - SDK < / artifactId > < version > [7.2.0, 7.2.99] < / version > < / dependency >Copy the code
2. Qiuniuyun token generation
/** ** a token is created ** @param fileName * @return
*/
public QiNiuAuth generateToken(String userId, String fileName) {
Auth auth = Auth.create(accessKey, secretKey);
String key = "upload/file/000/" + userId + "/" + fileName;
StringMap putPolicy = new StringMap();
putPolicy.put("returnBody"."{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"fsize\":$(fsize)}");
long expireSeconds = 3600;
String upToken = auth.uploadToken(bucket, key, expireSeconds, putPolicy);
Map<String, String> resultMap = Maps.newHashMapWithExpectedSize(3);
resultMap.put("domain"."https://www.w77996.cn");
resultMap.put("key", key);
resultMap.put("upToken", upToken);
return new QiNiuAuth("https://www.w77996.cn", key, upToken);
}
Copy the code
3. Wrote codes for uploading files
** @param file * @param key * @param token * @return*/ public String uploadImage(MultipartFile file, String key, String token) { Configuration cfg = new Configuration(Zone.zone2()); UploadManager uploadManager = new UploadManager(cfg); String filePath = null; // Generate upload credentials with file contents without specifying keyhashValue as filename Response Response = null; try { byte[] uploadBytes = file.getBytes(); Auth auth = Auth.create(accessKey, secretKey); String upToken = auth.uploadToken(bucket); try { response = uploadManager.put(uploadBytes, key, upToken); DefaultPutRet putRet = new Gson().fromjson (response.bodyString(), defaultputret.class); log.info("Upload result {} {}", putRet.hash, putRet.key);
filePath = putRet.key;
} catch (QiniuException ex) {
try {
response = ex.response;
log.error(response.bodyString());
} catch (QiniuException ex2) {
//ignore
ex.printStackTrace();
}
}
} catch (Exception ex) {
//ignore
ex.printStackTrace();
}
return filePath;
}
Copy the code
4. Delete pictures
/** @param key */ public void delete(String key) {Configuration CFG = new Configuration(zone2()); Auth auth = Auth.create(accessKey, secretKey); BucketManager BucketManager = new BucketManager(auth, CFG); Try {// Call the delete method to move bucketManager.delete(bucket, key); } catch (QiniuException e) {throw new GlobalException(resultcode.error); }}Copy the code
Annotation +AOP interface limiting
1. Annotation writing
Code com. W77996. Straw. Core. The annotation. The Limiter
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limiter {
/**
*
* @return
*/
String value() default ""; /** * The default maximum number of tokens to be added to the bucket per second is no flow limiting * @return*/ double perSecond() default Double.MAX_VALUE; /** * The waiting time for retrieving the token defaults to 0 * @return*/ int timeOut() default 0; /** * Timeout period unit * @return
*/
TimeUnit timeOutUnit() default TimeUnit.MILLISECONDS;
}
Copy the code
2. AOP implementation
Code com. W77996. Straw, the core aop. RateLimitAspect
@Aspect @Component @Slf4j public class RateLimitAspect { private RateLimiter rateLimiter = RateLimiter.create(Double.MAX_VALUE); /** * Pointcut(@pointcut);"@annotation(com.w77996.straw.core.annotation.Limiter)")
public void checkPointcut() {
}
@ResponseBody
@Around(value = "checkPointcut()")
public Object aroundNotice(ProceedingJoinPoint pjp) throws Throwable {
log.info("Intercepting {} method...", pjp.getSignature().getName()); Signature signature = pjp.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; / / get the target Method Method targetMethod = methodSignature. GetMethod ();if(targetMethod isAnnotationPresent (Limiter. Class)) {/ / to get the target method @ Limiter annotation Limiter Limiter = targetMethod.getAnnotation(Limiter.class); rateLimiter.setRate(limiter.perSecond());if(! rateLimiter.tryAcquire(limiter.timeOut(), limiter.timeOutUnit())) { log.info("rateLimiter lock");
returnResult.error(ResultCode.BUSY); }}returnpjp.proceed(); }}Copy the code
3. Use of annotations
Only one call per second is allowed, and if this is exceeded, resultcode.busy is returned.
@GetMapping("/limit") @limiter (perSecond = 1.0, timeOut = 500) public StringtestLimiter() {
return " success";
}
Copy the code
JWT implementation
1. JWT generated
Generate JWT tokens using JwtUtil
/** * generate JWT ** @param userId * @return
*/
public static String createJWT(String userId) {
String token = JwtHelper.createJWT(userId, Constant.JWT_CLIENT_ID,
Constant.JWT_NAME, Constant.JWT_EXPIRES_SECOND, Constant.JWT_BASE64_SECRET);
return token;
}
Copy the code
2. The token is resolved to userId
By putting the userId into the token, you can retrieve the Bearer through the request Header when requesting the interface{token} decodes to obtain the userId.
/** * Obtain user information by token ** @return
*/
public String getUserIdByToken() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String accessToken = request.getHeader("Authorization");
if (StringUtils.isEmpty(accessToken) || accessToken.length() < 20) {
throw new GlobalException(ResultCode.ERROR_TOKEN_NULL);
}
accessToken = accessToken.substring(7);
if ("admin".equals(accessToken)) {
return "1";
}
Claims claims = JwtHelper.parseJWT(accessToken, Constant.JWT_BASE64_SECRET);
return claims.getSubject();
}
Copy the code
3. Use interceptor and annotations for token authentication
Code com. W77996. Straw. Core. The annotation. IgnoreToken first set to ignore token annotations
/** * @description: override token * @author: w77996 **/ @Retention(RetentionPolicy.RUNTIME) @Target(value={ElementType.METHOD,ElementType.TYPE}) public @interface IgnoreToken { }Copy the code
Code com. W77996. Straw. Core. Filter. TokenFilter interceptor HandlerInterceptor TokenFilter implementation, Intercepts every time a request comes in. PerHandle is called before the Controller is called, so get an annotation of the method name in perHandler, check for ignoreToken annotations, and then do JWT validation.
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
IgnoreToken ignoreToken = handlerMethod.getBeanType().getAnnotation(IgnoreToken.class);
log.info("enter preHandle {}",request.getRequestURL());
if (ignoreToken == null) {
ignoreToken = handlerMethod.getMethodAnnotation(IgnoreToken.class);
}
if(ignoreToken ! = null) { log.info("ignoreToken not null");
return true;
}
log.info("ignoreToken null");
String token = request.getHeader("Authorization");
if(token ! = null){ log.info("token is {}",token);
if ("admin".equals(token.substring(7))) {
return true;
}
Claims claims = JwtHelper.parseJWT(token.substring(7), Audience.BASE64SECRET);
if(claims ! = null){ log.info("claims is {} {}",claims.toString(),claims.getSubject());
return true;
}else{
log.info("claims is null"); throw new GlobalException(ResultCode.ERROR_AUTH); }}return false;
}
Copy the code
4. Implement web interceptors
Code com. W77996. Straw. Config. WebMvcAdapterConfig don’t intercept/druid / * interfaces
/** * @description: web interceptor * @author: w77996 **/ @Component public class WebMvcAdapterConfig extends WebMvcConfigurationSupport { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new TokenFilter()).excludePathPatterns("/druid/*"); }}Copy the code
Druid Monitoring configuration
Code com. W77996. Straw. Config. DruidConfig project after the operation to http://ip:port/druid, input the admin account password amdin can access
@Configuration
public class DruidConfig {
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource druidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
return dataSource;
}
@Bean
public ServletRegistrationBean druidStatViewServlet() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); / / IP white list (no configuration or empty, allows all access) e registrationBean. AddInitParameter ("allow".""); When there is a common / / IP blacklist (deny prior to allow) registrationBean. AddInitParameter ("deny"."");
registrationBean.addInitParameter("loginUsername"."admin");
registrationBean.addInitParameter("loginPassword"."admin");
registrationBean.addInitParameter("resetEnable"."false");
return registrationBean;
}
@Bean
public FilterRegistrationBean druidWebStatViewFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(new WebStatFilter());
registrationBean.addInitParameter("urlPatterns"."/ *");
registrationBean.addInitParameter("exclusions"."*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*");
returnregistrationBean; }}Copy the code
Global exception interception
Global Exception interception mainly relies on the @RestControllerAdvice annotation. The @ExceptionHandler(value = exception.class) is used to represent all exceptions in the method and then perform the corresponding operation
@restControllerAdvice Public class GlobalExceptionHandler {/** * global error interception ** @param e * @return
*/
@ExceptionHandler(value = Exception.class)
private Result<Object> exceptionHandler(Exception e) {
if (e instanceof GlobalException) {
GlobalException ex = (GlobalException) e;
return Result.error(ex.getCode());
}
returnResult.error(ResultCode.ERROR.getCode(),e.getMessage()); }}Copy the code
WeChat login
It is necessary to obtain the corresponding appId and appSec on wechat public platform, and then send the code to the background. After obtaining the code, the background sends the HTTP request to wechat, using the restTemplate, but it is necessary to pay attention to the code. Iso-8859-1 is returned by wechat. After the successful call, the openId of the user can be obtained, and then the corresponding user information can be obtained from the database for logon update and user creation
@RestController
@RequestMapping("/wx")
@Slf4j
public class WxController {
@Autowired
private IUserService iUserService;
@Value("${wx.appId}")
private String wxAppId;
@Value("${wx.appSec}") private String wxAppSec; /** * Get openId ** @param wxLoginDto * @return
*/
@IgnoreToken
@PostMapping("/code")
public Result getUserInfoByCode(@RequestBody WxLoginDto wxLoginDto) {
log.info("enter getUserInfoByCode"); OpenId String reqUrl ="https://api.weixin.qq.com/sns/jscode2session?appid=" + wxAppId + "&secret=" + wxAppSec + "&js_code=" + wxLoginDto.getCode() + "&grant_type=authorization_code";
JSONObject wxAuthObject = RestHttpClient.client(reqUrl, HttpMethod.GET, null);
log.info("wxAuthObject {}", wxAuthObject.toJSONString());
WxTokenDto wxTokenDto = JSONObject.parseObject(wxAuthObject.toJSONString(), WxTokenDto.class);
log.info("wxTokenDto {}", wxTokenDto.toString()); Map<String, Object> tokenMapper = Maps.newHashMapWithExpectedSize(2); / / generate new user UserEntity UserEntity = iUserService. GetUserByOpenId (wxTokenDto. GetOpenid ());if(! ObjectUtils.allNotNull(userEntity)) { WxUserInfoDto wxUserInfoDto = new WxUserInfoDto(); wxUserInfoDto.setNickname(wxLoginDto.getNickName()); wxUserInfoDto.setUserLogo(wxLoginDto.getUserLogo()); wxUserInfoDto.setSex(wxLoginDto.getSex()); wxUserInfoDto.setLastLogin(new Date()); wxUserInfoDto.setOpenId(wxTokenDto.getOpenid()); wxUserInfoDto.setLocation(StringUtils.join(new String[]{wxLoginDto.getCountry(), wxLoginDto.getProvince(), wxLoginDto.getCity()},"-"));
iUserService.createNewUser(wxUserInfoDto);
log.info("create new user {}", wxUserInfoDto);
}
tokenMapper.put("token", JwtHelper.createJWT(userEntity.getId() + ""));
returnResult.success(tokenMapper); }}Copy the code
Spring Boot + Maven multi-environment package
1. Yml file in resouce
The project environment is divided into dev and prod. Application. Yml is loaded in the resource file by default.
- Dev environment: application-dev.yml
- Prod environment: application-prod.yml in
application.yml
In the
spring:
profiles:
active: @spring.profiles.active@
Copy the code
Active @ corresponds to the spring.profiles. Active attribute under profiles in pom. XML
2. Pom. The XML configuration
By default, the configuration information in the DEV environment is used
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault> </activation> <properties> <! -- default Spring profiles --> <spring.profiles.active>dev</spring.profiles.active> </properties> </profile> <profile> <id>prod</id> <properties> <! -- default Spring profiles --> <spring.profiles.active>prod</spring.profiles.active> </properties> </profile> </profiles>Copy the code
3. Pack for different environments
- packaging
prod
Environment: Executionmvn package -Pprod -DskipTests
- packaging
dev
Environment: Executionmvn package -Pdev -DskipTests
4. Package and name the project
Add the time format to the properties property, and then add fileName to the build format fileName.
- straw < artifactId > hi < / artifactId > < version > 1.0.0 < / version > < properties >... <maven.build.timestamp.format>yyyy-MM-ddHHmm</maven.build.timestamp.format> </properties> <build> ... <finalName>${project.artifactId}-${project.version}-${spring.profiles.active}_${maven.build.timestamp}
</finalName>
</build>
Copy the code
Jar generated after packaging: hi-straw-1.0.0-prod_2019-04-091533.jar
Shell scripting
Clone the project to /root/repo_git/, go to script, and run./build.sh. Change RELEASE_HOST to your own server address for easy backup
#! /bin/sh
set -e
Package server address
RELEASE_HOST="Your own server address"
# Packaged environment
RELEASE_ENV=prod
# Project Catalog
BASE_DIR=/root/repo_git/Histraw
Enter the project directory
cd ${BASE_DIR}
Execute git to pull the latest code
echo "pulling changes..."
git pull origin master
echo "pulling changes... finish"
echo "building..."
Run the MVN command to pack
mvn clean
mvn package -P${RELEASE_ENV} -DskipTests docker:build
echo "building... finish"
echo "env =${RELEASE_ENV}"
#for HOST in ${RELEASE_HOST}; do
Copy and back up
RELEASE_TARGET=root@${RELEASE_HOST}:~/release/
echo "copying to $RELEASE_TARGET..."
scp ${BASE_DIR}/target/*.jar ${RELEASE_TARGET}
echo "copying to $RELEASE_TARGET. done"
#done
Copy the code
docker images
Maven + Docker package deployment
1. Docker environment installation
Uninstall an older version (skip this step if it has not been installed) :
sudo apt-get remove docker docker-engine docker.io
Copy the code
Install the latest docker:
curl -fsSL get.docker.com -o get-docker.sh
sudo sh get-docker.sh
Copy the code
Confirm successful installation of Docker:
docker run hello-world
Copy the code
2. Project compilation and packaging
Create dockerFile under SRC /main/docker
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD *.jar app.jar
ENTRYPOINT ["java"."-Djava.security.egd=file:/dev/./urandom"."-jar"."/app.jar"]
Copy the code
Pom.xml configuration docker packaging, with shell script in Linux to achieve maven automatic packaging docker
<! -- Docker maven plugin --> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> The < version > 1.0.0 < / version > < configuration > < imageName >${project.artifactId}</imageName>
<dockerDirectory>src/main/docker</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin> <! -- Docker maven plugin -->Copy the code
Execute Docker Images to view the docker image you just packaged
Run docker run –name hi-straw -p 8989:8989 -t hi-straw to start the image
Execute Dockers ps to view the docker image that has been started
Nginx configuration HTTPS
1. Install nginx
Log in to the server and execute
$apt-get install nginx $apt-get install nginxCopy the code
2. Obtain the certificate
You can go to Aliyun to obtain a free certificate and put the generated certificate in /etc/nginx/sites-enabled/cert/(depending on which directory you install nginx in).
3. Configure the nginx file
Create an HTTPS. Conf file
server { listen 443; Server_name Your own domain name; ssl on; Ssl_certificate cert/ your own certificate. Ssl_certificate_key cert/ Your own certificate. ssl_session_timeout 5m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:! NULL:! aNULL:! MD5:! ADH:! RC4; Ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; location / {# IP and port for project deploymentproxy_pass http://localhost:port; }}Copy the code
After the configuration is complete, check to see if the nginx configuration file is available
Nginx -t check the nginx configuration fileCopy the code
After the configuration is correct, reload the configuration file for the configuration to take effect
Nginx -s reload // Enables the configuration to take effectCopy the code
To restart nginx, run the following command:
Service nginx stop service nginx start service nginx restart // RestartCopy the code
The deployment of
Yml/application-dev. Yml/application-prod. Yml/application-dev
# 7 cattleSecretKey: SEC bucket: Your request for bucket Domain: your request for bucket Domain# WeChatWx: appId: wechat ID you applied for appSec: wechat SEC you applied forCopy the code
Spring: datasource: name: graduate driver-class-name: com.mysql.jdbc. driver url: database address username: database username password: database passwordCopy the code
Modify build.sh in the script directory
RELEASE_HOST="Your own server address"
Copy the code
The project’s servers expire on July 15th… I’d appreciate someone funding the server
donations