')
$table. append ( $tr)
$tr. append ( ` ${ user. name} ` )
$tr. append ( ` ${ user. age} ` )
}
} )
} )
} )
script>
Layui
官网:http://layuimini.99php.cn/
文档:http://layuimini.99php.cn/docs/
演示:http://layuimini.99php.cn/onepage/v2/index.html
Github:https://github.com/zhongshaofa/layuimini/tree/v2-onepage
下载:https://codeload.github.com/zhongshaofa/layuimini/zip/refs/heads/v2-onepage
注意 :Layui中的相对路径都是相对于index.html来说的。
注意 :layuimini的表格要求服务器返回的JSON数据格式如下:
{
"code" : 0 ,
"data" : [
{
"id" : 1 ,
"name" : "职业" ,
"value" : "job" ,
"intro" : "一份工作"
} ,
{
"id" : 2 ,
"name" : "性格" ,
"value" : "character" ,
"intro" : "人的性格"
}
] ,
"count" : 87
}
MySQL建议
最大IP地址:255.255 .255 .255
CREATE TABLE user (
age TINYINT UNSIGNED ,
ip VARCHAR ( 15 )
)
CREATE TABLE user (
age TINYINT UNSIGNED ,
ip INT UNSIGNED
)
INSERT INTO user VALUES ( 10 , INET_ATON( '255.255.255.255' ) )
SELECT INET_NTOA( ip) FROM user
注意:
数据字典
使用PowerDesigner:
drop table if exists dict_item;
drop table if exists dict_type;
create table dict_item
(
id bigint not null ,
name varchar ( 20 ) not null ,
value varchar ( 20 ) not null ,
no int not null default 0 comment '用来排序,数字越小,优先级越高,越先展示' ,
type_id bigint comment '该条目所属的数据字典类型' ,
status int not null default 1 comment '是否启用该条目,0:不启用,1:启用' ,
primary key ( id) ,
unique key AK_UK_1 ( name, type_id) ,
unique key AK_UK_2 ( value , type_id)
) ;
alter table dict_item comment '数据字典每一项具体的内容' ;
create table dict_type
(
id bigint not null auto_increment ,
name varchar ( 20 ) not null comment '名称是展示在客户端的,是有可能会发生改变的' ,
value varchar ( 20 ) not null comment '值不会发生改变,编写SQL操作数据时,一般使用value而不是name' ,
intro varchar ( 100 ) comment '防止程序员忘记该数据字典类型的作用、功能(根据项目需求可有可无)' ,
primary key ( id) ,
unique key AK_UK_1 ( name) ,
unique key AK_UK_2 ( value )
) ;
alter table dict_type comment '数据字典类型' ;
alter table dict_item add constraint FK_Reference_1 foreign key ( type_id)
references dict_type ( id) on delete restrict on update restrict ;
使用IDEA自带的数据库工具+不使用外键+数据库使用最优字段类型:
create table jk. dict_type
(
id smallint unsigned auto_increment comment '主键'
primary key ,
name varchar ( 20 ) default '' not null comment '名称' ,
value varchar ( 20 ) default '' not null comment '值' ,
intro varchar ( 100 ) default '' not null comment '简介' ,
constraint dict_type_name_uindex
unique ( name) ,
constraint dict_type_value_uindex
unique ( value )
)
comment '数据字典类型' ;
create table jk. dict_item
(
id smallint unsigned auto_increment comment '主键'
primary key ,
name varchar ( 20 ) default '' not null comment '名称' ,
value varchar ( 20 ) default '' not null comment '值' ,
type_id smallint unsigned not null comment '类型id' ,
sn smallint unsigned default 0 not null comment '排序序号:默认为0,值越大,越优先排列展示' ,
enabled tinyint unsigned default 1 not null comment '是否启用:0,禁用;1,启用;默认为1' ,
constraint dict_item_name_type_id_uindex
unique ( name, type_id) ,
constraint dict_item_value_type_id_uindex
unique ( value , type_id)
)
comment '数据字典条目' ;
客户端:
服务端:
POJO—Query:
@Data
public class PageQuery {
private static final int MIN_SIZE = 1 ;
private static final int DEFAULT_SIZE = 10 ;
private long size;
private long page;
private List < ? > data;
private long total;
private long pages;
public long getSize ( ) {
return size < MIN_SIZE ? DEFAULT_SIZE : size;
}
public long getPage ( ) {
return page < MIN_SIZE ? MIN_SIZE : page;
}
}
@EqualsAndHashCode ( callSuper = true )
@Data
public class KeywordQuery extends PageQuery {
private String keyword;
}
@EqualsAndHashCode ( callSuper = true )
@Data
public class DictTypeQuery extends KeywordQuery {
}
ServiceImpl:
@Service
@Transactional
public class DictTypeServiceImpl extends ServiceImpl < DictTypeMapper , DictType > implements DictTypeService {
@Autowired
private DictTypeMapper mapper;
@Override
@Transactional ( readOnly = true )
public void list ( DictTypeQuery query) {
final String keyword = query. getKeyword ( ) ;
LambdaQueryWrapper < DictType > wrapper = new LambdaQueryWrapper < > ( ) ;
if ( ! StringUtils . isEmpty ( keyword) ) {
wrapper. like ( DictType :: getName , keyword) . or ( )
. like ( DictType :: getIntro , keyword) . or ( )
. like ( DictType :: getValue , keyword) ;
}
wrapper. orderByDesc ( DictType :: getId ) ;
Page < DictType > page = new Page < > ( query. getNo ( ) , query. getSize ( ) ) ;
mapper. selectPage ( page, wrapper) ;
query. setData ( page. getRecords ( ) ) ;
query. setPages ( page. getPages ( ) ) ;
query. setTotal ( page. getTotal ( ) ) ;
query. setSize ( getSize ( ) ) ;
query. setPage ( getCurrent ( ) ) ;
}
}
Controller:
@RestController
@RequestMapping ( "/dictTypes" )
public class DictTypeController {
@Autowired
private DictTypeService service;
@GetMapping
public Map < String , Object > list ( DictTypeQuery query) {
service. list ( query) ;
final Map < String , Object > map = new HashMap < > ( ) ;
map. put ( "msg" , "" ) ;
map. put ( "data" , query. getData ( ) ) ;
map. put ( "count" , query. getTotal ( ) ) ;
map. put ( "code" , 0 ) ;
return map;
}
@PostMapping ( "/remove" )
public Map < String , Object > remove ( String id) {
final String [ ] ids = id. split ( "," ) ;
final Map < String , Object > map = new HashMap < > ( ) ;
if ( service. removeByIds ( Arrays . asList ( ids) ) ) {
map. put ( "msg" , "删除成功" ) ;
map. put ( "code" , 0 ) ;
} else {
map. put ( "msg" , "删除失败" ) ;
map. put ( "code" , 1 ) ;
}
return map;
}
@PostMapping ( "/save" )
public Map < String , Object > save ( DictType dictType) {
final Map < String , Object > map = new HashMap < > ( ) ;
if ( service. saveOrUpdate ( dictType) ) {
map. put ( "msg" , "保存成功" ) ;
map. put ( "code" , 0 ) ;
} else {
map. put ( "msg" , "保存失败" ) ;
map. put ( "code" , 1 ) ;
}
return map;
}
}
封装MyBatis-Plus方便查询
以查询DictType(数据字典类型)为例
enhance—MPPage、MPQueryWrapper:
public class MPPage < T > extends Page < T > {
private final PageQuery query;
public MPPage ( PageQuery query) {
super ( query. getPage ( ) , query. getSize ( ) ) ;
this . query = query;
}
public void updateQuery ( ) {
query. setData ( getRecords ( ) ) ;
query. setPages ( getPages ( ) ) ;
query. setTotal ( getTotal ( ) ) ;
query. setSize ( getSize ( ) ) ;
query. setPage ( getCurrent ( ) ) ;
}
}
public class MPQueryWrapper < T > extends LambdaQueryWrapper < T > {
@SafeVarargs
public final MPQueryWrapper < T > like ( Object val, SFunction < T , ? > . . . funcs) {
if ( val == null || funcs == null || funcs. length == 0 ) return this ;
final String str = val. toString ( ) ;
if ( str. length ( ) == 0 ) return this ;
return ( MPQueryWrapper < T > ) nested ( ( wrapper) -> {
for ( SFunction < T , ? > func : funcs) {
wrapper. like ( func, str) . or ( ) ;
}
} ) ;
}
}
动态代理更新Query对象:
@Configuration
@EnableAspectJAutoProxy
public class SpringConfig {
}
@Aspect
@Component
public class PageMapperInterceptor {
@Around ( "execution(public com.baomidou.mybatisplus.core.metadata.IPage com.baomidou.mybatisplus.core.mapper.BaseMapper.selectPage(com.baomidou.mybatisplus.core.metadata.IPage, com.baomidou.mybatisplus.core.conditions.Wrapper))" )
public Object updateQuery ( ProceedingJoinPoint point) throws Throwable {
Object result = point. proceed ( ) ;
final Object [ ] args = point. getArgs ( ) ;
if ( args != null && args. length > 0 ) {
Object arg = args[ 0 ] ;
if ( arg instanceof MPPage ) {
( ( MPPage < ? > ) arg) . updateQuery ( ) ;
}
}
return result;
}
}
使用—ServiceImpl:
@Transactional
@Service
public class DictTypeServiceImpl extends ServiceImpl < DictTypeMapper , DictType > implements DictTypeService {
@Override
@Transactional ( readOnly = true )
public void list ( DictTypeQuery query) {
MPQueryWrapper < DictType > wrapper = new MPQueryWrapper < > ( ) ;
wrapper. like ( query. getKeyword ( ) , DictType :: getName , DictType :: getValue , DictType :: getIntro ) ;
wrapper. orderByDesc ( DictType :: getId ) ;
baseMapper. selectPage ( new MPPage < > ( query) , wrapper) ;
}
}
Controller:
@RestController
@RequestMapping ( "/dictTypes" )
public class DictTypeController {
@Autowired
private DictTypeService service;
@GetMapping
public Map < String , Object > list ( DictTypeQuery query) {
service. list ( query) ;
final Map < String , Object > map = new HashMap < > ( ) ;
map. put ( "msg" , "" ) ;
map. put ( "data" , query. getData ( ) ) ;
map. put ( "count" , query. getTotal ( ) ) ;
map. put ( "code" , 0 ) ;
return map;
}
}
封装给客户端的返回值
public class R extends HashMap < String , Object > {
public static final int CODE_SUCCESS = 0 ;
private static final String K_CODE = "code" ;
private static final String K_MSG = "msg" ;
private static final String K_DATA = "data" ;
public R setCode ( int code) {
return add ( K_CODE, code) ;
}
public R setMsg ( String msg) {
return add ( K_MSG, msg) ;
}
public R setData ( Object data) {
return add ( K_DATA, data) ;
}
public R add ( String key, Object data) {
put ( key, data) ;
return this ;
}
}
public final class Rs {
private Rs ( ) {
}
public static final int CODE_SUCCESS = 0 ;
public static final int CODE_ERROR_DEFAULT = 1 ;
private static R success ( ) {
return new R ( ) . setCode ( CODE_SUCCESS) ;
}
public static R success ( PageQuery query) {
return success ( ) . setData ( query. getData ( ) ) ;
}
public static R success ( String msg) {
return success ( ) . setMsg ( msg) ;
}
public static R success ( PageQuery query, String msg) {
return success ( ) . setData ( query. getData ( ) ) . setMsg ( msg) ;
}
public static R error ( ) {
return new R ( ) . setCode ( CODE_ERROR_DEFAULT) ;
}
public static R error ( String msg) {
return error ( ) . setMsg ( msg) ;
}
public static R error ( int code, String msg) {
return new R ( ) . setCode ( code) . setMsg ( msg) ;
}
public static R r ( boolean success) {
return new R ( ) . setCode ( success ? CODE_SUCCESS : CODE_ERROR_DEFAULT) ;
}
public static R r ( boolean success, String msg) {
return r ( success) . setMsg ( msg) ;
}
public static R r ( boolean success, Object data) {
return r ( success) . setData ( data) ;
}
}
Controller使用:
@RestController
@RequestMapping ( "/dictTypes" )
public class DictTypeController {
@Autowired
private DictTypeService service;
@GetMapping
public R list ( DictTypeQuery query) {
service. list ( query) ;
return Rs . success ( query) . add ( "count" , query. getTotal ( ) ) ;
}
@PostMapping ( "/remove" )
public R remove ( String id) {
final String [ ] ids = id. split ( "," ) ;
final boolean success = service. removeByIds ( Arrays . asList ( ids) ) ;
final String msg = success ? "删除成功" : "删除失败" ;
return Rs . r ( success, msg) ;
}
@PostMapping ( "/save" )
public R save ( DictType dictType) {
if ( ! service. saveOrUpdate ( dictType) ) {
throw new RuntimeException ( "保存失败" ) ;
}
return Rs . success ( "保存成功" ) ;
}
}
统一异常处理+HTTP响应状态码
如果服务器端操作失败的话,比如删除失败、保存失败,那么给客户端返回的StatusCode就不应该是200,应该是400/500,原因如下:
客户端(前端)极大可能是根据HTTP请求的响应状态码来判断某个请求是否成功的,而不是通过服务器返回的JSON数据的某个属性值来判断
比如AJAX的回调方法默认就是通过HTTP的响应状态码来判断是否请求成功的。
因此如果服务器处理数据失败,应该修改响应状态码200(OK)为其它StatusCode,比如400、500。
public interface JSONable {
default String jsonString ( ) throws Exception {
return JSONs . getMAPPER ( ) . writeValueAsString ( this ) ;
}
}
public class R extends HashMap < String , Object > implements JSONable {
}
@ControllerAdvice
public class ExceptionInterceptor {
@ExceptionHandler ( Throwable . class )
public void exceptionHandlerOther ( Throwable throwable, HttpServletResponse response) throws Exception {
response. setCharacterEncoding ( "UTF-8" ) ;
response. setStatus ( 400 ) ;
response. getWriter ( ) . write ( Rs . error ( throwable. getMessage ( ) ) . jsonString ( ) ) ;
}
private Throwable getRealCause ( Throwable throwable) {
Throwable cause = throwable. getCause ( ) ;
while ( cause != null ) {
throwable = cause;
cause = cause. getCause ( ) ;
}
return throwable;
}
}
@RestController
@RequestMapping ( "/dictTypes" )
public class DictTypeController {
@Autowired
private DictTypeService service;
@PostMapping ( "/remove" )
public R remove ( String id) {
final String [ ] ids = id. split ( "," ) ;
if ( ! service. removeByIds ( Arrays . asList ( ids) ) ) {
throw new RuntimeException ( "删除失败" ) ;
}
return Rs . success ( "删除成功" ) ;
}
}
还可以继续封装:
public enum CodeMsg {
BAD_REQUEST ( 400 , "请求出错" ) ,
UNAUTHORIZED ( 401 , "未授权" ) ,
FORBIDDEN ( 403 , "禁止访问" ) ,
NOT_FOUND ( 404 , "资源不存在" ) ,
INTERNAL_SERVER_ERROR ( 500 , "服务器内部错误" ) ,
OPERATE_OK ( R . CODE_SUCCESS, "操作成功" ) ,
SAVE_OK ( R . CODE_SUCCESS, "保存成功" ) ,
REMOVE_OK ( R . CODE_SUCCESS, "删除成功" ) ,
OPERATE_ERROR ( 40001 , "操作失败" ) ,
SAVE_ERROR ( 40002 , "保存失败" ) ,
REMOVE_ERROR ( 40003 , "删除失败" ) ,
UPLOAD_IMG_ERROR ( 40004 , "图片上传失败" ) ,
WRONG_USERNAME ( 50001 , "用户名不存在" ) ,
WRONG_PASSWORD ( 50002 , "密码错误" ) ,
USER_LOCKED ( 50003 , "用户被锁定,无法正常登录" ) ,
WRONG_CAPTCHA ( 50004 , "验证码错误" ) ,
NO_TOKEN ( 60001 , "没有Token,请登录" ) ,
TOKEN_EXPIRED ( 60002 , "Token过期,请重新登录" ) ,
NO_PERMISSION ( 60003 , "没有相关的操作权限" ) ;
private final int code;
private final String msg;
CodeMsg ( int code, String msg) {
this . code = code;
this . msg = msg;
}
public int getCode ( ) {
return code;
}
public String getMsg ( ) {
return msg;
}
}
@EqualsAndHashCode ( callSuper = true )
@Data
public class CommonException extends RuntimeException {
private int code;
public CommonException ( ) {
this ( CodeMsg . BAD_REQUEST. getCode ( ) , null ) ;
}
public CommonException ( String msg) {
this ( msg, null ) ;
}
public CommonException ( int code, String msg) {
this ( code, msg, null ) ;
}
public CommonException ( String msg, Throwable cause) {
this ( CodeMsg . BAD_REQUEST. getCode ( ) , msg, cause) ;
}
public CommonException ( int code, String msg, Throwable cause) {
super ( msg, cause) ;
this . code = code;
}
public CommonException ( CodeMsg codeMsg) {
this ( codeMsg, null ) ;
}
public CommonException ( CodeMsg codeMsg, Throwable cause) {
this ( codeMsg. getCode ( ) , codeMsg. getMsg ( ) , cause) ;
}
public int getCode ( ) {
return code;
}
}
public final class Rs {
private Rs ( ) {
}
public static final String K_COUNT = "count" ;
private static final int CODE_SUCCESS = 0 ;
private static final int CODE_ERROR_DEFAULT = CodeMsg . BAD_REQUEST. getCode ( ) ;
private static R success ( ) {
return new R ( ) . setCode ( CODE_SUCCESS) ;
}
public static R success ( PageQuery query) {
return success ( ) . setData ( query. getData ( ) ) ;
}
public static R success ( String msg) {
return success ( ) . setMsg ( msg) ;
}
public static R success ( Object data) {
return success ( ) . setData ( data) ;
}
public static R success ( PageQuery query, String msg) {
return success ( ) . setData ( query. getData ( ) ) . setMsg ( msg) ;
}
public static R success ( CodeMsg codeMsg) {
return success ( ) . setMsg ( codeMsg. getMsg ( ) ) ;
}
public static R error ( ) {
return error ( CODE_ERROR_DEFAULT) ;
}
public static R error ( int code) {
return new R ( ) . setCode ( code) ;
}
public static R error ( String msg) {
return error ( ) . setMsg ( msg) ;
}
public static R error ( int code, String msg) {
return error ( code) . setMsg ( msg) ;
}
public static R error ( Throwable e) {
R r = error ( ) ;
if ( e instanceof CommonException ) {
r. setCode ( ( ( CommonException ) e) . getCode ( ) ) ;
}
return r;
}
public static R r ( boolean success) {
return new R ( ) . setCode ( success ? CODE_SUCCESS : CODE_ERROR_DEFAULT) ;
}
public static R r ( boolean success, String msg) {
return r ( success) . setMsg ( msg) ;
}
public static R r ( boolean success, Object data) {
return r ( success) . setData ( data) ;
}
public static R exception ( String msg) {
throw new CommonException ( msg) ;
}
public static R exception ( CodeMsg codeMsg) {
throw new CommonException ( codeMsg) ;
}
}
@ControllerAdvice
public class ExceptionInterceptor {
@ExceptionHandler ( Throwable . class )
public void handle ( Throwable throwable,
HttpServletResponse response) throws Exception {
response. setContentType ( MediaType . APPLICATION_JSON_VALUE) ;
response. setCharacterEncoding ( StandardCharsets . UTF_8. displayName ( ) ) ;
response. setStatus ( 400 ) ;
response. getWriter ( ) . write ( Rs . error ( throwable) . json ( ) ) ;
}
private Throwable getRealCause ( Throwable throwable) {
Throwable cause = throwable. getCause ( ) ;
while ( cause != null ) {
throwable = cause;
cause = cause. getCause ( ) ;
}
return throwable;
}
}
其实异常处理器还有更简便的写法:
@RestControllerAdvice
@Slf4j
public class CommonExceptionHandler {
@ExceptionHandler ( Throwable . class )
@ResponseStatus ( code = HttpStatus . BAD_REQUEST)
public JSONResult handle ( Throwable throwable) {
log. error ( "error" , throwable) ;
return JSONResults . exception ( throwable) ;
}
}
Controller使用:
@RestController
@RequestMapping ( "/dictTypes" )
public class DictTypeController {
@Autowired
private DictTypeService service;
@GetMapping
public R list ( DictTypeQuery query) {
service. list ( query) ;
return Rs . success ( query) . add ( Rs . K_COUNT, query. getTotal ( ) ) ;
}
@PostMapping ( "/remove" )
public R remove ( String id) {
final String [ ] ids = id. split ( "," ) ;
if ( ! service. removeByIds ( Arrays . asList ( ids) ) ) {
throw new CommonException ( CodeMsg . REMOVE_ERROR) ;
}
return Rs . success ( CodeMsg . REMOVE_OK) ;
}
@PostMapping ( "/save" )
public R save ( DictType dictType) {
if ( ! service. saveOrUpdate ( dictType) ) {
throw new CommonException ( CodeMsg . SAVE_ERROR) ;
}
return Rs . success ( CodeMsg . SAVE_OK. getMsg ( ) ) ;
}
}
还可以将Controller中的公共代码抽取出来:
public abstract class BaseController < T > {
protected abstract IService < T > service ( ) ;
@GetMapping ( "/list" )
public R list ( ) {
return Rs . success ( service ( ) . list ( ) ) ;
}
@PostMapping ( "/remove" )
public R remove ( String id) {
final String [ ] ids = id. split ( "," ) ;
if ( ! service ( ) . removeByIds ( Arrays . asList ( ids) ) ) {
Rs . exception ( CodeMsg . REMOVE_ERROR) ;
}
return Rs . success ( CodeMsg . REMOVE_OK) ;
}
@PostMapping ( "/save" )
public R save ( T entity) {
if ( ! service ( ) . saveOrUpdate ( entity) ) {
Rs . exception ( CodeMsg . SAVE_ERROR) ;
}
return Rs . success ( CodeMsg . SAVE_OK) ;
}
}
@RestController
@RequestMapping ( "/dictTypes" )
public class DictTypeController extends BaseController < DictType > {
@Autowired
private DictTypeService service;
@GetMapping
public R list ( DictTypeQuery query) {
service. list ( query) ;
return Rs . success ( query) . add ( Rs . K_COUNT, query. getTotal ( ) ) ;
}
@Override
protected IService < DictType > service ( ) {
return service;
}
}
统一异常处理—配合Shiro
@RestControllerAdvice
只能拦截到Controller抛出的异常
public class ErrorFilter implements Filter {
public static final String ERROR_URI = "/handleError" ;
@Override
public void doFilter ( ServletRequest request, ServletResponse response, FilterChain chain) throws IOException , ServletException {
try {
chain. doFilter ( request, response) ;
} catch ( Exception e) {
request. setAttribute ( ERROR_URI, e) ;
request. getRequestDispatcher ( ERROR_URI) . forward ( request, response) ;
}
}
}
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean < Filter > filterRegistrationBean ( ) {
FilterRegistrationBean < Filter > bean = new FilterRegistrationBean < > ( ) ;
bean. setFilter ( new ErrorFilter ( ) ) ;
bean. addUrlPatterns ( "/*" ) ;
bean. setOrder ( Ordered . HIGHEST_PRECEDENCE) ;
return bean;
}
}
@RestController
public class ErrorController {
@RequestMapping ( ErrorFilter . ERROR_URI)
public void handle ( HttpServletRequest request) throws Exception {
throw ( Exception ) request. getAttribute ( ErrorFilter . ERROR_URI) ;
}
}
@RestControllerAdvice
@Slf4j
public class CommonExceptionHandler {
@ExceptionHandler ( Throwable . class )
@ResponseStatus ( code = HttpStatus . BAD_REQUEST)
public JSONResult handle ( Throwable t) {
log. error ( "handle" , t) ;
if ( t instanceof CommonException ) {
return handle ( ( CommonException ) t) ;
} else if ( t instanceof BindException ) {
return handle ( ( BindException ) t) ;
} else if ( t instanceof ConstraintViolationException ) {
return handle ( ( ConstraintViolationException ) t) ;
} else if ( t instanceof AuthorizationException ) {
return JSONResults . error ( CodeMsg . NO_PERMISSION) ;
}
Throwable cause = t. getCause ( ) ;
if ( cause != null ) {
return handle ( cause) ;
}
return JSONResults . error ( ) ;
}
private JSONResult handle ( CommonException ce) {
return JSONResults . error ( ce. getCode ( ) , ce. getMessage ( ) ) ;
}
private JSONResult handle ( BindException be) {
List < ObjectError > errors = be. getBindingResult ( ) . getAllErrors ( ) ;
List < String > defaultMsgs = Streams . map ( errors, ObjectError :: getDefaultMessage ) ;
String msg = StringUtils . collectionToDelimitedString ( defaultMsgs, ", " ) ;
return JSONResults . error ( msg) ;
}
private JSONResult handle ( ConstraintViolationException cve) {
List < String > msgs = Streams . map ( cve. getConstraintViolations ( ) , ConstraintViolation :: getMessage ) ;
String msg = StringUtils . collectionToDelimitedString ( msgs, ", " ) ;
return JSONResults . error ( msg) ;
}
}
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean ( Realm realm) {
ShiroFilterFactoryBean filterBean = new ShiroFilterFactoryBean ( ) ;
filterBean. setSecurityManager ( new DefaultWebSecurityManager ( realm) ) ;
Map < String , Filter > filters = new HashMap < > ( ) ;
filters. put ( "token" , new TokenFilter ( ) ) ;
filterBean. setFilters ( filters) ;
Map < String , String > urlMap = new LinkedHashMap < > ( ) ;
urlMap. put ( ErrorFilter . ERROR_URI, "anon" ) ;
urlMap. put ( "/**" , "token" ) ;
filterBean. setFilterChainDefinitionMap ( urlMap) ;
return filterBean;
}
}
数据的一致性
外键有优点,同样,也有它的缺点
如果在项目中使用了外键,那么表与表之间的数据一致性其实是不用我们操心的,因为有外键自动帮我们约束。
因此对于那些小型的、对于数据一致性要求很高的项目,需要使用外键。
但是大型的、分布式的互联网项目出于对数据库的性能、备份、迁移、维护等原因的考虑,一般而言在设计数据库时是不使用外键的,那么这时表与表之间的联系,也就是数据一致性问题怎么解决呢?
答案就是需要我们自己在应用层做好相应的处理,但做好这个处理并不简单。
假设现在要对某张表的进行数据一致性的处理,有许多非常麻烦的点
虽然数据库没有使用外键,但对业务来讲,表与表之间应该是有联系的
那么对这张表进行删除、更新、添加等操作都需要考虑数据一致性,具体到代码,就是remove、save、update这种方法有很多,需要都考虑到
有可能会有很多表在数据上都关联这张表,因此我们需要清楚每一张表与每一张表之间的关联关系,知道了表与表之间的关联关系后,才能去逐个处理
解决方案:自己写一个保证数据一致性的小框架,这个小框架的特点:
MJ老师编写框架经验:
如果你自己想写一个比较好用的框架
首先应该从应用的角度出发,先从使用者(自己、其他开发者)的角度出发
考虑别人应该怎么用这个框架、这个框架能够怎样简化开发、这个框架怎么样能够使开发变得更爽、更高效、更敏捷
然后再考虑减少BUG
然后再考虑安全问题
然后再考虑性能问题
然后再考虑解耦、抽取、可扩展…
拼音库—tinypinyin的使用
< dependency>
< groupId> com.github.promeg groupId>
< artifactId> tinypinyin artifactId>
< version> 2.0.3 version>
dependency>
@Override
public boolean updateById ( PlateRegion entity) {
processPinyin ( entity) ;
return super . updateById ( entity) ;
}
@Override
public boolean save ( PlateRegion entity) {
processPinyin ( entity) ;
return super . save ( entity) ;
}
private void processPinyin ( PlateRegion entity) {
final String name = entity. getName ( ) ;
if ( StringUtils . isEmpty ( name) ) return ;
entity. setPinyin ( Pinyin . toPinyin ( name, "_" ) ) ;
}
MapStruct
作用:对象转换
< dependency>
< groupId> org.mapstruct groupId>
< artifactId> mapstruct artifactId>
< version> ${map.struct.version} version>
< scope> provided scope>
dependency>
< dependency>
< groupId> org.mapstruct groupId>
< artifactId> mapstruct-processor artifactId>
< version> ${map.struct.version} version>
< scope> provided scope>
dependency>
@Mapper
public interface MapStruct {
MapStruct INSTANCE = Mappers . getMapper ( MapStruct . class ) ;
DictItem vo2po ( ReqSaveDictItem reqSaveVo) ;
DictType vo2po ( ReqSaveDictType reqSaveVo) ;
ExamPlace vo2po ( ReqSaveExamPlace reqSaveVo) ;
ExamPlaceCourse vo2po ( ReqSaveExamPlaceCourse reqSaveVo) ;
PlateRegion vo2po ( ReqSavePlateRegion reqSaveVo) ;
RespDictItem po2vo ( DictItem po) ;
RespDictType po2vo ( DictType po) ;
RespExamPlace po2vo ( ExamPlace po) ;
RespExamPlaceCourse po2vo ( ExamPlaceCourse po) ;
RespPlateRegion po2vo ( PlateRegion po) ;
}
基本使用:
final DictItem dictItem = MapStruct . INSTANCE. vo2po ( new ReqSaveDictItem ( ) ) ;
final RespDictItem respDictItem = MapStruct . INSTANCE. po2vo ( new DictItem ( ) ) ;
项目中使用:
public abstract class BaseController < T , ReqSave > {
protected abstract Function < ReqSave , T > function ( ) ;
@PostMapping ( "/save" )
public JSONResult save ( @Valid ReqSave entity) {
service. saveOrUpdate ( function ( ) . apply ( entity) ) ;
}
}
public class DictItemController extends BaseController < DictItem , ReqSaveDictItem > {
@Override
protected Function < ReqSaveDictItem , DictItem > function ( ) {
return MapStruct . INSTANCE:: vo2po ;
}
}
public class PlateRegionServiceImpl extends ServiceImpl < PlateRegionMapper , PlateRegion > implements PlateRegionService {
public JSONDataResult < List < RespPlateRegion > > listProvinces ( ) {
final List < RespPlateRegion > data = baseMapper. selectList ( wrapper)
. stream ( ) . map ( MapStruct . INSTANCE:: po2vo )
. collect ( Collectors . toList ( ) ) ;
return JSONResults . success ( data) ;
}
}
自定义转换规则 :
public class MapStructFormatter {
@Qualifier
@Target ( ElementType . METHOD)
@Retention ( RetentionPolicy . CLASS)
public @interface Date2Millis { }
@Date2Millis
public static Long date2millis ( Date date) {
if ( date == null ) return null ;
return date. getTime ( ) ;
}
}
@Data
public class SysUser {
private Date loginTime;
}
@Data
@ApiModel ( "系统用户" )
public class RespSysUser {
@ApiModelProperty ( "最后一次登录的时间" )
private Long loginTime;
}
@Mapper ( uses = {
MapStructFormatter . class
} )
public interface MapStructs {
@Mapping ( source = "loginTime" ,
target = "loginTime" ,
qualifiedBy = MapStructFormatter. Date2Millis . class )
RespSysUser po2vo ( SysUser po) ;
}
登录—简单登录
< dependency>
< groupId> com.github.whvcse groupId>
< artifactId> easy-captcha artifactId>
< version> 1.6.2 version>
dependency>
@RestController
@RequestMapping ( "/sysUsers" )
@Api ( tags = "系统用户" , description = "SysUser" )
public class SysUserController extends BaseController < SysUser , ReqSaveSysUser > {
@Autowired
private SysUserService service;
@GetMapping ( "/captcha" )
@ApiOperation ( "生成验证码" )
public void captcha ( HttpServletRequest request,
HttpServletResponse response) throws Exception {
CaptchaUtil . out ( request, response) ;
}
@PostMapping ( "/login" )
@ApiOperation ( "登录" )
public JSONDataResult < RespLogin > login ( ReqLogin reqVo, HttpServletRequest request) {
if ( CaptchaUtil . ver ( reqVo. getCaptcha ( ) , request) ) {
return JSONResults . success ( service. login ( reqVo) ) ;
}
JSONResults . exception ( CodeMsg . WRONG_CAPTCHA) ;
return null ;
}
}
@Override
public RespLogin login ( ReqLogin reqVo) {
MPLambdaQueryWrapper < SysUser > wrapper = new MPLambdaQueryWrapper < > ( ) ;
wrapper. eq ( SysUser :: getUsername , reqVo. getUsername ( ) ) ;
SysUser po = baseMapper. selectOne ( wrapper) ;
if ( po == null ) {
return JsonVos . raise ( CodeMsg . WRONG_USERNAME) ;
}
if ( ! po. getPassword ( ) . equals ( reqVo. getPassword ( ) ) ) {
return JsonVos . raise ( CodeMsg . WRONG_PASSWORD) ;
}
if ( po. getStatus ( ) == Constants. SysUserStatus . LOCKED) {
return JsonVos . raise ( CodeMsg . USER_LOCKED) ;
}
po. setLoginTime ( new Date ( ) ) ;
baseMapper. updateById ( po) ;
RespLogin vo = MapStruct . INSTANCE. po2loginVo ( po) ;
return vo;
}
前端Ajax登录:
Ajaxs. loadPost ( {
uri : 'sysUsers/login' ,
data : data. field,
success : ( response ) => {
location. href = '../index.html'
} ,
xhrFields : {
withCredentials : true
}
}
登录—Token
后端
登录
@Data
@ApiModel ( "登录成功的结果" )
public class RespLogin {
@ApiModelProperty ( "登录令牌" )
private String token;
}
@PostMapping ( "/login" )
@ApiOperation ( "登录" )
public JSONDataResult < RespLogin > login ( ReqLogin reqVo, HttpServletRequest request) {
if ( CaptchaUtil . ver ( reqVo. getCaptcha ( ) , request) ) {
return JSONResults . success ( service. login ( reqVo) ) ;
}
JSONResults . exception ( CodeMsg . WRONG_CAPTCHA) ;
return null ;
}
@Override
public RespLogin login ( ReqLogin reqVo) {
MPLambdaQueryWrapper < SysUser > wrapper = new MPLambdaQueryWrapper < > ( ) ;
wrapper. eq ( SysUser :: getUsername , reqVo. getUsername ( ) ) ;
SysUser po = baseMapper. selectOne ( wrapper) ;
if ( po == null ) {
return JsonVos . raise ( CodeMsg . WRONG_USERNAME) ;
}
if ( ! po. getPassword ( ) . equals ( reqVo. getPassword ( ) ) ) {
return JsonVos . raise ( CodeMsg . WRONG_PASSWORD) ;
}
if ( po. getStatus ( ) == Constants. SysUserStatus . LOCKED) {
return JsonVos . raise ( CodeMsg . USER_LOCKED) ;
}
po. setLoginTime ( new Date ( ) ) ;
baseMapper. updateById ( po) ;
String token = UUID. randomUUID ( ) . toString ( ) ;
Caches . putToken ( token, po) ;
RespLogin vo = MapStruct . INSTANCE. po2loginVo ( po) ;
vo. setToken ( token) ;
return vo;
}
退出登录
@PostMapping ( "/logout" )
@ApiOperation ( "退出登录" )
public JSONResult logout ( @RequestHeader ( "Token" ) String token) {
Caches . removeToken ( token) ;
return JSONResults . success ( CodeMsg . LOGOUT_OK) ;
}
前端
登录:
class DataKey {
static USER = 'user'
static TOKEN = 'token'
static TOKEN_HEADER = 'Token'
}
Ajaxs. loadPost ( {
uri : 'sysUsers/login' ,
data : data. field,
success : ( response ) => {
Datas. save ( DataKey. USER , response. data)
location. href = '../index.html'
} ,
xhrFields : {
withCredentials : true
}
} )
需要确保登录后的每次请求都带上Token信息
static get ( ) {
let ret = layui. data ( this . TABLE )
for ( let i = 0 ; i < arguments. length; i++ ) {
if ( ! ret) return null
ret = ret[ arguments[ i] ]
}
return ret
}
static _addTokenHeader ( cfg ) {
const token = Datas. get ( DataKey. USER , DataKey. TOKEN )
if ( token) {
if ( ! cfg. headers) {
cfg. headers = { }
}
cfg. headers[ DataKey. TOKEN_HEADER ] = token
}
}
static ajax ( cfg ) {
cfg. url = Commons. url ( cfg. uri)
Commons. _addTokenHeader ( cfg)
$. ajax ( cfg)
}
_init ( ) {
const cfg = this . _commonCfg ( )
cfg. url = Commons. url ( this . _cfg. uri)
$. extend ( cfg, this . _cfg)
cfg. elem = cfg. selector
Commons. _addTokenHeader ( cfg)
this . _innerTable = this . _layuiTable ( ) . render ( cfg)
this . _cfg = cfg
}
登出:
$ ( '.login-out' ) . click ( ( ) => {
Ajaxs. loadPost ( {
uri : 'sysUsers/logout' ,
success : ( ) => {
Datas. remove ( DataKey. USER )
Layers. msgSuccess ( '退出登录成功' , ( ) => {
location. href = 'page/login.html'
} )
}
} )
} )
权限管理—RBAC
可以登录后台管理系统的员工/系统管理员:比如:sys_user
(表名以sys_
开头)
使用产品的用户/客户(APP、小程序、网页):比如:user
表结构设计 :
可以登录后台管理系统的员工/系统管理员:比如:sys_user(表名以sys_开头)
使用产品的用户/客户(APP、小程序、网页):比如:user
create table if not exists jk. sys_resource
(
id tinyint unsigned auto_increment comment '主键'
primary key ,
name varchar ( 15 ) default '' not null comment '名称' ,
uri varchar ( 100 ) default '' not null comment '链接地址' ,
permission varchar ( 100 ) default '' not null comment '权限标识' ,
type tinyint unsigned default 0 not null comment '资源类型(0是目录,1是菜单,2是按钮)PS:按钮就是增删改查之类的能点击的' ,
icon varchar ( 100 ) default '' not null comment '图标' ,
sn tinyint unsigned default 0 not null comment '序号' ,
parent_id tinyint unsigned default 0 not null comment '父资源id' ,
constraint sys_resource_parent_id_name_uindex
unique ( parent_id, name)
)
comment '资源' ;
create table if not exists jk. sys_role
(
id tinyint unsigned auto_increment comment '主键'
primary key ,
name varchar ( 15 ) default '' not null comment '角色名称' ,
constraint sys_role_name_uindex
unique ( name)
)
comment '角色' ;
create table if not exists jk. sys_role_resource
(
role_id tinyint unsigned default 0 not null comment '角色id' ,
resource_id tinyint unsigned default 0 not null comment '资源id' ,
primary key ( resource_id, role_id)
)
comment '角色-资源' ;
create table if not exists jk. sys_user
(
id smallint unsigned auto_increment comment '主键'
primary key ,
nickname varchar ( 15 ) default '' not null comment '昵称' ,
username varchar ( 15 ) default '' not null comment '登录用的用户名' ,
password char ( 32 ) default '' not null comment '登录用的密码,密码经过MD5加密之后就是32位的字符串' ,
create_time datetime default CURRENT_TIMESTAMP not null comment '创建的时间' ,
login_time datetime null comment '最后一次登录的时间' ,
status tinyint unsigned default 0 not null comment '账号的状态,0是正常,1是锁定' ,
constraint sys_user_username_uindex
unique ( username)
)
comment '用户(可以登录后台系统的)' ;
create table if not exists jk. sys_user_role
(
role_id tinyint unsigned default 0 not null comment '角色id' ,
user_id smallint unsigned default 0 not null comment '用户id' ,
primary key ( user_id, role_id)
)
comment '用户-角色' ;
逻辑删除
逻辑删除—MyBatisPlus
create table user
(
id int unsigned auto_increment
primary key ,
name varchar ( 15 ) default '' not null ,
deleted tinyint unsigned default 0 not null comment '1是被删除,0是未删除' ,
constraint user_name_uindex
unique ( name)
) ;
企业级文件上传
@RequestBody修饰的请求参数
前端/客户端将Content-Type改为:application/json
请求体传递符合要求的JSON字符串【JSON优势:灵活、前端处理方便、第三方库也多】
@PostMapping ( "/save1" )
@ApiOperation ( "添加或更新" )
public JsonVo save1 ( User user) {
JsonVo jsonVo = new JsonVo ( ) ;
jsonVo. setMsg ( service. saveOrUpdate ( user) ? "保存成功" : "保存失败" ) ;
return jsonVo;
}
@PostMapping ( "/save2" )
@ApiOperation ( "添加或更新" )
public JsonVo save2 ( @RequestBody User user) {
JsonVo jsonVo = new JsonVo ( ) ;
jsonVo. setMsg ( service. saveOrUpdate ( user) ? "保存成功" : "保存失败" ) ;
return jsonVo;
}
单元测试
Spring单元测试
SpringBoot单元测试
< dependency>
< groupId> org.springframework.boot groupId>
< artifactId> spring-boot-starter-test artifactId>
< scope> test scope>
dependency>
@RunWith ( SpringRunner . class )
@SpringBootTest
public class SpringAMQPTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendSimpleQueue ( ) {
String queueName = "simple.queue" ;
Object message = "Hello, SpringAMQP! I am LP!" ;
rabbitTemplate. convertAndSend ( queueName, message) ;
}
}
打包部署
打包部署—jar
打包部署—war
注意
配置JackSON将Model转为JSON时,不包含值为null的属性:
application.yml:
spring :
jackson :
default-property-inclusion : non_null
Java代码:
public final class JSONs {
private JSONs ( ) {
}
private static final ObjectMapper MAPPER = new ObjectMapper ( ) ;
static {
MAPPER. setDefaultPropertyInclusion ( JsonInclude. Include . NON_NULL) ;
}
public static ObjectMapper getMAPPER ( ) {
return MAPPER;
}
}
MySQL配置:
#url=jdbc:mysql://127.0.0.1:3306/lp_resume
#url=jdbc:mysql://localhost:3306/lp_resume
#url=jdbc:mysql:///lp_resume
#UTC:世界同一时间
#url=jdbc:mysql:///lp_resume?serverTimezone=UTC&useSSL=false
#中国时间:serverTimezone=Asia/Shanghai == serverTimezone=GMT+8
#url=jdbc:mysql:///lp_resume?serverTimezone=GMT+8&useSSL=false
url=jdbc:mysql:///lp_resume?serverTimezone=Asia/Shanghai&useSSL=false
############使用IDEA连接数据库############
#使用IDEA连接MySQL数据库时,由于“+”是一个特殊字符,因此需要编码处理为:“%2B”
#例如:jdbc:mysql:///?serverTimezone=GMT%2B8&useSSL=false
#或者:jdbc:mysql:///?serverTimezone=Asia/Shanghai&useSSL=false
HTML的button标签,默认类型是
,因此button如果是其它类型的话,最好显示声明button的type,比如:
客户端向服务器发送请求参数时
如果http://localhost:8080/jk/dictTypes/list?page=1&size=20
,那么服务器获取到的keyword
就是null
如果http://localhost:8080/jk/dictTypes/list?page=1&size=20&keyword=
,那么服务器获取到的keyword
就是""(空字符串)
数据库中,表名和字段名建议使用``、字符串建议使用’'(单引号)
MySQL数据库,行(记录)从0开始,列(字段)从1开始
标准JSON格式:key使用""(双引号):
[
{
"age" : 10 ,
"name" : "lp"
} ,
{
"age" : 20 ,
"name" : "ruoyu"
}
]
{
"string" : "value" ,
"integer" : 10 ,
"bool" : true ,
"null" : null ,
"array" : [ ] ,
"obj" : { }
}
补充:ChromeJSON插件
https://chrome.google.com/webstore/detail/json-handle/iahnhfdhidomcpggpaimmmahffihkfnj
参考
小码哥-李明杰: Java从0到架构师③进阶互联网架构师.
本文完,感谢您的关注支持!
你可能感兴趣的:(JAVA,java,spring,boot,MyBatisPlus,Shiro)
ts之变量声明以及语法细节,ts小白初学ing
菥菥爱嘻嘻
小白学习ts typescript 前端
TypeScript用js编写的项目虽然开发很快,但是维护是成本很高,而且js不报错啊啊啊啊啊!!!以js为基础进行扩展的给变量赋予了类型语法、实战(ts+vue3)TypeScript是JavaScript的一个超集,支持ECMAScript6标准(ES6教程)。TypeScript由微软开发的自由和开源的编程语言,在JavaScript的基础上增加了静态类型检查的超集。TypeScript设计
Node.js 如何发布一个 NPM 包——详细教程
还是鼠鼠
node.js npm 前端 node.js vscode
在本文中,我将带大家一步步学习如何创建并发布一个NPM包,帮助开发者理解整个流程,并能顺利将自己的JavaScript库发布到NPM上供他人使用。1.安装Node.js和npm在开始之前,请确保你的电脑上已经安装了Node.js和npm(Node.js自带npm)。你可以在终端(Windows用户请使用cmd或PowerShell)输入以下命令检查是否已安装:node-vnpm-v如果出现版本号,
深入探讨Spring MVC:原理、架构与实践
luckilyil
开发框架 spring mvc 架构
SpringMVC原理与架构解析1.SpringMVC概述SpringMVC是Spring框架中的一个模块,专注于实现Web应用的MVC设计模式。它通过将应用逻辑分为模型(Model)、视图(View)和控制器(Controller),使得开发人员能够清晰地组织代码,提高开发效率和可维护性。2.SpringMVC的核心组件SpringMVC的核心组件包括:DispatcherServlet:作为前
同时使用接口文档swagger和knife4j
黑taoA
java 开发语言
项目场景:springboot项目中同时使用接口文档swagger和knife4j问题描述在实体类中设置了字段必填的属性,在访问接口文档时出现异常实体类关键代码片段/***部门表sys_dept*/publicclassSysDeptextendsBaseEntity{privatestaticfinallongserialVersionUID=1L;/**部门ID*/privateLongdep
SpringCloud框架下的注册中心比较:Eureka与Consul的实战解析
耶耶Norsea
网络杂烩 spring cloud
摘要在探讨SpringCloud框架中的两种注册中心之前,有必要回顾单体架构与分布式架构的特点。单体架构将所有业务功能集成在一个项目中,优点是架构简单、部署成本低,但耦合度高。分布式架构则根据业务功能对系统进行拆分,每个模块作为独立服务开发,降低了服务间的耦合,便于升级和扩展,然而其复杂性增加,运维、监控和部署难度也随之提高。关键词SpringCloud,注册中心,单体架构,分布式架构,服务拆分一
程序员晋升架构师实战指南
甘苦人生
职业规划 职场和发展
以下是为程序员量身定制的晋升架构师实战指南,结合行业案例与可落地路径,助你完成技术跃迁:一、晋升路径拆解(从Code到Architecture)程序员→高级工程师核心任务:独立完成模块开发(需求分析+方案设计+编码实现)技术重点:掌握1-2门核心语言(如Java/Go)、熟悉主流框架(SpringCloud/Dubbo)案例:主导用户中心模块开发,通过缓存优化将接口响应时间从800ms降至150m
Linux 启动Jar脚本&&设置开机自启【超级详细】
黑taoA
linux jar python
Linux启动Jar脚本&&设置开机自启【超级详细】概要服务器开机自启服务重启脚本概要最近在Linux服务器中部署了一个项目(单机版),每次更新服务的时候需要用到好几个命令,停止服务,再重启,并且服务器突然重启后,还需要人工重启服务,非常繁琐,下面展示了两个脚本的写法。。服务器开机自启检查系统是否安装jdk;java-version查看jdk安装位置whereisjava编写脚本restart_y
Spring Cloud Config 快速介绍与实例
oscar999
Spring Boot实战开发大全 Spring Boot Cloud Config
SpringCloudConfig是什么?SpringCloudConfig是一个用于分布式系统的配置管理工具,提供集中化的外部配置支持。它适用于微服务架构,能够将各个服务的配置集中存储在服务端(如Git仓库),客户端按需动态获取配置,解决了配置分散、环境切换复杂等问题。SpringCloudConfig核心概念ConfigServer:配置中心服务端,统一管理配置,支持Git、本地文件等存储方式
包管理工具
她的双马尾
JS javascript 包管理工具 npm yarn pnpm
JavaScript包管理工具对比:npm、yarn和pnpm1.npm1.1历史与背景npm(NodePackageManager)是Node.js的默认包管理工具,首次发布于2010年。它是JavaScript生态系统中最早的包管理工具,主要用于管理和共享JavaScript模块。目前,npm拥有全球最大的JavaScript包注册中心(npmregistry),包含数百万个开源包。1.2核心
Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实战指南
QQ828929QQ
spring boot 后端 java
SpringBoot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实战指南一、核心概念对比1.本质区别维度过滤器(Filter)拦截器(Interceptor)规范层级Servlet规范(J2EE标准)SpringMVC框架机制作用范围所有请求(包括静态资源)只处理Controller请求依赖关系不依赖Spring容器完全集成SpringIOC容器执行顺序最先执行(
PV操作(Java代码)进程同步实战指南
Cloud_.
java 开发语言 操作系统 并发
引言在Java并发编程中,资源同步如同精密仪器的齿轮咬合,任何偏差都可能导致系统崩溃。本文将以Java视角解析经典PV操作原理,通过真实可运行的代码示例,带你掌握线程同步的底层实现逻辑。一、Java信号量实现机制1.1Semaphore类解析importjava.util.concurrent.Semaphore;//创建包含5个许可的信号量(相当于计数信号量)Semaphoresemaphore
Spring Boot 整合 RabbitMQ:注解声明队列与交换机详解
Cloud_.
java-rabbitmq spring boot rabbitmq MQ 消息队列
RabbitMQ作为一款高性能的消息中间件,在分布式系统中广泛应用。SpringBoot通过spring-boot-starter-amqp提供了对RabbitMQ的无缝集成,开发者可以借助注解快速声明队列、交换机及绑定规则,极大简化了配置流程。本文将通过代码示例和原理分析,详细介绍如何用注解实现RabbitMQ的集成,并深入解析交换机的作用与类型。一、环境准备1.添加依赖在pom.xml中引入S
191113面试题总结
快乐男孩小东
1.Maven中A依赖BB依赖C那么A可以使用C中的类吗?*按照依赖关系,可推C继承A,则C可以使用A中修饰符为public,protected的类2.SpringBoot中有一个类标记了@Controller注解,通过自动扫描把这个类的对象加入IOC,那么这个类应该放那?*在@SpringBootApplication所在包或者下面的子包,才能被自动扫描到#3.通过Maven下载jar包,下载失
开发语言漫谈-groovy
大道不孤,众行致远
技术杂谈 开发语言
groovy是一门脚本语言,在前期的脚本语言中简单介绍了下。现在再深入介绍下,因为它是本平台上选用的脚本语言。所谓脚本语言就是不用编译,直接执行。这种特色非常适合做嵌入编程,即编即用。我们知道平台后台的业务开发语言是Java,开发人员都熟悉Java。那么使用groovy就是自然而然的事情,因为groovy最大特点就是和Java兼容。然后做了最有意义的改造:1、可以解释执行;2、增加动态类型。发明人
java:实现设置窗体背景颜色为淡蓝色(附带源码)
Katie。
Java 实战项目 java 信息可视化 开发语言
一、项目简介在桌面应用开发中,窗体背景颜色作为界面设计的重要组成部分,不仅影响整体美观,还能传递特定的情感和品牌信息。本项目旨在使用JavaSwing简单实现将窗体背景颜色设置为淡蓝色效果。该示例展示了如何创建一个基本的JFrame,并通过调用其内容面板的setBackground()方法,设置背景颜色为淡蓝色(RGB值173,216,230)。通过本项目,初学者可以了解Swing基本组件的使用方
前端实例:轮播图效果
2301_81535770
前端
利用HTML、CSS和JavaScript实现轮播图效果。一、轮播图原理:通过给窗口设置position属性和overflow属性,使得超出窗口范围的部分被隐藏,表面可见范围只包含窗口,但实际上其内部空间很大;调整胶卷相对于窗口的位置,使得整个胶卷向左移动;调用JS中的定时器,实现轮播效果。流程图如下:二、实现自动切换效果1、HTML搭建基础框架分为图片展示窗口和上下页切换按键两部分>2、CSS设
Orange 单体架构 - 快速启动
mmd0308
Orange 开源项目 架构 开源
1后端服务1.1基础设施组件说明版本MySQLMySQL数据库服务5.7/8+JavaJava17redis-stackRedis向量数据库最新版本Node安装Node22.11.0+1.2orange-dependencies-parent项目Maven依赖版本管理1.2.1项目克隆GitHubgitclonehttps://github.com/hengzq/orange-dependenci
SpringbootActuator未授权访问漏洞
web_15534274656
面试 学习路线 阿里巴巴 java
漏洞介绍Actuator是SpringBoot提供的用来对应用系统进行自省和监控的功能模块,借助于Actuator开发者可以很方便地对应用系统某些监控指标进行查看、统计等。然而,其默认配置会出现接口未授权访问,导致部分接口会泄露网站数据库连接信息等配置信息,使用Jolokia库特性甚至可以远程执行任意代码,获取服务器权限。1、漏洞危害1、信息泄露:未授权的访问者可以通过Actuator端点获取敏感
过滤器Filter
" 微笑
spring
过滤器Filter1.快速入门什么是Filter?Filter表示过滤器,是JavaWeb三大组件(Servlet、Filter、Listener)之一。过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能使用了过滤器之后,要想访问web服务器上的资源,必须先经过滤器,过滤器处理完毕之后,才可以访问对应的资源。过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。下面我们
【MyBatisPlus】MyBatisPlus介绍与使用
web_15534274656
面试 学习路线 阿里巴巴 java
【MyBatisPlus】MyBatisPlus介绍与使用文章目录【MyBatisPlus】MyBatisPlus介绍与使用1、什么MyBatisPlus2、MyBatisPlus的CRUD操作3、MyBatisPlus分页使用1、什么MyBatisPlusMyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率官网:https://baomidou
k8s运维 设置Pod实现JVM内存根据容器内存动态调整
风行無痕
K8S kubernetes jvm 容器
一、实现方式推荐方案:利用JVM容器感知特性,按比例动态分配。适用场景:动态根据Pod内存限制自动分配堆内存,无需硬编码参数Java要求:Java8u191+或Java11+Java8u191+或Java11+支持通过-XX:InitialRAMPercentage替代-Xms,根据容器内存限制自动计算堆内存。在容器环境变量中配置-XX:MaxRAMPercentage=75.0,使JVM根据容
基于Redis分布锁+事务补偿解决数据不一致性问题
yiridancan
并发编程 Redis 分布式 redis 数据库 缓存
基于Redis的分布式设备库存服务设计与实现概述本文介绍一个基于Redis实现的分布式设备库存服务方案,通过分布式锁、重试机制和事务补偿等关键技术,保证在并发场景下库存操作的原子性和一致性。该方案适用于物联网设备管理、分布式资源调度等场景。代码实现importjava.util.HashMap;importjava.util.Map;importorg.slf4j.Logger;importorg
Java并发实战——CountDownLatch优化商品详情页数据加载
1加1等于
Java并发 java 开发语言 多线程
本文将结合电商场景比如优化商品详情页数据加载,深入探讨CountDownLatch的工作原理及实际应用。本文目录1.简介2.商品详情页数据加载优化实战3.CountDownLatch的优势4.其他应用场景5.使用误区1.简介CountDownLatch是Java并发包java.util.concurrent中的一个同步工具类。允许一个或多个线程等待,直到其他一组线程完成一系列操作。CountDow
Java进阶——常用类及常用方法详解
1加1等于
Java java
本文将深入探讨Java常用类的核心知识点以及在日常工作中的使用场景。本文目录一、String类1.不可变性2.字符串常量池3.比较字符串二、日期时间常用类1.Java8引入2.时间计算三、Math数值处理四、Optional空值处理五、异常处理类六、枚举类一、String类1.不可变性String类是不可变的,这意味着一旦创建就不能被修改。在进行字符串拼接时,需要注意性能问题。//不推荐:会创建多
Java进阶——数组超详细整理
1加1等于
Java java 数据结构
数组是一种基础且重要的数据结构,广泛应用于各种场景,本文将深入探讨Java数组的相关知识点,并结合实际场景展示其应用。本文目录一、数组声明与初始化1.声明方式2.初始化方法3.长度特性二、内存管理三、数组遍历与操作1.遍历方式2.数组填充四、多维数组五、数组工具类Arrays六、数组与集合的转换1.数组转集合2.集合转数组总结一、数组声明与初始化1.声明方式数组的声明有两种方式:int[]prod
SpringBoot + Facade Pattern : 通过统一接口简化多模块业务
Java布道者
spring boot 外观模式 后端
概述外观设计模式(FacadePattern)是一种常见的结构型设计模式,它的主要目的是简化复杂系统的使用。可以把它想象成一个“控制面板”或者“遥控器”,通过这个控制面板,用户可以轻松操作一个复杂的系统,而不需要关心系统内部是如何运作的。举个生活中的例子,想象一下,你家有一台多功能的家电,比如一台智能电视,它不仅能看电视,还能上网、播放视频、控制智能家居等等。对于电视的操作,你有遥控器,可以通过一
SpringMVC-解决跨域的两种方案
青岛欢迎您
开发框架 springmvc
1.什么是跨域跨域,即跨站HTTP请求(Cross-siteHTTPrequest),指发起请求的资源所在域不同于请求指向资源所在域的HTTP请求。2.跨域的应用情景当使用前后端分离,后端主导的开发方式进行前后端协作开发时,常常有如下情景:后端开发完毕在服务器上进行部署并给前端API文档。前端在本地进行开发并向远程服务器上部署的后端发送请求。在这种开发过程中,如果前端想要一边开发一边测试接口,就需
Vue——Vue-cli脚手架+前端路由
pdsu_zhao
Vue Vue学习之旅 vue v-router v-resource vue-cli ES6
Vue-cli是Vue的脚手架工具可以进行目录结构、本地调试、代码部署、热加载、单元测试1、MVVM框架View——ViewModel——Model(视图)(通讯)(数据)“DOM”“观察者vue实例”“Javascript”注意:交互为双向的特点:(1)针对具有复杂交互逻辑的前端应用;(2)提供基础的架构抽象;(3)通过Ajax数据持久化,保证前端用户体验。2、什么是Vue.js它是一个轻量级M
AsyncHttpClient使用说明书
有梦想的攻城狮
netty学习专栏 Java asynchttpclient 异步处理 netty
[[toc]]AsyncHttpClient(AHC)是一个高性能、异步的HTTP客户端库,广泛用于Java和Scala应用中,特别适合处理高并发、非阻塞的HTTP请求。它基于Netty或Java原生的异步HTTP客户端实现,支持HTTP/1.1和HTTP/2协议,适用于微服务、API调用、爬虫等场景。1.核心特性特性说明异步非阻塞基于事件驱动模型,避免线程阻塞,支持高并发(如每秒数千请求)。HT
Java的包结构
MingDong523
笔记
Java的包结构类就相当于文件夹(目录)。包结构的作用一般有以下两个方面第一个就是Java的包是根据Java文件的功能和性质来区分,方便区分和查找另一个就是重复的文件名可以存在于不同的包(文件夹)里。当我们选择去创建Java包时有两种创建方式,其中一种就是手动创建,手动创建包太过繁琐,不推荐。而另一种就是使用代码去创建(打包语句package),当我们使用打包语句时要注意一下几点1.在写packa
解线性方程组
qiuwanchi
package gaodai.matrix;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner scanner = new Sc
在mysql内部存储代码
annan211
性能 mysql 存储过程 触发器
在mysql内部存储代码
在mysql内部存储代码,既有优点也有缺点,而且有人倡导有人反对。
先看优点:
1 她在服务器内部执行,离数据最近,另外在服务器上执行还可以节省带宽和网络延迟。
2 这是一种代码重用。可以方便的统一业务规则,保证某些行为的一致性,所以也可以提供一定的安全性。
3 可以简化代码的维护和版本更新。
4 可以帮助提升安全,比如提供更细
Android使用Asynchronous Http Client完成登录保存cookie的问题
hotsunshine
android
Asynchronous Http Client是android中非常好的异步请求工具
除了异步之外还有很多封装比如json的处理,cookie的处理
引用
Persistent Cookie Storage with PersistentCookieStore
This library also includes a PersistentCookieStore whi
java面试题
Array_06
java 面试
java面试题
第一,谈谈final, finally, finalize的区别。
final-修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能
网站加速
oloz
网站加速
前序:本人菜鸟,此文研究总结来源于互联网上的资料,大牛请勿喷!本人虚心学习,多指教.
1、减小网页体积的大小,尽量采用div+css模式,尽量避免复杂的页面结构,能简约就简约。
2、采用Gzip对网页进行压缩;
GZIP最早由Jean-loup Gailly和Mark Adler创建,用于UNⅨ系统的文件压缩。我们在Linux中经常会用到后缀为.gz
正确书写单例模式
随意而生
java 设计模式 单例
单例模式算是设计模式中最容易理解,也是最容易手写代码的模式了吧。但是其中的坑却不少,所以也常作为面试题来考。本文主要对几种单例写法的整理,并分析其优缺点。很多都是一些老生常谈的问题,但如果你不知道如何创建一个线程安全的单例,不知道什么是双检锁,那这篇文章可能会帮助到你。
懒汉式,线程不安全
当被问到要实现一个单例模式时,很多人的第一反应是写出如下的代码,包括教科书上也是这样
单例模式
香水浓
java
懒汉 调用getInstance方法时实例化
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(null == ins
安装Apache问题:系统找不到指定的文件 No installed service named "Apache2"
AdyZhang
apache http server
安装Apache问题:系统找不到指定的文件 No installed service named "Apache2"
每次到这一步都很小心防它的端口冲突问题,结果,特意留出来的80端口就是不能用,烦。
解决方法确保几处:
1、停止IIS启动
2、把端口80改成其它 (譬如90,800,,,什么数字都好)
3、防火墙(关掉试试)
在运行处输入 cmd 回车,转到apa
如何在android 文件选择器中选择多个图片或者视频?
aijuans
android
我的android app有这样的需求,在进行照片和视频上传的时候,需要一次性的从照片/视频库选择多条进行上传
但是android原生态的sdk中,只能一个一个的进行选择和上传。
我想知道是否有其他的android上传库可以解决这个问题,提供一个多选的功能,可以使checkbox之类的,一次选择多个 处理方法
官方的图片选择器(但是不支持所有版本的androi,只支持API Level
mysql中查询生日提醒的日期相关的sql
baalwolf
mysql
SELECT sysid,user_name,birthday,listid,userhead_50,CONCAT(YEAR(CURDATE()),DATE_FORMAT(birthday,'-%m-%d')),CURDATE(), dayofyear( CONCAT(YEAR(CURDATE()),DATE_FORMAT(birthday,'-%m-%d')))-dayofyear(
MongoDB索引文件破坏后导致查询错误的问题
BigBird2012
mongodb
问题描述:
MongoDB在非正常情况下关闭时,可能会导致索引文件破坏,造成数据在更新时没有反映到索引上。
解决方案:
使用脚本,重建MongoDB所有表的索引。
var names = db.getCollectionNames();
for( var i in names ){
var name = names[i];
print(name);
Javascript Promise
bijian1013
JavaScript Promise
Parse JavaScript SDK现在提供了支持大多数异步方法的兼容jquery的Promises模式,那么这意味着什么呢,读完下文你就了解了。
一.认识Promises
“Promises”代表着在javascript程序里下一个伟大的范式,但是理解他们为什么如此伟大不是件简
[Zookeeper学习笔记九]Zookeeper源代码分析之Zookeeper构造过程
bit1129
zookeeper
Zookeeper重载了几个构造函数,其中构造者可以提供参数最多,可定制性最多的构造函数是
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd, boolea
【Java命令三】jstack
bit1129
jstack
jstack是用于获得当前运行的Java程序所有的线程的运行情况(thread dump),不同于jmap用于获得memory dump
[hadoop@hadoop sbin]$ jstack
Usage:
jstack [-l] <pid>
(to connect to running process)
jstack -F
jboss 5.1启停脚本 动静分离部署
ronin47
以前启动jboss,往各种xml配置文件,现只要运行一句脚本即可。start nohup sh /**/run.sh -c servicename -b ip -g clustername -u broatcast jboss.messaging.ServerPeerID=int -Djboss.service.binding.set=p
UI之如何打磨设计能力?
brotherlamp
UI ui教程 ui自学 ui资料 ui视频
在越来越拥挤的初创企业世界里,视觉设计的重要性往往可以与杀手级用户体验比肩。在许多情况下,尤其对于 Web 初创企业而言,这两者都是不可或缺的。前不久我们在《右脑革命:别学编程了,学艺术吧》中也曾发出过重视设计的呼吁。如何才能提高初创企业的设计能力呢?以下是 9 位创始人的体会。
1.找到自己的方式
如果你是设计师,要想提高技能可以去设计博客和展示好设计的网站如D-lists或
三色旗算法
bylijinnan
java 算法
import java.util.Arrays;
/**
问题:
假设有一条绳子,上面有红、白、蓝三种颜色的旗子,起初绳子上的旗子颜色并没有顺序,
您希望将之分类,并排列为蓝、白、红的顺序,要如何移动次数才会最少,注意您只能在绳
子上进行这个动作,而且一次只能调换两个旗子。
网上的解法大多类似:
在一条绳子上移动,在程式中也就意味只能使用一个阵列,而不使用其它的阵列来
警告:No configuration found for the specified action: \'s
chiangfai
configuration
1.index.jsp页面form标签未指定namespace属性。
<!--index.jsp代码-->
<%@taglib prefix="s" uri="/struts-tags"%>
...
<s:form action="submit" method="post"&g
redis -- hash_max_zipmap_entries设置过大有问题
chenchao051
redis hash
使用redis时为了使用hash追求更高的内存使用率,我们一般都用hash结构,并且有时候会把hash_max_zipmap_entries这个值设置的很大,很多资料也推荐设置到1000,默认设置为了512,但是这里有个坑
#define ZIPMAP_BIGLEN 254
#define ZIPMAP_END 255
/* Return th
select into outfile access deny问题
daizj
mysql txt 导出数据到文件
本文转自:http://hatemysql.com/2010/06/29/select-into-outfile-access-deny%E9%97%AE%E9%A2%98/
为应用建立了rnd的帐号,专门为他们查询线上数据库用的,当然,只有他们上了生产网络以后才能连上数据库,安全方面我们还是很注意的,呵呵。
授权的语句如下:
grant select on armory.* to rn
phpexcel导出excel表简单入门示例
dcj3sjt126com
PHP Excel phpexcel
<?php
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);
if (PHP_SAPI == 'cli')
die('This example should only be run from a Web Brows
美国电影超短200句
dcj3sjt126com
电影
1. I see. 我明白了。2. I quit! 我不干了!3. Let go! 放手!4. Me too. 我也是。5. My god! 天哪!6. No way! 不行!7. Come on. 来吧(赶快)8. Hold on. 等一等。9. I agree。 我同意。10. Not bad. 还不错。11. Not yet. 还没。12. See you. 再见。13. Shut up!
Java访问远程服务
dyy_gusi
httpclient webservice get post
随着webService的崛起,我们开始中会越来越多的使用到访问远程webService服务。当然对于不同的webService框架一般都有自己的client包供使用,但是如果使用webService框架自己的client包,那么必然需要在自己的代码中引入它的包,如果同时调运了多个不同框架的webService,那么就需要同时引入多个不同的clien
Maven的settings.xml配置
geeksun
settings.xml
settings.xml是Maven的配置文件,下面解释一下其中的配置含义:
settings.xml存在于两个地方:
1.安装的地方:$M2_HOME/conf/settings.xml
2.用户的目录:${user.home}/.m2/settings.xml
前者又被叫做全局配置,后者被称为用户配置。如果两者都存在,它们的内容将被合并,并且用户范围的settings.xml优先。
ubuntu的init与系统服务设置
hongtoushizi
ubuntu
转载自:
http://iysm.net/?p=178 init
Init是位于/sbin/init的一个程序,它是在linux下,在系统启动过程中,初始化所有的设备驱动程序和数据结构等之后,由内核启动的一个用户级程序,并由此init程序进而完成系统的启动过程。
ubuntu与传统的linux略有不同,使用upstart完成系统的启动,但表面上仍维持init程序的形式。
运行
跟我学Nginx+Lua开发目录贴
jinnianshilongnian
nginx lua
使用Nginx+Lua开发近一年的时间,学习和实践了一些Nginx+Lua开发的架构,为了让更多人使用Nginx+Lua架构开发,利用春节期间总结了一份基本的学习教程,希望对大家有用。也欢迎谈探讨学习一些经验。
目录
第一章 安装Nginx+Lua开发环境
第二章 Nginx+Lua开发入门
第三章 Redis/SSDB+Twemproxy安装与使用
第四章 L
php位运算符注意事项
home198979
位运算 PHP &
$a = $b = $c = 0;
$a & $b = 1;
$b | $c = 1
问a,b,c最终为多少?
当看到这题时,我犯了一个低级错误,误 以为位运算符会改变变量的值。所以得出结果是1 1 0
但是位运算符是不会改变变量的值的,例如:
$a=1;$b=2;
$a&$b;
这样a,b的值不会有任何改变
Linux shell数组建立和使用技巧
pda158
linux
1.数组定义 [chengmo@centos5 ~]$ a=(1 2 3 4 5) [chengmo@centos5 ~]$ echo $a 1 一对括号表示是数组,数组元素用“空格”符号分割开。
2.数组读取与赋值 得到长度: [chengmo@centos5 ~]$ echo ${#a[@]} 5 用${#数组名[@或
hotspot源码(JDK7)
ol_beta
java HotSpot jvm
源码结构图,方便理解:
├─agent Serviceab
Oracle基本事务和ForAll执行批量DML练习
vipbooks
oracle sql
基本事务的使用:
从账户一的余额中转100到账户二的余额中去,如果账户二不存在或账户一中的余额不足100则整笔交易回滚
select * from account;
-- 创建一张账户表
create table account(
-- 账户ID
id number(3) not null,
-- 账户名称
nam